Skip to content

Commit

Permalink
gRPC: fix server reflection (#8945)
Browse files Browse the repository at this point in the history
* add: gogoreflection

* fix: server reflection

* fix: make tests resolve imports in a transitive way

* chore: cleanup getMessageType

* chore: lint

* add: extensions reflection

* chore: update interact-node.md docs

* chore: update CHANGELOG.md

* Update server/grpc/gogoreflection/fix_registration.go

Co-authored-by: Robert Zaremba <[email protected]>

Co-authored-by: Jonathan Gimeno <[email protected]>
Co-authored-by: Alessio Treglia <[email protected]>
Co-authored-by: Robert Zaremba <[email protected]>
  • Loading branch information
4 people authored Mar 22, 2021
1 parent e9e978d commit ac48ffe
Show file tree
Hide file tree
Showing 10 changed files with 688 additions and 31 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Bug Fixes

* (gRPC) [\#8945](https://github.com/cosmos/cosmos-sdk/pull/8945) gRPC reflection now works correctly.
* (keyring) [#\8635](https://github.com/cosmos/cosmos-sdk/issues/8635) Remove hardcoded default passphrase value on `NewMnemonic`
* (x/bank) [\#8434](https://github.com/cosmos/cosmos-sdk/pull/8434) Fix legacy REST API `GET /bank/total` and `GET /bank/total/{denom}` in swagger
* (x/slashing) [\#8427](https://github.com/cosmos/cosmos-sdk/pull/8427) Fix query signing infos command
Expand Down
19 changes: 4 additions & 15 deletions docs/run-node/interact-node.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Since the code generation library largely depends on your own tech stack, we wil

### grpcurl

[grpcurl])https://github.com/fullstorydev/grpcurl is like `curl` but for gRPC. It is also available as a Go library, but we will use it only as a CLI command for debugging and testing purposes. Follow the instructions in the previous link to install it.
[grpcurl](https://github.com/fullstorydev/grpcurl) is like `curl` but for gRPC. It is also available as a Go library, but we will use it only as a CLI command for debugging and testing purposes. Follow the instructions in the previous link to install it.

Assuming you have a local node running (either a localnet, or connected a live network), you should be able to run the following command to list the Protobuf services available (you can replace `localhost:9000` by the gRPC server endpoint of another node, which is configured under the `grpc.address` field inside [`app.toml`](../run-node/run-node.md#configuring-the-node-using-apptoml)):

Expand All @@ -70,27 +70,19 @@ grpcurl -plaintext localhost:9090 list

You should see a list of gRPC services, like `cosmos.bank.v1beta1.Query`. This is called reflection, which is a Protobuf endpoint returning a description of all available endpoints. Each of these represents a different Protobuf service, and each service exposes multiple RPC methods you can query against.

In the Cosmos SDK, we use [gogoprotobuf](https://github.com/gogo/protobuf) for code generation, and [grpc-go](https://github.com/grpc/grpc-go) for creating the gRPC server. Unfortunately, these two don't play well together, and more in-depth reflection (such as using grpcurl's `describe`) is not possible. See [this issue](https://github.com/grpc/grpc-go/issues/1873) for more info.

Instead, we need to manually pass the reference to relevant `.proto` files. For example:
In order to get a description of the service you can run the following command:

```bash
grpcurl \
-import-path ./proto \ # Import these proto files too
-import-path ./third_party/proto \ # Import these proto files too
-proto ./proto/cosmos/bank/v1beta1/query.proto \ # That's the proto file with the description of your service
localhost:9090 \
describe cosmos.bank.v1beta1.Query # Service we want to inspect
```

Once the Protobuf definitions are given, making a gRPC query is then straightforward, by calling the correct `Query` service RPC method, and by passing the request argument as data (`-d` flag):
It's also possible to execute an RPC call to query the node for information:

```bash
grpcurl \
-plaintext
-import-path ./proto \
-import-path ./third_party/proto \
-proto ./proto/cosmos/bank/v1beta1/query.proto \
-d '{"address":"$MY_VALIDATOR"}' \
localhost:9090 \
cosmos.bank.v1beta1.Query/AllBalances
Expand All @@ -104,10 +96,7 @@ You may also query for historical data by passing some [gRPC metadata](https://g

```bash
grpcurl \
-plaintext
-import-path ./proto \
-import-path ./third_party/proto \
-proto ./proto/cosmos/bank/v1beta1/query.proto \
-plaintext \
-H "x-cosmos-block-height: 279256" \
-d '{"address":"$MY_VALIDATOR"}' \
localhost:9090 \
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ require (
github.com/hashicorp/golang-lru v0.5.4
github.com/hdevalence/ed25519consensus v0.0.0-20210204194344-59a8610d2b87
github.com/improbable-eng/grpc-web v0.14.0
github.com/jhump/protoreflect v1.8.2
github.com/magiconair/properties v1.8.4
github.com/mattn/go-isatty v0.0.12
github.com/otiai10/copy v1.5.0
Expand All @@ -53,7 +54,7 @@ require (
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
google.golang.org/genproto v0.0.0-20210114201628-6edceaf6022f
google.golang.org/grpc v1.36.0
google.golang.org/protobuf v1.25.0
google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12
gopkg.in/ini.v1 v1.61.0 // indirect
gopkg.in/yaml.v2 v2.4.0
nhooyr.io/websocket v1.8.6 // indirect
Expand Down
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
Expand Down Expand Up @@ -369,6 +370,8 @@ github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod
github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jhump/protoreflect v1.8.2 h1:k2xE7wcUomeqwY0LDCYA16y4WWfyTcMx5mKhk0d4ua0=
github.com/jhump/protoreflect v1.8.2/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U=
github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
Expand Down Expand Up @@ -473,6 +476,7 @@ github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
Expand Down Expand Up @@ -699,6 +703,7 @@ github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+m
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zondax/hid v0.9.0 h1:eiT3P6vNxAEVxXMw66eZUAAnU2zD33JBkfG/EnfAKl8=
github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM=
Expand Down Expand Up @@ -888,10 +893,13 @@ golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down Expand Up @@ -941,6 +949,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12 h1:OwhZOOMuf7leLaSCuxtQ9FW7ui2L2L6UKOtKAUqovUQ=
google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down Expand Up @@ -981,6 +991,7 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k=
nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
Expand Down
5 changes: 5 additions & 0 deletions server/grpc/gogoreflection/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Package gogoreflection implements gRPC reflection for gogoproto consumers
// the normal reflection library does not work as it points to a different
// singleton registry. The API and codebase is taken from the official gRPC
// reflection repository.
package gogoreflection
143 changes: 143 additions & 0 deletions server/grpc/gogoreflection/fix_registration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package gogoreflection

import (
"bytes"
"compress/gzip"
"fmt"
"reflect"

_ "github.com/gogo/protobuf/gogoproto" // required so it does register the gogoproto file descriptor
gogoproto "github.com/gogo/protobuf/proto"

// nolint: staticcheck
"github.com/golang/protobuf/proto"
dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
_ "github.com/regen-network/cosmos-proto" // look above
)

var importsToFix = map[string]string{
"gogo.proto": "gogoproto/gogo.proto",
"cosmos.proto": "cosmos_proto/cosmos.proto",
}

// fixRegistration is required because certain files register themselves in a way
// but are imported by other files in a different way.
// NOTE(fdymylja): This fix should not be needed and should be addressed in some CI.
// Currently every cosmos-sdk proto file is importing gogo.proto as gogoproto/gogo.proto,
// but gogo.proto registers itself as gogo.proto, same goes for cosmos.proto.
func fixRegistration(registeredAs, importedAs string) error {
raw := gogoproto.FileDescriptor(registeredAs)
if len(raw) == 0 {
return fmt.Errorf("file descriptor not found for %s", registeredAs)
}

fd, err := decodeFileDesc(raw)
if err != nil {
return err
}

// fix name
*fd.Name = importedAs
fixedRaw, err := compress(fd)
if err != nil {
return fmt.Errorf("unable to compress: %w", err)
}
gogoproto.RegisterFile(importedAs, fixedRaw)
return nil
}

func init() {
// we need to fix the gogoproto filedesc to match the import path
// in theory this shouldn't be required, generally speaking
// proto files should be imported as their registration path

for registeredAs, importedAs := range importsToFix {
err := fixRegistration(registeredAs, importedAs)
if err != nil {
panic(err)
}
}
}

// compress compresses the given file descriptor
// nolint: interfacer
func compress(fd *dpb.FileDescriptorProto) ([]byte, error) {
fdBytes, err := proto.Marshal(fd)
if err != nil {
return nil, err
}
buf := new(bytes.Buffer)
cw := gzip.NewWriter(buf)
_, err = cw.Write(fdBytes)
if err != nil {
return nil, err
}
err = cw.Close()
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}

func getFileDescriptor(filePath string) []byte {
// since we got well known descriptors which are not registered into gogoproto registry
// but are instead registered into the proto one, we need to check both
fd := gogoproto.FileDescriptor(filePath)
if len(fd) != 0 {
return fd
}
// nolint: staticcheck
return proto.FileDescriptor(filePath)
}

func getMessageType(name string) reflect.Type {
typ := gogoproto.MessageType(name)
if typ != nil {
return typ
}
// nolint: staticcheck
return proto.MessageType(name)
}

func getExtension(extID int32, m proto.Message) *gogoproto.ExtensionDesc {
// check first in gogoproto registry
for id, desc := range gogoproto.RegisteredExtensions(m) {
if id == extID {
return desc
}
}
// check into proto registry
// nolint: staticcheck
for id, desc := range proto.RegisteredExtensions(m) {
if id == extID {
return &gogoproto.ExtensionDesc{
ExtendedType: desc.ExtendedType,
ExtensionType: desc.ExtensionType,
Field: desc.Field,
Name: desc.Name,
Tag: desc.Tag,
Filename: desc.Filename,
}
}
}

return nil
}

func getExtensionsNumbers(m proto.Message) []int32 {
gogoProtoExts := gogoproto.RegisteredExtensions(m)
out := make([]int32, 0, len(gogoProtoExts))
for id := range gogoProtoExts {
out = append(out, id)
}
if len(out) != 0 {
return out
}
// nolint: staticcheck
protoExts := proto.RegisteredExtensions(m)
out = make([]int32, 0, len(protoExts))
for id := range protoExts {
out = append(out, id)
}
return out
}
22 changes: 22 additions & 0 deletions server/grpc/gogoreflection/fix_registration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package gogoreflection

import (
"testing"

"google.golang.org/protobuf/runtime/protoimpl"
)

func TestRegistrationFix(t *testing.T) {
res := getFileDescriptor("gogoproto/gogo.proto")
rawDesc, err := decompress(res)
if err != nil {
t.Fatal(err)
}
fd := protoimpl.DescBuilder{
RawDescriptor: rawDesc,
}.Build()

if fd.File.Extensions().Len() == 0 {
t.Fatal("unexpected parsing")
}
}
Loading

0 comments on commit ac48ffe

Please sign in to comment.