github.com/Finschia/finschia-sdk@v0.48.1/client/grpc_query.go (about) 1 package client 2 3 import ( 4 gocontext "context" 5 "fmt" 6 "reflect" 7 "strconv" 8 9 gogogrpc "github.com/gogo/protobuf/grpc" 10 abci "github.com/tendermint/tendermint/abci/types" 11 "google.golang.org/grpc" 12 "google.golang.org/grpc/encoding" 13 "google.golang.org/grpc/encoding/proto" 14 "google.golang.org/grpc/metadata" 15 16 "github.com/Finschia/finschia-sdk/codec/types" 17 sdkerrors "github.com/Finschia/finschia-sdk/types/errors" 18 grpctypes "github.com/Finschia/finschia-sdk/types/grpc" 19 "github.com/Finschia/finschia-sdk/types/tx" 20 ) 21 22 var _ gogogrpc.ClientConn = Context{} 23 24 var protoCodec = encoding.GetCodec(proto.Name) 25 26 // Invoke implements the grpc ClientConn.Invoke method 27 func (ctx Context) Invoke(grpcCtx gocontext.Context, method string, req, reply interface{}, opts ...grpc.CallOption) (err error) { 28 // Two things can happen here: 29 // 1. either we're broadcasting a Tx, in which call we call Tendermint's broadcast endpoint directly, 30 // 2. or we are querying for state, in which case we call ABCI's Query. 31 32 // In both cases, we don't allow empty request args (it will panic unexpectedly). 33 if reflect.ValueOf(req).IsNil() { 34 return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "request cannot be nil") 35 } 36 37 // Case 1. Broadcasting a Tx. 38 if reqProto, ok := req.(*tx.BroadcastTxRequest); ok { 39 res, ok := reply.(*tx.BroadcastTxResponse) 40 if !ok { 41 return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "expected %T, got %T", (*tx.BroadcastTxResponse)(nil), req) 42 } 43 44 broadcastRes, err := TxServiceBroadcast(grpcCtx, ctx, reqProto) 45 if err != nil { 46 return err 47 } 48 *res = *broadcastRes 49 50 return err 51 } 52 53 // Case 2. Querying state. 54 reqBz, err := protoCodec.Marshal(req) 55 if err != nil { 56 return err 57 } 58 59 // parse height header 60 md, _ := metadata.FromOutgoingContext(grpcCtx) 61 if heights := md.Get(grpctypes.GRPCBlockHeightHeader); len(heights) > 0 { 62 height, err := strconv.ParseInt(heights[0], 10, 64) 63 if err != nil { 64 return err 65 } 66 if height < 0 { 67 return sdkerrors.Wrapf( 68 sdkerrors.ErrInvalidRequest, 69 "client.Context.Invoke: height (%d) from %q must be >= 0", height, grpctypes.GRPCBlockHeightHeader) 70 } 71 72 ctx = ctx.WithHeight(height) 73 } 74 75 abciReq := abci.RequestQuery{ 76 Path: method, 77 Data: reqBz, 78 Height: ctx.Height, 79 } 80 81 res, err := ctx.QueryABCI(abciReq) 82 if err != nil { 83 return err 84 } 85 86 err = protoCodec.Unmarshal(res.Value, reply) 87 if err != nil { 88 return err 89 } 90 91 // Create header metadata. For now the headers contain: 92 // - block height 93 // We then parse all the call options, if the call option is a 94 // HeaderCallOption, then we manually set the value of that header to the 95 // metadata. 96 md = metadata.Pairs(grpctypes.GRPCBlockHeightHeader, strconv.FormatInt(res.Height, 10)) 97 for _, callOpt := range opts { 98 header, ok := callOpt.(grpc.HeaderCallOption) 99 if !ok { 100 continue 101 } 102 103 *header.HeaderAddr = md 104 } 105 106 if ctx.InterfaceRegistry != nil { 107 return types.UnpackInterfaces(reply, ctx.InterfaceRegistry) 108 } 109 110 return nil 111 } 112 113 // NewStream implements the grpc ClientConn.NewStream method 114 func (Context) NewStream(gocontext.Context, *grpc.StreamDesc, string, ...grpc.CallOption) (grpc.ClientStream, error) { 115 return nil, fmt.Errorf("streaming rpc not supported") 116 }