147 lines
4.4 KiB
Go
147 lines
4.4 KiB
Go
package comet
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strconv"
|
|
|
|
abci "github.com/cometbft/cometbft/api/cometbft/abci/v1"
|
|
rpcclient "github.com/cometbft/cometbft/rpc/client"
|
|
gogogrpc "github.com/cosmos/gogoproto/grpc"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/encoding"
|
|
"google.golang.org/grpc/metadata"
|
|
"google.golang.org/grpc/status"
|
|
|
|
errorsmod "cosmossdk.io/errors"
|
|
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
"github.com/cosmos/cosmos-sdk/codec/types"
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
)
|
|
|
|
const grpcBlockHeightHeader = "x-cosmos-block-height"
|
|
|
|
var (
|
|
_ gogogrpc.ClientConn = &CometBFTBroadcaster{}
|
|
_ grpc.ClientConnInterface = &CometBFTBroadcaster{}
|
|
)
|
|
|
|
func (c *CometBFTBroadcaster) NewStream(_ context.Context, _ *grpc.StreamDesc, _ string, _ ...grpc.CallOption) (grpc.ClientStream, error) {
|
|
return nil, errors.New("not implemented")
|
|
}
|
|
|
|
// Invoke implements the gRPC ClientConn interface by forwarding the RPC call to CometBFT's ABCI Query.
|
|
// It marshals the request, sends it as an ABCI query, and unmarshals the response.
|
|
func (c *CometBFTBroadcaster) Invoke(ctx context.Context, method string, req, reply interface{}, opts ...grpc.CallOption) (err error) {
|
|
reqBz, err := c.getRPCCodec().Marshal(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// parse height header
|
|
md, _ := metadata.FromOutgoingContext(ctx)
|
|
var height int64
|
|
if heights := md.Get(grpcBlockHeightHeader); len(heights) > 0 {
|
|
height, err = strconv.ParseInt(heights[0], 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if height < 0 {
|
|
return errorsmod.Wrapf(
|
|
sdkerrors.ErrInvalidRequest,
|
|
"client.Context.Invoke: height (%d) from %q must be >= 0", height, grpcBlockHeightHeader)
|
|
}
|
|
}
|
|
|
|
abciR := abci.QueryRequest{
|
|
Path: method,
|
|
Data: reqBz,
|
|
Height: height,
|
|
}
|
|
|
|
res, err := c.queryABCI(ctx, abciR)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = c.getRPCCodec().Unmarshal(res.Value, reply)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create header metadata. For now the headers contain:
|
|
// - block height
|
|
// We then parse all the call options, if the call option is a
|
|
// HeaderCallOption, then we manually set the value of that header to the
|
|
// metadata.
|
|
md = metadata.Pairs(grpcBlockHeightHeader, strconv.FormatInt(res.Height, 10))
|
|
for _, callOpt := range opts {
|
|
header, ok := callOpt.(grpc.HeaderCallOption)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
*header.HeaderAddr = md
|
|
}
|
|
|
|
if c.cdc.InterfaceRegistry() != nil {
|
|
return types.UnpackInterfaces(reply, c.cdc.InterfaceRegistry())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// queryABCI performs an ABCI query request to the CometBFT RPC client.
|
|
// If the RPC query fails or returns a non-OK response, it will return an error.
|
|
// The response is converted from ABCI error codes to gRPC status errors.
|
|
func (c *CometBFTBroadcaster) queryABCI(ctx context.Context, req abci.QueryRequest) (abci.QueryResponse, error) {
|
|
opts := rpcclient.ABCIQueryOptions{
|
|
Height: req.Height,
|
|
Prove: req.Prove,
|
|
}
|
|
|
|
result, err := c.rpcClient.ABCIQueryWithOptions(ctx, req.Path, req.Data, opts)
|
|
if err != nil {
|
|
return abci.QueryResponse{}, err
|
|
}
|
|
|
|
if !result.Response.IsOK() {
|
|
return abci.QueryResponse{}, sdkErrorToGRPCError(result.Response)
|
|
}
|
|
|
|
return result.Response, nil
|
|
}
|
|
|
|
// sdkErrorToGRPCError converts an ABCI query response error code to an appropriate gRPC status error.
|
|
// It maps common SDK error codes to their gRPC equivalents:
|
|
// - ErrInvalidRequest -> InvalidArgument
|
|
// - ErrUnauthorized -> Unauthenticated
|
|
// - ErrKeyNotFound -> NotFound
|
|
// Any other error codes are mapped to Unknown.
|
|
func sdkErrorToGRPCError(resp abci.QueryResponse) error {
|
|
switch resp.Code {
|
|
case sdkerrors.ErrInvalidRequest.ABCICode():
|
|
return status.Error(codes.InvalidArgument, resp.Log)
|
|
case sdkerrors.ErrUnauthorized.ABCICode():
|
|
return status.Error(codes.Unauthenticated, resp.Log)
|
|
case sdkerrors.ErrKeyNotFound.ABCICode():
|
|
return status.Error(codes.NotFound, resp.Log)
|
|
default:
|
|
return status.Error(codes.Unknown, resp.Log)
|
|
}
|
|
}
|
|
|
|
// getRPCCodec returns the gRPC codec for the CometBFT broadcaster.
|
|
// If the broadcaster's codec implements GRPCCodecProvider, it returns its gRPC codec.
|
|
// Otherwise, it creates a new ProtoCodec with the broadcaster's interface registry and returns its gRPC codec.
|
|
func (c *CometBFTBroadcaster) getRPCCodec() encoding.Codec {
|
|
cdc, ok := c.cdc.(codec.GRPCCodecProvider)
|
|
if !ok {
|
|
return codec.NewProtoCodec(c.cdc.InterfaceRegistry()).GRPCCodec()
|
|
}
|
|
|
|
return cdc.GRPCCodec()
|
|
}
|