Skip to content

Commit ac48ffe

Browse files
fdymyljajgimenoAlessio Tregliarobert-zaremba
authored
gRPC: fix server reflection (#8945)
* 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 <robert@zaremba.ch> Co-authored-by: Jonathan Gimeno <jgimeno@gmail.com> Co-authored-by: Alessio Treglia <alessio@tendermint.com> Co-authored-by: Robert Zaremba <robert@zaremba.ch>
1 parent e9e978d commit ac48ffe

File tree

10 files changed

+688
-31
lines changed

10 files changed

+688
-31
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
8686

8787
### Bug Fixes
8888

89+
* (gRPC) [\#8945](https://github.com/cosmos/cosmos-sdk/pull/8945) gRPC reflection now works correctly.
8990
* (keyring) [#\8635](https://github.com/cosmos/cosmos-sdk/issues/8635) Remove hardcoded default passphrase value on `NewMnemonic`
9091
* (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
9192
* (x/slashing) [\#8427](https://github.com/cosmos/cosmos-sdk/pull/8427) Fix query signing infos command

docs/run-node/interact-node.md

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ Since the code generation library largely depends on your own tech stack, we wil
6060

6161
### grpcurl
6262

63-
[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.
63+
[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.
6464

6565
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)):
6666

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

7171
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.
7272

73-
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.
74-
75-
Instead, we need to manually pass the reference to relevant `.proto` files. For example:
73+
In order to get a description of the service you can run the following command:
7674

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

86-
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):
81+
It's also possible to execute an RPC call to query the node for information:
8782

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

10597
```bash
10698
grpcurl \
107-
-plaintext
108-
-import-path ./proto \
109-
-import-path ./third_party/proto \
110-
-proto ./proto/cosmos/bank/v1beta1/query.proto \
99+
-plaintext \
111100
-H "x-cosmos-block-height: 279256" \
112101
-d '{"address":"$MY_VALIDATOR"}' \
113102
localhost:9090 \

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ require (
2727
github.com/hashicorp/golang-lru v0.5.4
2828
github.com/hdevalence/ed25519consensus v0.0.0-20210204194344-59a8610d2b87
2929
github.com/improbable-eng/grpc-web v0.14.0
30+
github.com/jhump/protoreflect v1.8.2
3031
github.com/magiconair/properties v1.8.4
3132
github.com/mattn/go-isatty v0.0.12
3233
github.com/otiai10/copy v1.5.0
@@ -53,7 +54,7 @@ require (
5354
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
5455
google.golang.org/genproto v0.0.0-20210114201628-6edceaf6022f
5556
google.golang.org/grpc v1.36.0
56-
google.golang.org/protobuf v1.25.0
57+
google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12
5758
gopkg.in/ini.v1 v1.61.0 // indirect
5859
gopkg.in/yaml.v2 v2.4.0
5960
nhooyr.io/websocket v1.8.6 // indirect

go.sum

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
290290
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
291291
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
292292
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
293+
github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
293294
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
294295
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
295296
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
@@ -369,6 +370,8 @@ github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod
369370
github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
370371
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
371372
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
373+
github.com/jhump/protoreflect v1.8.2 h1:k2xE7wcUomeqwY0LDCYA16y4WWfyTcMx5mKhk0d4ua0=
374+
github.com/jhump/protoreflect v1.8.2/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg=
372375
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
373376
github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U=
374377
github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
@@ -473,6 +476,7 @@ github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi
473476
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
474477
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
475478
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
479+
github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso=
476480
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
477481
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
478482
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
@@ -699,6 +703,7 @@ github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+m
699703
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
700704
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
701705
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
706+
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
702707
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
703708
github.com/zondax/hid v0.9.0 h1:eiT3P6vNxAEVxXMw66eZUAAnU2zD33JBkfG/EnfAKl8=
704709
github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM=
@@ -888,10 +893,13 @@ golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtn
888893
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
889894
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
890895
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
896+
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
891897
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
892898
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
893899
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
900+
golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
894901
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
902+
golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
895903
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
896904
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
897905
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -941,6 +949,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
941949
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
942950
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
943951
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
952+
google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12 h1:OwhZOOMuf7leLaSCuxtQ9FW7ui2L2L6UKOtKAUqovUQ=
953+
google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
944954
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
945955
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
946956
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -981,6 +991,7 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
981991
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
982992
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
983993
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
994+
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
984995
nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k=
985996
nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
986997
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

server/grpc/gogoreflection/doc.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Package gogoreflection implements gRPC reflection for gogoproto consumers
2+
// the normal reflection library does not work as it points to a different
3+
// singleton registry. The API and codebase is taken from the official gRPC
4+
// reflection repository.
5+
package gogoreflection
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package gogoreflection
2+
3+
import (
4+
"bytes"
5+
"compress/gzip"
6+
"fmt"
7+
"reflect"
8+
9+
_ "github.com/gogo/protobuf/gogoproto" // required so it does register the gogoproto file descriptor
10+
gogoproto "github.com/gogo/protobuf/proto"
11+
12+
// nolint: staticcheck
13+
"github.com/golang/protobuf/proto"
14+
dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
15+
_ "github.com/regen-network/cosmos-proto" // look above
16+
)
17+
18+
var importsToFix = map[string]string{
19+
"gogo.proto": "gogoproto/gogo.proto",
20+
"cosmos.proto": "cosmos_proto/cosmos.proto",
21+
}
22+
23+
// fixRegistration is required because certain files register themselves in a way
24+
// but are imported by other files in a different way.
25+
// NOTE(fdymylja): This fix should not be needed and should be addressed in some CI.
26+
// Currently every cosmos-sdk proto file is importing gogo.proto as gogoproto/gogo.proto,
27+
// but gogo.proto registers itself as gogo.proto, same goes for cosmos.proto.
28+
func fixRegistration(registeredAs, importedAs string) error {
29+
raw := gogoproto.FileDescriptor(registeredAs)
30+
if len(raw) == 0 {
31+
return fmt.Errorf("file descriptor not found for %s", registeredAs)
32+
}
33+
34+
fd, err := decodeFileDesc(raw)
35+
if err != nil {
36+
return err
37+
}
38+
39+
// fix name
40+
*fd.Name = importedAs
41+
fixedRaw, err := compress(fd)
42+
if err != nil {
43+
return fmt.Errorf("unable to compress: %w", err)
44+
}
45+
gogoproto.RegisterFile(importedAs, fixedRaw)
46+
return nil
47+
}
48+
49+
func init() {
50+
// we need to fix the gogoproto filedesc to match the import path
51+
// in theory this shouldn't be required, generally speaking
52+
// proto files should be imported as their registration path
53+
54+
for registeredAs, importedAs := range importsToFix {
55+
err := fixRegistration(registeredAs, importedAs)
56+
if err != nil {
57+
panic(err)
58+
}
59+
}
60+
}
61+
62+
// compress compresses the given file descriptor
63+
// nolint: interfacer
64+
func compress(fd *dpb.FileDescriptorProto) ([]byte, error) {
65+
fdBytes, err := proto.Marshal(fd)
66+
if err != nil {
67+
return nil, err
68+
}
69+
buf := new(bytes.Buffer)
70+
cw := gzip.NewWriter(buf)
71+
_, err = cw.Write(fdBytes)
72+
if err != nil {
73+
return nil, err
74+
}
75+
err = cw.Close()
76+
if err != nil {
77+
return nil, err
78+
}
79+
return buf.Bytes(), nil
80+
}
81+
82+
func getFileDescriptor(filePath string) []byte {
83+
// since we got well known descriptors which are not registered into gogoproto registry
84+
// but are instead registered into the proto one, we need to check both
85+
fd := gogoproto.FileDescriptor(filePath)
86+
if len(fd) != 0 {
87+
return fd
88+
}
89+
// nolint: staticcheck
90+
return proto.FileDescriptor(filePath)
91+
}
92+
93+
func getMessageType(name string) reflect.Type {
94+
typ := gogoproto.MessageType(name)
95+
if typ != nil {
96+
return typ
97+
}
98+
// nolint: staticcheck
99+
return proto.MessageType(name)
100+
}
101+
102+
func getExtension(extID int32, m proto.Message) *gogoproto.ExtensionDesc {
103+
// check first in gogoproto registry
104+
for id, desc := range gogoproto.RegisteredExtensions(m) {
105+
if id == extID {
106+
return desc
107+
}
108+
}
109+
// check into proto registry
110+
// nolint: staticcheck
111+
for id, desc := range proto.RegisteredExtensions(m) {
112+
if id == extID {
113+
return &gogoproto.ExtensionDesc{
114+
ExtendedType: desc.ExtendedType,
115+
ExtensionType: desc.ExtensionType,
116+
Field: desc.Field,
117+
Name: desc.Name,
118+
Tag: desc.Tag,
119+
Filename: desc.Filename,
120+
}
121+
}
122+
}
123+
124+
return nil
125+
}
126+
127+
func getExtensionsNumbers(m proto.Message) []int32 {
128+
gogoProtoExts := gogoproto.RegisteredExtensions(m)
129+
out := make([]int32, 0, len(gogoProtoExts))
130+
for id := range gogoProtoExts {
131+
out = append(out, id)
132+
}
133+
if len(out) != 0 {
134+
return out
135+
}
136+
// nolint: staticcheck
137+
protoExts := proto.RegisteredExtensions(m)
138+
out = make([]int32, 0, len(protoExts))
139+
for id := range protoExts {
140+
out = append(out, id)
141+
}
142+
return out
143+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package gogoreflection
2+
3+
import (
4+
"testing"
5+
6+
"google.golang.org/protobuf/runtime/protoimpl"
7+
)
8+
9+
func TestRegistrationFix(t *testing.T) {
10+
res := getFileDescriptor("gogoproto/gogo.proto")
11+
rawDesc, err := decompress(res)
12+
if err != nil {
13+
t.Fatal(err)
14+
}
15+
fd := protoimpl.DescBuilder{
16+
RawDescriptor: rawDesc,
17+
}.Build()
18+
19+
if fd.File.Extensions().Len() == 0 {
20+
t.Fatal("unexpected parsing")
21+
}
22+
}

0 commit comments

Comments
 (0)