feat: wire new handlers to grpc (#22333)

Co-authored-by: Randy Grok <@faulttolerance.net>
Co-authored-by: Julien Robert <julien@rbrt.fr>
This commit is contained in:
Randy Grok 2024-11-05 16:31:22 +01:00 committed by GitHub
parent 8c24b6bef1
commit 62ddd3e939
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 4276 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,167 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc (unknown)
// source: cosmos/base/grpc/v2/service.proto
package grpcv2
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
Service_Query_FullMethodName = "/cosmos.base.grpc.v2.Service/Query"
Service_ListQueryHandlers_FullMethodName = "/cosmos.base.grpc.v2.Service/ListQueryHandlers"
)
// ServiceClient is the client API for Service service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
//
// Service defines the gRPC service for query server for v2
type ServiceClient interface {
// Query queries the server with a request, the request can be any sdk Msg.
Query(ctx context.Context, in *QueryRequest, opts ...grpc.CallOption) (*QueryResponse, error)
// ListQueryHandlers lists all the available query handlers.
ListQueryHandlers(ctx context.Context, in *ListQueryHandlersRequest, opts ...grpc.CallOption) (*ListQueryHandlersResponse, error)
}
type serviceClient struct {
cc grpc.ClientConnInterface
}
func NewServiceClient(cc grpc.ClientConnInterface) ServiceClient {
return &serviceClient{cc}
}
func (c *serviceClient) Query(ctx context.Context, in *QueryRequest, opts ...grpc.CallOption) (*QueryResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(QueryResponse)
err := c.cc.Invoke(ctx, Service_Query_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *serviceClient) ListQueryHandlers(ctx context.Context, in *ListQueryHandlersRequest, opts ...grpc.CallOption) (*ListQueryHandlersResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListQueryHandlersResponse)
err := c.cc.Invoke(ctx, Service_ListQueryHandlers_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// ServiceServer is the server API for Service service.
// All implementations must embed UnimplementedServiceServer
// for forward compatibility.
//
// Service defines the gRPC service for query server for v2
type ServiceServer interface {
// Query queries the server with a request, the request can be any sdk Msg.
Query(context.Context, *QueryRequest) (*QueryResponse, error)
// ListQueryHandlers lists all the available query handlers.
ListQueryHandlers(context.Context, *ListQueryHandlersRequest) (*ListQueryHandlersResponse, error)
mustEmbedUnimplementedServiceServer()
}
// UnimplementedServiceServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedServiceServer struct{}
func (UnimplementedServiceServer) Query(context.Context, *QueryRequest) (*QueryResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Query not implemented")
}
func (UnimplementedServiceServer) ListQueryHandlers(context.Context, *ListQueryHandlersRequest) (*ListQueryHandlersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListQueryHandlers not implemented")
}
func (UnimplementedServiceServer) mustEmbedUnimplementedServiceServer() {}
func (UnimplementedServiceServer) testEmbeddedByValue() {}
// UnsafeServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to ServiceServer will
// result in compilation errors.
type UnsafeServiceServer interface {
mustEmbedUnimplementedServiceServer()
}
func RegisterServiceServer(s grpc.ServiceRegistrar, srv ServiceServer) {
// If the following call pancis, it indicates UnimplementedServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&Service_ServiceDesc, srv)
}
func _Service_Query_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(QueryRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ServiceServer).Query(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Service_Query_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ServiceServer).Query(ctx, req.(*QueryRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Service_ListQueryHandlers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListQueryHandlersRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ServiceServer).ListQueryHandlers(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Service_ListQueryHandlers_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ServiceServer).ListQueryHandlers(ctx, req.(*ListQueryHandlersRequest))
}
return interceptor(ctx, in, info, handler)
}
// Service_ServiceDesc is the grpc.ServiceDesc for Service service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Service_ServiceDesc = grpc.ServiceDesc{
ServiceName: "cosmos.base.grpc.v2.Service",
HandlerType: (*ServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Query",
Handler: _Service_Query_Handler,
},
{
MethodName: "ListQueryHandlers",
Handler: _Service_ListQueryHandlers_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "cosmos/base/grpc/v2/service.proto",
}

View File

@ -0,0 +1,39 @@
syntax = "proto3";
package cosmos.base.grpc.v2;
import "google/protobuf/any.proto";
option go_package = "cosmossdk.io/server/v2/api/grpc";
// Service defines the gRPC service for query server for v2
service Service {
// Query queries the server with a request, the request can be any sdk Msg.
rpc Query(QueryRequest) returns (QueryResponse) {}
// ListQueryHandlers lists all the available query handlers.
rpc ListQueryHandlers(ListQueryHandlersRequest) returns (ListQueryHandlersResponse) {}
}
// QueryRequest is the request for the Query method
message QueryRequest {
google.protobuf.Any request = 1;
}
// QueryResponse is the response for the Query method
message QueryResponse {
google.protobuf.Any response = 1;
}
// ListQueryHandlersRequest is the request for the ListQueryHandlers method
message ListQueryHandlersRequest {}
// ListQueryHandlersResponse is the response for the ListQueryHandlers method
message ListQueryHandlersResponse {
repeated Handler handlers = 1;
}
// Handler defines a query handler
message Handler {
string request_name = 1;
string response_name = 2;
}

View File

@ -74,6 +74,9 @@ func New[T transaction.Tx](
// Reflection allows external clients to see what services and methods the gRPC server exposes.
gogoreflection.Register(grpcSrv, slices.Collect(maps.Keys(queryHandlers)), logger.With("sub-module", "grpc-reflection"))
// Register V2
RegisterServiceServer(grpcSrv, &v2Service{queryHandlers, queryable})
srv.grpcSrv = grpcSrv
srv.config = serverCfg
srv.logger = logger.With(log.ModuleKey, srv.Name())

View File

@ -0,0 +1,73 @@
package grpc
import (
"context"
"github.com/cosmos/gogoproto/proto"
gogoproto "github.com/cosmos/gogoproto/types/any"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
appmodulev2 "cosmossdk.io/core/appmodule/v2"
"cosmossdk.io/core/transaction"
)
// v2Service implements the gRPC service interface for handling queries and listing handlers.
type v2Service struct {
queryHandlers map[string]appmodulev2.Handler
queryable interface {
Query(ctx context.Context, version uint64, msg transaction.Msg) (transaction.Msg, error)
}
}
// Query handles incoming query requests by unmarshaling the request, processing it,
// and returning the response in an Any protobuf message.
func (s v2Service) Query(ctx context.Context, request *QueryRequest) (*QueryResponse, error) {
if request == nil || request.Request == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}
msgName := request.Request.TypeUrl
handler, exists := s.queryHandlers[msgName]
if !exists {
return nil, status.Errorf(codes.NotFound, "handler not found for %s", msgName)
}
protoMsg := handler.MakeMsg()
if err := proto.Unmarshal(request.Request.Value, protoMsg); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "failed to unmarshal request: %v", err)
}
queryResp, err := s.queryable.Query(ctx, 0, protoMsg)
if err != nil {
return nil, status.Errorf(codes.Internal, "query failed: %v", err)
}
respBytes, err := proto.Marshal(queryResp)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to marshal response: %v", err)
}
anyResp := &gogoproto.Any{
TypeUrl: "/" + proto.MessageName(queryResp),
Value: respBytes,
}
return &QueryResponse{Response: anyResp}, nil
}
func (s v2Service) ListQueryHandlers(_ context.Context, _ *ListQueryHandlersRequest) (*ListQueryHandlersResponse, error) {
var handlerDescriptors []*Handler
for handlerName := range s.queryHandlers {
msg := s.queryHandlers[handlerName].MakeMsg()
resp := s.queryHandlers[handlerName].MakeMsgResp()
handlerDescriptors = append(handlerDescriptors, &Handler{
RequestName: proto.MessageName(msg),
ResponseName: proto.MessageName(resp),
})
}
return &ListQueryHandlersResponse{Handlers: handlerDescriptors}, nil
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,227 @@
package grpc
import (
"context"
"fmt"
"testing"
"github.com/cosmos/gogoproto/proto"
gogoproto "github.com/cosmos/gogoproto/types/any"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
appmodulev2 "cosmossdk.io/core/appmodule/v2"
"cosmossdk.io/core/transaction"
serverv2 "cosmossdk.io/server/v2"
)
type MockRequestMessage struct {
Data string `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
}
func (m *MockRequestMessage) XXX_MessageName() string {
return "MockRequestMessage"
}
func (m *MockRequestMessage) Reset() {}
func (m *MockRequestMessage) String() string { return "" }
func (m *MockRequestMessage) ProtoMessage() {}
func (m *MockRequestMessage) ValidateBasic() error {
return nil
}
type MockResponseMessage struct {
Data string `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
}
func (m *MockResponseMessage) Reset() {}
func (m *MockResponseMessage) String() string { return "" }
func (m *MockResponseMessage) ProtoMessage() {}
func (m *MockResponseMessage) ValidateBasic() error {
return nil
}
type mockApp[T transaction.Tx] struct {
mock.Mock
serverv2.AppI[T]
}
func (m *mockApp[T]) QueryHandlers() map[string]appmodulev2.Handler {
args := m.Called()
return args.Get(0).(map[string]appmodulev2.Handler)
}
func (m *mockApp[T]) Query(ctx context.Context, height uint64, msg transaction.Msg) (transaction.Msg, error) {
args := m.Called(ctx, height, msg)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(transaction.Msg), args.Error(1)
}
func TestQuery(t *testing.T) {
tests := []struct {
name string
setupMock func(app *mockApp[transaction.Tx])
request *QueryRequest
expectError bool
expectedError string
}{
{
name: "successful query",
setupMock: func(app *mockApp[transaction.Tx]) {
reqMsg := &MockRequestMessage{Data: "request"}
respMsg := &MockResponseMessage{Data: "response"}
handlers := map[string]appmodulev2.Handler{
"/" + proto.MessageName(&MockRequestMessage{}): {
Func: func(ctx context.Context, msg transaction.Msg) (transaction.Msg, error) {
return respMsg, nil
},
MakeMsg: func() transaction.Msg {
return reqMsg
},
MakeMsgResp: func() transaction.Msg {
return respMsg
},
},
}
app.On("QueryHandlers").Return(handlers)
app.On("Query", mock.Anything, uint64(0), reqMsg).Return(respMsg, nil)
},
request: createTestRequest(t),
expectError: false,
},
{
name: "handler not found",
setupMock: func(app *mockApp[transaction.Tx]) {
handlers := map[string]appmodulev2.Handler{}
app.On("QueryHandlers").Return(handlers)
},
request: createTestRequest(t),
expectError: true,
expectedError: "rpc error: code = NotFound desc = handler not found for /MockRequestMessage",
},
{
name: "query error",
setupMock: func(app *mockApp[transaction.Tx]) {
reqMsg := &MockRequestMessage{Data: "request"}
respMsg := &MockRequestMessage{Data: "response"}
handlers := map[string]appmodulev2.Handler{
"/" + proto.MessageName(&MockRequestMessage{}): {
Func: func(ctx context.Context, msg transaction.Msg) (transaction.Msg, error) {
return respMsg, nil
},
MakeMsg: func() transaction.Msg {
return reqMsg
},
MakeMsgResp: func() transaction.Msg {
return respMsg
},
},
}
app.On("QueryHandlers").Return(handlers)
app.On("Query", mock.Anything, uint64(0), reqMsg).Return(nil, assert.AnError)
},
request: createTestRequest(t),
expectError: true,
expectedError: fmt.Sprintf("rpc error: code = Internal desc = query failed: %s", assert.AnError.Error()),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockApp := &mockApp[transaction.Tx]{}
if tt.setupMock != nil {
tt.setupMock(mockApp)
}
service := &v2Service{mockApp.QueryHandlers(), mockApp}
resp, err := service.Query(context.Background(), tt.request)
if tt.expectError {
assert.Error(t, err)
if tt.expectedError != "" {
assert.Equal(t, tt.expectedError, err.Error())
}
} else {
assert.NoError(t, err)
assert.NotNil(t, resp)
assert.NotNil(t, resp.Response)
}
mockApp.AssertExpectations(t)
})
}
}
func TestV2Service_ListQueryHandlers(t *testing.T) {
tests := []struct {
name string
setupMock func(app *mockApp[transaction.Tx])
}{
{
name: "successful list query handlers",
setupMock: func(app *mockApp[transaction.Tx]) {
reqMsg := &MockRequestMessage{Data: "request"}
respMsg := &MockResponseMessage{Data: "response"}
handlers := map[string]appmodulev2.Handler{
"/test.Query": {
Func: func(ctx context.Context, msg transaction.Msg) (transaction.Msg, error) {
return respMsg, nil
},
MakeMsg: func() transaction.Msg {
return reqMsg
},
MakeMsgResp: func() transaction.Msg {
return respMsg
},
},
}
app.On("QueryHandlers").Return(handlers)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockApp := &mockApp[transaction.Tx]{}
if tt.setupMock != nil {
tt.setupMock(mockApp)
}
service := &v2Service{mockApp.QueryHandlers(), mockApp}
resp, err := service.ListQueryHandlers(context.Background(), &ListQueryHandlersRequest{})
assert.NoError(t, err)
assert.NotNil(t, resp)
assert.Len(t, resp.Handlers, 1)
resp.Handlers[0].RequestName = "/MockRequestMessage"
resp.Handlers[0].ResponseName = "/MockResponseMessage"
mockApp.AssertExpectations(t)
})
}
}
func createTestRequest(t *testing.T) *QueryRequest {
t.Helper()
reqMsg := &MockRequestMessage{Data: "request"}
reqBytes, err := proto.Marshal(reqMsg)
if err != nil {
t.Fatalf("failed to marshal request: %v", err)
}
return &QueryRequest{
Request: &gogoproto.Any{
TypeUrl: "/" + proto.MessageName(reqMsg),
Value: reqBytes,
},
}
}

View File

@ -92,8 +92,10 @@ require (
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/tendermint/go-amino v0.16.0 // indirect
github.com/tidwall/btree v1.7.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.28.0 // indirect

View File

@ -119,6 +119,7 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
@ -147,7 +148,10 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
@ -320,6 +324,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs=
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E=
github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME=
github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI=
github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
@ -371,6 +377,7 @@ golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4Iltr
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -440,6 +447,7 @@ golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNq
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=