Make gRPC requests go through tendermint Query (#8549)
* Make gRPC requests go through tendermint Query * Remove commented code * Dry run in InitChain? * Save type of first run * Add metadata in repsonse * Factorize some code * Fix lint * Update comments * Fix md test * Fix test expected * Don't put RunGRPCQuery as clientCtx method * Update baseapp/grpcserver.go Co-authored-by: Robert Zaremba <robert@zaremba.ch> * Address review comments Co-authored-by: Robert Zaremba <robert@zaremba.ch>
This commit is contained in:
+56
-47
@@ -24,86 +24,54 @@ var _ gogogrpc.ClientConn = Context{}
|
||||
var protoCodec = encoding.GetCodec(proto.Name)
|
||||
|
||||
// Invoke implements the grpc ClientConn.Invoke method
|
||||
func (ctx Context) Invoke(grpcCtx gocontext.Context, method string, args, reply interface{}, opts ...grpc.CallOption) (err error) {
|
||||
func (ctx Context) Invoke(grpcCtx gocontext.Context, method string, req, reply interface{}, opts ...grpc.CallOption) (err error) {
|
||||
// Two things can happen here:
|
||||
// 1. either we're broadcasting a Tx, in which call we call Tendermint's broadcast endpoint directly,
|
||||
// 2. or we are querying for state, in which case we call ABCI's Query.
|
||||
|
||||
// In both cases, we don't allow empty request args (it will panic unexpectedly).
|
||||
if reflect.ValueOf(args).IsNil() {
|
||||
// In both cases, we don't allow empty request req (it will panic unexpectedly).
|
||||
if reflect.ValueOf(req).IsNil() {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "request cannot be nil")
|
||||
}
|
||||
|
||||
// Case 1. Broadcasting a Tx.
|
||||
if isBroadcast(method) {
|
||||
req, ok := args.(*tx.BroadcastTxRequest)
|
||||
if reqProto, ok := req.(*tx.BroadcastTxRequest); ok {
|
||||
if !ok {
|
||||
return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "expected %T, got %T", (*tx.BroadcastTxRequest)(nil), args)
|
||||
return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "expected %T, got %T", (*tx.BroadcastTxRequest)(nil), req)
|
||||
}
|
||||
res, ok := reply.(*tx.BroadcastTxResponse)
|
||||
resProto, ok := reply.(*tx.BroadcastTxResponse)
|
||||
if !ok {
|
||||
return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "expected %T, got %T", (*tx.BroadcastTxResponse)(nil), args)
|
||||
return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "expected %T, got %T", (*tx.BroadcastTxResponse)(nil), req)
|
||||
}
|
||||
|
||||
broadcastRes, err := TxServiceBroadcast(grpcCtx, ctx, req)
|
||||
broadcastRes, err := TxServiceBroadcast(grpcCtx, ctx, reqProto)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*res = *broadcastRes
|
||||
*resProto = *broadcastRes
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Case 2. Querying state.
|
||||
reqBz, err := protoCodec.Marshal(args)
|
||||
inMd, _ := metadata.FromOutgoingContext(grpcCtx)
|
||||
abciRes, outMd, err := RunGRPCQuery(ctx, grpcCtx, method, req, inMd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// parse height header
|
||||
md, _ := metadata.FromOutgoingContext(grpcCtx)
|
||||
if heights := md.Get(grpctypes.GRPCBlockHeightHeader); len(heights) > 0 {
|
||||
height, err := strconv.ParseInt(heights[0], 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if height < 0 {
|
||||
return sdkerrors.Wrapf(
|
||||
sdkerrors.ErrInvalidRequest,
|
||||
"client.Context.Invoke: height (%d) from %q must be >= 0", height, grpctypes.GRPCBlockHeightHeader)
|
||||
}
|
||||
|
||||
ctx = ctx.WithHeight(height)
|
||||
}
|
||||
|
||||
req := abci.RequestQuery{
|
||||
Path: method,
|
||||
Data: reqBz,
|
||||
}
|
||||
|
||||
res, err := ctx.QueryABCI(req)
|
||||
err = protoCodec.Unmarshal(abciRes.Value, reply)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = protoCodec.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(grpctypes.GRPCBlockHeightHeader, strconv.FormatInt(res.Height, 10))
|
||||
for _, callOpt := range opts {
|
||||
header, ok := callOpt.(grpc.HeaderCallOption)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
*header.HeaderAddr = md
|
||||
*header.HeaderAddr = outMd
|
||||
}
|
||||
|
||||
if ctx.InterfaceRegistry != nil {
|
||||
@@ -118,6 +86,47 @@ func (Context) NewStream(gocontext.Context, *grpc.StreamDesc, string, ...grpc.Ca
|
||||
return nil, fmt.Errorf("streaming rpc not supported")
|
||||
}
|
||||
|
||||
func isBroadcast(method string) bool {
|
||||
return method == "/cosmos.tx.v1beta1.Service/BroadcastTx"
|
||||
// RunGRPCQuery runs a gRPC query from the clientCtx, given all necessary
|
||||
// arguments for the gRPC method, and returns the ABCI response. It is used
|
||||
// to factorize code between client (Invoke) and server (RegisterGRPCServer)
|
||||
// gRPC handlers.
|
||||
func RunGRPCQuery(ctx Context, grpcCtx gocontext.Context, method string, req interface{}, md metadata.MD) (abci.ResponseQuery, metadata.MD, error) {
|
||||
reqBz, err := protoCodec.Marshal(req)
|
||||
if err != nil {
|
||||
return abci.ResponseQuery{}, nil, err
|
||||
}
|
||||
|
||||
// parse height header
|
||||
if heights := md.Get(grpctypes.GRPCBlockHeightHeader); len(heights) > 0 {
|
||||
height, err := strconv.ParseInt(heights[0], 10, 64)
|
||||
if err != nil {
|
||||
return abci.ResponseQuery{}, nil, err
|
||||
}
|
||||
if height < 0 {
|
||||
return abci.ResponseQuery{}, nil, sdkerrors.Wrapf(
|
||||
sdkerrors.ErrInvalidRequest,
|
||||
"client.Context.Invoke: height (%d) from %q must be >= 0", height, grpctypes.GRPCBlockHeightHeader)
|
||||
}
|
||||
|
||||
ctx = ctx.WithHeight(height)
|
||||
}
|
||||
|
||||
abciReq := abci.RequestQuery{
|
||||
Path: method,
|
||||
Data: reqBz,
|
||||
}
|
||||
|
||||
abciRes, err := ctx.QueryABCI(abciReq)
|
||||
if err != nil {
|
||||
return abci.ResponseQuery{}, nil, 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(grpctypes.GRPCBlockHeightHeader, strconv.FormatInt(abciRes.Height, 10))
|
||||
|
||||
return abciRes, md, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user