Co-authored-by: Alex | Interchain Labs <alex@skip.money> Co-authored-by: Julien Robert <julien@rbrt.fr>
607 lines
18 KiB
Go
607 lines
18 KiB
Go
package cometbft
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
abci "github.com/cometbft/cometbft/abci/types"
|
|
abciproto "github.com/cometbft/cometbft/api/cometbft/abci/v1"
|
|
rpchttp "github.com/cometbft/cometbft/rpc/client/http"
|
|
"github.com/cosmos/gogoproto/proto"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
|
|
autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
|
|
cmtv1beta1 "cosmossdk.io/api/cosmos/base/tendermint/v1beta1"
|
|
"cosmossdk.io/core/server"
|
|
corestore "cosmossdk.io/core/store"
|
|
"cosmossdk.io/core/transaction"
|
|
"cosmossdk.io/log"
|
|
storeserver "cosmossdk.io/server/v2/store"
|
|
|
|
"github.com/cosmos/cosmos-sdk/client"
|
|
"github.com/cosmos/cosmos-sdk/client/grpc/cmtservice"
|
|
nodeservice "github.com/cosmos/cosmos-sdk/client/grpc/node"
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
"github.com/cosmos/cosmos-sdk/types/query"
|
|
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
|
|
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
|
|
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
|
|
)
|
|
|
|
type appSimulator[T transaction.Tx] interface {
|
|
Simulate(ctx context.Context, tx T) (server.TxResult, corestore.WriterMap, error)
|
|
}
|
|
|
|
// gRPCServiceRegistrar returns a function that registers the CometBFT gRPC service
|
|
// Those services are defined for backward compatibility.
|
|
// Eventually, they will be removed in favor of the new gRPC services.
|
|
func gRPCServiceRegistrar[T transaction.Tx](
|
|
clientCtx client.Context,
|
|
cfg server.ConfigMap,
|
|
cometBFTAppConfig *AppTomlConfig,
|
|
txCodec transaction.Codec[T],
|
|
consensus abci.Application,
|
|
app appSimulator[T],
|
|
) func(srv *grpc.Server) error {
|
|
return func(srv *grpc.Server) error {
|
|
cmtservice.RegisterServiceServer(srv, cmtservice.NewQueryServer(clientCtx.Client, consensus.Query, clientCtx.ConsensusAddressCodec))
|
|
txtypes.RegisterServiceServer(srv, txServer[T]{clientCtx, txCodec, app, consensus})
|
|
nodeservice.RegisterServiceServer(srv, nodeServer[T]{cfg, cometBFTAppConfig, consensus})
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
type txServer[T transaction.Tx] struct {
|
|
clientCtx client.Context
|
|
txCodec transaction.Codec[T]
|
|
app appSimulator[T]
|
|
consensus abci.Application
|
|
}
|
|
|
|
// BroadcastTx implements tx.ServiceServer.
|
|
func (t txServer[T]) BroadcastTx(ctx context.Context, req *txtypes.BroadcastTxRequest) (*txtypes.BroadcastTxResponse, error) {
|
|
return client.TxServiceBroadcast(ctx, t.clientCtx, req)
|
|
}
|
|
|
|
// GetBlockWithTxs implements tx.ServiceServer.
|
|
func (t txServer[T]) GetBlockWithTxs(ctx context.Context, req *txtypes.GetBlockWithTxsRequest) (*txtypes.GetBlockWithTxsResponse, error) {
|
|
logger := log.NewNopLogger()
|
|
if req == nil {
|
|
return nil, status.Error(codes.InvalidArgument, "request cannot be nil")
|
|
}
|
|
|
|
resp, err := t.consensus.Info(ctx, &abci.InfoRequest{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
currentHeight := resp.LastBlockHeight
|
|
|
|
if req.Height < 1 || req.Height > currentHeight {
|
|
return nil, sdkerrors.ErrInvalidHeight.Wrapf("requested height %d but height must not be less than 1 "+
|
|
"or greater than the current height %d", req.Height, currentHeight)
|
|
}
|
|
|
|
node, err := t.clientCtx.GetNode()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
blockID, block, err := cmtservice.GetProtoBlock(ctx, node, &req.Height)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var offset, limit uint64
|
|
if req.Pagination != nil {
|
|
offset = req.Pagination.Offset
|
|
limit = req.Pagination.Limit
|
|
} else {
|
|
offset = 0
|
|
limit = query.DefaultLimit
|
|
}
|
|
|
|
blockTxs := block.Data.Txs
|
|
blockTxsLn := uint64(len(blockTxs))
|
|
txs := make([]*txtypes.Tx, 0, limit)
|
|
if offset >= blockTxsLn && blockTxsLn != 0 {
|
|
return nil, sdkerrors.ErrInvalidRequest.Wrapf("out of range: cannot paginate %d txs with offset %d and limit %d", blockTxsLn, offset, limit)
|
|
}
|
|
decodeTxAt := func(i uint64) error {
|
|
tx := blockTxs[i]
|
|
txb, err := t.txCodec.Decode(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// txServer works only with sdk.Tx
|
|
p, err := any(txb).(interface{ AsTx() (*txtypes.Tx, error) }).AsTx()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
txs = append(txs, p)
|
|
return nil
|
|
}
|
|
if req.Pagination != nil && req.Pagination.Reverse {
|
|
for i, count := offset, uint64(0); i > 0 && count != limit; i, count = i-1, count+1 {
|
|
if err = decodeTxAt(i); err != nil {
|
|
logger.Error("failed to decode tx", "error", err)
|
|
}
|
|
}
|
|
} else {
|
|
for i, count := offset, uint64(0); i < blockTxsLn && count != limit; i, count = i+1, count+1 {
|
|
if err = decodeTxAt(i); err != nil {
|
|
logger.Error("failed to decode tx", "error", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return &txtypes.GetBlockWithTxsResponse{
|
|
Txs: txs,
|
|
BlockId: &blockID,
|
|
Block: block,
|
|
Pagination: &query.PageResponse{
|
|
Total: blockTxsLn,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// GetTx implements tx.ServiceServer.
|
|
func (t txServer[T]) GetTx(ctx context.Context, req *txtypes.GetTxRequest) (*txtypes.GetTxResponse, error) {
|
|
if req == nil {
|
|
return nil, status.Error(codes.InvalidArgument, "request cannot be nil")
|
|
}
|
|
|
|
if len(req.Hash) == 0 {
|
|
return nil, status.Error(codes.InvalidArgument, "tx hash cannot be empty")
|
|
}
|
|
|
|
result, err := authtx.QueryTx(t.clientCtx, req.Hash)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "not found") {
|
|
return nil, status.Errorf(codes.NotFound, "tx not found: %s", req.Hash)
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
protoTx, ok := result.Tx.GetCachedValue().(*txtypes.Tx)
|
|
if !ok {
|
|
return nil, status.Errorf(codes.Internal, "expected %T, got %T", txtypes.Tx{}, result.Tx.GetCachedValue())
|
|
}
|
|
|
|
return &txtypes.GetTxResponse{
|
|
Tx: protoTx,
|
|
TxResponse: result,
|
|
}, nil
|
|
}
|
|
|
|
// GetTxsEvent implements tx.ServiceServer.
|
|
func (t txServer[T]) GetTxsEvent(ctx context.Context, req *txtypes.GetTxsEventRequest) (*txtypes.GetTxsEventResponse, error) {
|
|
if req == nil {
|
|
return nil, status.Error(codes.InvalidArgument, "request cannot be nil")
|
|
}
|
|
|
|
orderBy := parseOrderBy(req.OrderBy)
|
|
|
|
result, err := authtx.QueryTxsByEvents(t.clientCtx, int(req.Page), int(req.Limit), req.Query, orderBy)
|
|
if err != nil {
|
|
return nil, status.Error(codes.Internal, err.Error())
|
|
}
|
|
|
|
txsList := make([]*txtypes.Tx, len(result.Txs))
|
|
for i, tx := range result.Txs {
|
|
protoTx, ok := tx.Tx.GetCachedValue().(*txtypes.Tx)
|
|
if !ok {
|
|
return nil, status.Errorf(codes.Internal, "getting cached value failed expected %T, got %T", txtypes.Tx{}, tx.Tx.GetCachedValue())
|
|
}
|
|
|
|
txsList[i] = protoTx
|
|
}
|
|
|
|
return &txtypes.GetTxsEventResponse{
|
|
Txs: txsList,
|
|
TxResponses: result.Txs,
|
|
Total: result.TotalCount,
|
|
}, nil
|
|
}
|
|
|
|
// Simulate implements tx.ServiceServer.
|
|
func (t txServer[T]) Simulate(ctx context.Context, req *txtypes.SimulateRequest) (*txtypes.SimulateResponse, error) {
|
|
if req == nil {
|
|
return nil, status.Error(codes.InvalidArgument, "invalid empty tx")
|
|
}
|
|
|
|
txBytes := req.TxBytes
|
|
if txBytes == nil && req.Tx != nil {
|
|
// This block is for backwards-compatibility.
|
|
// We used to support passing a `Tx` in req. But if we do that, sig
|
|
// verification might not pass, because the .Marshal() below might not
|
|
// be the same marshaling done by the client.
|
|
var err error
|
|
txBytes, err = proto.Marshal(req.Tx)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.InvalidArgument, "invalid tx; %v", err)
|
|
}
|
|
}
|
|
|
|
if txBytes == nil {
|
|
return nil, status.Errorf(codes.InvalidArgument, "empty txBytes is not allowed")
|
|
}
|
|
|
|
tx, err := t.txCodec.Decode(txBytes)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.InvalidArgument, "failed to decode tx: %v", err)
|
|
}
|
|
|
|
txResult, _, err := t.app.Simulate(ctx, tx)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Unknown, "%v with gas used: '%d'", err, txResult.GasUsed)
|
|
}
|
|
|
|
msgResponses := make([]*codectypes.Any, 0, len(txResult.Resp))
|
|
// pack the messages into Any
|
|
for _, msg := range txResult.Resp {
|
|
anyMsg, err := codectypes.NewAnyWithValue(msg)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Unknown, "failed to pack message response: %v", err)
|
|
}
|
|
msgResponses = append(msgResponses, anyMsg)
|
|
}
|
|
|
|
event, err := intoABCIEvents(txResult.Events, map[string]struct{}{}, false)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Unknown, "failed to convert events: %v", err)
|
|
}
|
|
|
|
return &txtypes.SimulateResponse{
|
|
GasInfo: &sdk.GasInfo{
|
|
GasUsed: txResult.GasUsed,
|
|
GasWanted: txResult.GasWanted,
|
|
},
|
|
Result: &sdk.Result{
|
|
MsgResponses: msgResponses,
|
|
Events: event,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// TxDecode implements tx.ServiceServer.
|
|
func (t txServer[T]) TxDecode(ctx context.Context, req *txtypes.TxDecodeRequest) (*txtypes.TxDecodeResponse, error) {
|
|
if req.TxBytes == nil {
|
|
return nil, status.Error(codes.InvalidArgument, "invalid empty tx bytes")
|
|
}
|
|
|
|
txb, err := t.txCodec.Decode(req.TxBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// txServer works only with sdk.Tx
|
|
tx, err := any(txb).(interface{ AsTx() (*txtypes.Tx, error) }).AsTx()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &txtypes.TxDecodeResponse{
|
|
Tx: tx,
|
|
}, nil
|
|
}
|
|
|
|
// TxDecodeAmino implements tx.ServiceServer.
|
|
func (t txServer[T]) TxDecodeAmino(_ context.Context, req *txtypes.TxDecodeAminoRequest) (*txtypes.TxDecodeAminoResponse, error) {
|
|
if req.AminoBinary == nil {
|
|
return nil, status.Error(codes.InvalidArgument, "invalid empty tx bytes")
|
|
}
|
|
|
|
var stdTx legacytx.StdTx
|
|
err := t.clientCtx.LegacyAmino.Unmarshal(req.AminoBinary, &stdTx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
res, err := t.clientCtx.LegacyAmino.MarshalJSON(stdTx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &txtypes.TxDecodeAminoResponse{
|
|
AminoJson: string(res),
|
|
}, nil
|
|
}
|
|
|
|
// TxEncode implements tx.ServiceServer.
|
|
func (t txServer[T]) TxEncode(_ context.Context, req *txtypes.TxEncodeRequest) (*txtypes.TxEncodeResponse, error) {
|
|
if req.Tx == nil {
|
|
return nil, status.Error(codes.InvalidArgument, "invalid empty tx")
|
|
}
|
|
|
|
bodyBytes, err := t.clientCtx.Codec.Marshal(req.Tx.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
authInfoBytes, err := t.clientCtx.Codec.Marshal(req.Tx.AuthInfo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
raw := &txtypes.TxRaw{
|
|
BodyBytes: bodyBytes,
|
|
AuthInfoBytes: authInfoBytes,
|
|
Signatures: req.Tx.Signatures,
|
|
}
|
|
|
|
encodedBytes, err := t.clientCtx.Codec.Marshal(raw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &txtypes.TxEncodeResponse{
|
|
TxBytes: encodedBytes,
|
|
}, nil
|
|
}
|
|
|
|
// TxEncodeAmino implements tx.ServiceServer.
|
|
func (t txServer[T]) TxEncodeAmino(_ context.Context, req *txtypes.TxEncodeAminoRequest) (*txtypes.TxEncodeAminoResponse, error) {
|
|
if req.AminoJson == "" {
|
|
return nil, status.Error(codes.InvalidArgument, "invalid empty tx json")
|
|
}
|
|
|
|
var stdTx legacytx.StdTx
|
|
err := t.clientCtx.LegacyAmino.UnmarshalJSON([]byte(req.AminoJson), &stdTx)
|
|
if err != nil {
|
|
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("invalid request %s", err))
|
|
}
|
|
|
|
encodedBytes, err := t.clientCtx.LegacyAmino.Marshal(stdTx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &txtypes.TxEncodeAminoResponse{
|
|
AminoBinary: encodedBytes,
|
|
}, nil
|
|
}
|
|
|
|
var _ txtypes.ServiceServer = txServer[transaction.Tx]{}
|
|
|
|
type nodeServer[T transaction.Tx] struct {
|
|
cfg server.ConfigMap
|
|
cometBFTAppConfig *AppTomlConfig
|
|
consensus abci.Application
|
|
}
|
|
|
|
func (s nodeServer[T]) Config(ctx context.Context, _ *nodeservice.ConfigRequest) (*nodeservice.ConfigResponse, error) {
|
|
minGasPricesStr := ""
|
|
minGasPrices, ok := s.cfg["server"].(map[string]interface{})["minimum-gas-prices"]
|
|
if ok {
|
|
minGasPricesStr = minGasPrices.(string)
|
|
}
|
|
|
|
storeCfg, err := storeserver.UnmarshalConfig(s.cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &nodeservice.ConfigResponse{
|
|
MinimumGasPrice: minGasPricesStr,
|
|
PruningKeepRecent: fmt.Sprintf("%d", storeCfg.Options.SCPruningOption.KeepRecent),
|
|
PruningInterval: fmt.Sprintf("%d", storeCfg.Options.SCPruningOption.Interval),
|
|
HaltHeight: s.cometBFTAppConfig.HaltHeight,
|
|
}, nil
|
|
}
|
|
|
|
func (s nodeServer[T]) Status(ctx context.Context, _ *nodeservice.StatusRequest) (*nodeservice.StatusResponse, error) {
|
|
nodeInfo, err := s.consensus.Info(ctx, &abciproto.InfoRequest{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &nodeservice.StatusResponse{
|
|
Height: uint64(nodeInfo.LastBlockHeight),
|
|
Timestamp: nil,
|
|
AppHash: nil,
|
|
ValidatorHash: nodeInfo.LastBlockAppHash,
|
|
}, nil
|
|
}
|
|
|
|
// CometBFTAutoCLIDescriptor is the auto-generated CLI descriptor for the CometBFT service
|
|
var CometBFTAutoCLIDescriptor = &autocliv1.ServiceCommandDescriptor{
|
|
Service: cmtv1beta1.Service_ServiceDesc.ServiceName,
|
|
RpcCommandOptions: []*autocliv1.RpcCommandOptions{
|
|
{
|
|
RpcMethod: "GetNodeInfo",
|
|
Use: "node-info",
|
|
Short: "Query the current node info",
|
|
},
|
|
{
|
|
RpcMethod: "GetSyncing",
|
|
Use: "syncing",
|
|
Short: "Query node syncing status",
|
|
},
|
|
{
|
|
RpcMethod: "GetLatestBlock",
|
|
Use: "block-latest",
|
|
Short: "Query for the latest committed block",
|
|
},
|
|
{
|
|
RpcMethod: "GetBlockByHeight",
|
|
Use: "block-by-height <height>",
|
|
Short: "Query for a committed block by height",
|
|
Long: "Query for a specific committed block using the CometBFT RPC `block_by_height` method",
|
|
PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "height"}},
|
|
},
|
|
{
|
|
RpcMethod: "GetLatestValidatorSet",
|
|
Use: "validator-set",
|
|
Alias: []string{"validator-set-latest", "comet-validator-set", "cometbft-validator-set", "tendermint-validator-set"},
|
|
Short: "Query for the latest validator set",
|
|
},
|
|
{
|
|
RpcMethod: "GetValidatorSetByHeight",
|
|
Use: "validator-set-by-height <height>",
|
|
Short: "Query for a validator set by height",
|
|
PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "height"}},
|
|
},
|
|
{
|
|
RpcMethod: "ABCIQuery",
|
|
Skip: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
func parseOrderBy(orderBy txtypes.OrderBy) string {
|
|
switch orderBy {
|
|
case txtypes.OrderBy_ORDER_BY_ASC:
|
|
return "asc"
|
|
case txtypes.OrderBy_ORDER_BY_DESC:
|
|
return "desc"
|
|
default:
|
|
return "" // Defaults to CometBFT's default, which is `asc` now.
|
|
}
|
|
}
|
|
|
|
func (c *consensus[T]) maybeHandleExternalServices(ctx context.Context, req *abci.QueryRequest) (transaction.Msg, error) {
|
|
// Handle comet service
|
|
if strings.HasPrefix(req.Path, "/cosmos.base.tendermint.v1beta1.Service") {
|
|
rpcClient, _ := rpchttp.New(c.cfg.ConfigTomlConfig.RPC.ListenAddress)
|
|
|
|
cometQServer := cmtservice.NewQueryServer(rpcClient, c.Query, c.appCodecs.ConsensusAddressCodec)
|
|
paths := strings.Split(req.Path, "/")
|
|
if len(paths) <= 2 {
|
|
return nil, fmt.Errorf("invalid request path: %s", req.Path)
|
|
}
|
|
|
|
var resp transaction.Msg
|
|
var err error
|
|
switch paths[2] {
|
|
case "GetNodeInfo":
|
|
resp, err = handleExternalService(ctx, req, cometQServer.GetNodeInfo)
|
|
case "GetSyncing":
|
|
resp, err = handleExternalService(ctx, req, cometQServer.GetSyncing)
|
|
case "GetLatestBlock":
|
|
resp, err = handleExternalService(ctx, req, cometQServer.GetLatestBlock)
|
|
case "GetBlockByHeight":
|
|
resp, err = handleExternalService(ctx, req, cometQServer.GetBlockByHeight)
|
|
case "GetLatestValidatorSet":
|
|
resp, err = handleExternalService(ctx, req, cometQServer.GetLatestValidatorSet)
|
|
case "GetValidatorSetByHeight":
|
|
resp, err = handleExternalService(ctx, req, cometQServer.GetValidatorSetByHeight)
|
|
case "ABCIQuery":
|
|
resp, err = handleExternalService(ctx, req, cometQServer.ABCIQuery)
|
|
}
|
|
|
|
return resp, err
|
|
}
|
|
|
|
// Handle node service
|
|
if strings.HasPrefix(req.Path, "/cosmos.base.node.v1beta1.Service") {
|
|
nodeQService := nodeServer[T]{c.cfgMap, c.cfg.AppTomlConfig, c}
|
|
paths := strings.Split(req.Path, "/")
|
|
if len(paths) <= 2 {
|
|
return nil, fmt.Errorf("invalid request path: %s", req.Path)
|
|
}
|
|
|
|
var resp transaction.Msg
|
|
var err error
|
|
switch paths[2] {
|
|
case "Config":
|
|
resp, err = handleExternalService(ctx, req, nodeQService.Config)
|
|
case "Status":
|
|
resp, err = handleExternalService(ctx, req, nodeQService.Status)
|
|
}
|
|
|
|
return resp, err
|
|
}
|
|
|
|
// Handle tx service
|
|
if strings.HasPrefix(req.Path, "/cosmos.tx.v1beta1.Service") {
|
|
rpcClient, _ := client.NewClientFromNode(c.cfg.ConfigTomlConfig.RPC.ListenAddress)
|
|
|
|
txConfig := authtx.NewTxConfig(
|
|
c.appCodecs.AppCodec,
|
|
c.appCodecs.AppCodec.InterfaceRegistry().SigningContext().AddressCodec(),
|
|
c.appCodecs.AppCodec.InterfaceRegistry().SigningContext().ValidatorAddressCodec(),
|
|
authtx.DefaultSignModes,
|
|
)
|
|
|
|
// init simple client context
|
|
clientCtx := client.Context{}.
|
|
WithLegacyAmino(c.appCodecs.LegacyAmino.(*codec.LegacyAmino)).
|
|
WithCodec(c.appCodecs.AppCodec).
|
|
WithNodeURI(c.cfg.AppTomlConfig.Address).
|
|
WithClient(rpcClient).
|
|
WithTxConfig(txConfig)
|
|
|
|
txService := txServer[T]{
|
|
clientCtx: clientCtx,
|
|
txCodec: c.appCodecs.TxCodec,
|
|
app: c.app,
|
|
consensus: c,
|
|
}
|
|
paths := strings.Split(req.Path, "/")
|
|
if len(paths) <= 2 {
|
|
return nil, fmt.Errorf("invalid request path: %s", req.Path)
|
|
}
|
|
|
|
var resp transaction.Msg
|
|
var err error
|
|
switch paths[2] {
|
|
case "Simulate":
|
|
resp, err = handleExternalService(ctx, req, txService.Simulate)
|
|
case "GetTx":
|
|
resp, err = handleExternalService(ctx, req, txService.GetTx)
|
|
case "BroadcastTx":
|
|
return nil, errors.New("can't route a broadcast tx message")
|
|
case "GetTxsEvent":
|
|
resp, err = handleExternalService(ctx, req, txService.GetTxsEvent)
|
|
case "GetBlockWithTxs":
|
|
resp, err = handleExternalService(ctx, req, txService.GetBlockWithTxs)
|
|
case "TxDecode":
|
|
resp, err = handleExternalService(ctx, req, txService.TxDecode)
|
|
case "TxEncode":
|
|
resp, err = handleExternalService(ctx, req, txService.TxEncode)
|
|
case "TxEncodeAmino":
|
|
resp, err = handleExternalService(ctx, req, txService.TxEncodeAmino)
|
|
case "TxDecodeAmino":
|
|
resp, err = handleExternalService(ctx, req, txService.TxDecodeAmino)
|
|
}
|
|
|
|
return resp, err
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func handleExternalService[T any, PT interface {
|
|
*T
|
|
proto.Message
|
|
},
|
|
U any, UT interface {
|
|
*U
|
|
proto.Message
|
|
}](
|
|
ctx context.Context,
|
|
rawReq *abciproto.QueryRequest,
|
|
handler func(ctx context.Context, msg PT) (UT, error),
|
|
) (transaction.Msg, error) {
|
|
req := PT(new(T))
|
|
err := proto.Unmarshal(rawReq.Data, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
typedResp, err := handler(ctx, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return typedResp, nil
|
|
}
|