From f1398cb303f77354b20f22913ae367e17897e36c Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 12 Apr 2021 17:25:46 -0400 Subject: [PATCH 01/47] wip --- baseapp/baseapp.go | 18 +++++++++++---- baseapp/msg_service_router.go | 32 +++++++++++++++++++++++--- types/msgservice/service_msg_client.go | 11 ++++----- types/service_msg.go | 15 ++---------- types/tx_msg.go | 22 ++++++++++-------- 5 files changed, 61 insertions(+), 37 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 5ec243ba91b8..39cd0a44500e 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -709,23 +709,31 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s err error ) - if svcMsg, ok := msg.(sdk.ServiceMsg); ok { + msgType := proto.MessageName(msg) + + if handler := app.msgServiceRouter.Handler(msgType); handler != nil { + // ADR 031 request type routing + msgResult, err = handler(ctx, msg) + } else if svcMsg, ok := msg.(sdk.ServiceMsg); ok { + // deprecated ADR 031 method name routing msgFqName = svcMsg.MethodName handler := app.msgServiceRouter.Handler(msgFqName) if handler == nil { return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message service method: %s; message index: %d", msgFqName, i) } msgResult, err = handler(ctx, svcMsg.Request) - } else { + } else if legacyMsg, ok := msg.(sdk.LegacyMsg); ok { // legacy sdk.Msg routing - msgRoute := msg.Route() - msgFqName = msg.Type() + msgRoute := legacyMsg.Route() + msgFqName = legacyMsg.Type() handler := app.router.Route(ctx, msgRoute) if handler == nil { return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message route: %s; message index: %d", msgRoute, i) } msgResult, err = handler(ctx, msg) + } else { + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "can't route message %+v", msg) } if err != nil { @@ -743,7 +751,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s // separate each result. events = events.AppendEvents(msgEvents) - txMsgData.Data = append(txMsgData.Data, &sdk.MsgData{MsgType: msg.Type(), Data: msgResult.Data}) + txMsgData.Data = append(txMsgData.Data, &sdk.MsgData{MsgType: msgType, Data: msgResult.Data}) msgLogs = append(msgLogs, sdk.NewABCIMessageLog(uint32(i), msgResult.Log, msgEvents)) } diff --git a/baseapp/msg_service_router.go b/baseapp/msg_service_router.go index ea2ed4b4eb6a..153af46689f1 100644 --- a/baseapp/msg_service_router.go +++ b/baseapp/msg_service_router.go @@ -50,13 +50,35 @@ func (msr *MsgServiceRouter) RegisterService(sd *grpc.ServiceDesc, handler inter fqMethod := fmt.Sprintf("/%s/%s", sd.ServiceName, method.MethodName) methodHandler := method.Handler + var requestTypeName string + + // NOTE: This is how we pull the concrete request type for each handler for registering in the InterfaceRegistry. + // This approach is maybe a bit hacky, but less hacky than reflecting on the handler object itself. + // We use a no-op interceptor to avoid actually calling into the handler itself. + _, _ = methodHandler(nil, context.Background(), func(i interface{}) error { + msg, ok := i.(proto.Message) + if !ok { + // We panic here because there is no other alternative and the app cannot be initialized correctly + // this should only happen if there is a problem with code generation in which case the app won't + // work correctly anyway. + panic(fmt.Errorf("can't register request type %T for service method %s", i, fqMethod)) + } + + msgName := proto.MessageName(msg) + requestTypeName = fmt.Sprintf("/%s", msgName) + return nil + }, func(_ context.Context, _ interface{}, _ *grpc.UnaryServerInfo, _ grpc.UnaryHandler) (interface{}, error) { + return nil, nil + }, + ) + // Check that the service Msg fully-qualified method name has already // been registered (via RegisterInterfaces). If the user registers a // service without registering according service Msg type, there might be // some unexpected behavior down the road. Since we can't return an error // (`Server.RegisterService` interface restriction) we panic (at startup). - serviceMsg, err := msr.interfaceRegistry.Resolve(fqMethod) - if err != nil || serviceMsg == nil { + reqType, err := msr.interfaceRegistry.Resolve(requestTypeName) + if err != nil || reqType == nil { panic( fmt.Errorf( "type_url %s has not been registered yet. "+ @@ -83,7 +105,7 @@ func (msr *MsgServiceRouter) RegisterService(sd *grpc.ServiceDesc, handler inter ) } - msr.routes[fqMethod] = func(ctx sdk.Context, req sdk.MsgRequest) (*sdk.Result, error) { + handler := func(ctx sdk.Context, req sdk.MsgRequest) (*sdk.Result, error) { ctx = ctx.WithEventManager(sdk.NewEventManager()) interceptor := func(goCtx context.Context, _ interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { goCtx = context.WithValue(goCtx, sdk.SdkContextKey, ctx) @@ -103,6 +125,10 @@ func (msr *MsgServiceRouter) RegisterService(sd *grpc.ServiceDesc, handler inter return sdk.WrapServiceResult(ctx, resMsg, err) } + + msr.routes[requestTypeName] = handler + // we register a handler for the fully-qualified type URL for cases where + msr.routes[fqMethod] = handler } } diff --git a/types/msgservice/service_msg_client.go b/types/msgservice/service_msg_client.go index de3be89094f8..b77997dba7fd 100644 --- a/types/msgservice/service_msg_client.go +++ b/types/msgservice/service_msg_client.go @@ -20,10 +20,10 @@ type ServiceMsgClientConn struct { } // Invoke implements the grpc ClientConn.Invoke method -func (t *ServiceMsgClientConn) Invoke(_ context.Context, method string, args, _ interface{}, _ ...grpc.CallOption) error { - req, ok := args.(sdk.MsgRequest) +func (t *ServiceMsgClientConn) Invoke(_ context.Context, _ string, args, _ interface{}, _ ...grpc.CallOption) error { + req, ok := args.(sdk.Msg) if !ok { - return fmt.Errorf("%T should implement %T", args, (*sdk.MsgRequest)(nil)) + return fmt.Errorf("%T should implement %T", args, (*sdk.Msg)(nil)) } err := req.ValidateBasic() @@ -31,10 +31,7 @@ func (t *ServiceMsgClientConn) Invoke(_ context.Context, method string, args, _ return err } - t.msgs = append(t.msgs, sdk.ServiceMsg{ - MethodName: method, - Request: req, - }) + t.msgs = append(t.msgs, req) return nil } diff --git a/types/service_msg.go b/types/service_msg.go index ee6cede2eb16..3a77764945e8 100644 --- a/types/service_msg.go +++ b/types/service_msg.go @@ -2,22 +2,11 @@ package types import ( "fmt" - - "github.com/gogo/protobuf/proto" ) // MsgRequest is the interface a transaction message, defined as a proto // service method, must fulfill. -type MsgRequest interface { - proto.Message - // ValidateBasic does a simple validation check that - // doesn't require access to any other information. - ValidateBasic() error - // Signers returns the addrs of signers that must sign. - // CONTRACT: All signatures must be present to be valid. - // CONTRACT: Returns addrs in some deterministic order. - GetSigners() []AccAddress -} +type MsgRequest = Msg // ServiceMsg is the struct into which an Any whose typeUrl matches a service // method format (ex. `/cosmos.gov.v1beta1.Msg/SubmitProposal`) unpacks. @@ -51,7 +40,7 @@ func (msg ServiceMsg) GetSignBytes() []byte { // ref: https://github.com/cosmos/cosmos-sdk/issues/8346 // If `msg` is a service Msg, then we cast its `Request` to a sdk.Msg // and call GetSignBytes on the `Request`. - msgRequest, ok := msg.Request.(Msg) + msgRequest, ok := msg.Request.(LegacyMsg) if !ok { panic(fmt.Errorf("cannot convert ServiceMsg request to sdk.Msg, got %T", msgRequest)) } diff --git a/types/tx_msg.go b/types/tx_msg.go index b8a602d88c05..9205d456c62d 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -11,6 +11,19 @@ type ( Msg interface { proto.Message + // ValidateBasic does a simple validation check that + // doesn't require access to any other information. + ValidateBasic() error + + // Signers returns the addrs of signers that must sign. + // CONTRACT: All signatures must be present to be valid. + // CONTRACT: Returns addrs in some deterministic order. + GetSigners() []AccAddress + } + + LegacyMsg interface { + Msg + // Return the message type. // Must be alphanumeric or empty. Route() string @@ -19,17 +32,8 @@ type ( // within tags Type() string - // ValidateBasic does a simple validation check that - // doesn't require access to any other information. - ValidateBasic() error - // Get the canonical byte representation of the Msg. GetSignBytes() []byte - - // Signers returns the addrs of signers that must sign. - // CONTRACT: All signatures must be present to be valid. - // CONTRACT: Returns addrs in some deterministic order. - GetSigners() []AccAddress } // Fee defines an interface for an application application-defined concrete From b54d71f3a38381558785da6bd874f46dbf86a16b Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 12 Apr 2021 17:35:05 -0400 Subject: [PATCH 02/47] wip --- baseapp/baseapp.go | 18 +++++------------- types/msgservice/msg_service.go | 4 ++-- types/msgservice/service_msg_client.go | 11 ++++++++--- types/service_msg.go | 15 +++++++++++++-- types/tx_msg.go | 22 +++++++++------------- 5 files changed, 37 insertions(+), 33 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 39cd0a44500e..5ec243ba91b8 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -709,31 +709,23 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s err error ) - msgType := proto.MessageName(msg) - - if handler := app.msgServiceRouter.Handler(msgType); handler != nil { - // ADR 031 request type routing - msgResult, err = handler(ctx, msg) - } else if svcMsg, ok := msg.(sdk.ServiceMsg); ok { - // deprecated ADR 031 method name routing + if svcMsg, ok := msg.(sdk.ServiceMsg); ok { msgFqName = svcMsg.MethodName handler := app.msgServiceRouter.Handler(msgFqName) if handler == nil { return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message service method: %s; message index: %d", msgFqName, i) } msgResult, err = handler(ctx, svcMsg.Request) - } else if legacyMsg, ok := msg.(sdk.LegacyMsg); ok { + } else { // legacy sdk.Msg routing - msgRoute := legacyMsg.Route() - msgFqName = legacyMsg.Type() + msgRoute := msg.Route() + msgFqName = msg.Type() handler := app.router.Route(ctx, msgRoute) if handler == nil { return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message route: %s; message index: %d", msgRoute, i) } msgResult, err = handler(ctx, msg) - } else { - return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "can't route message %+v", msg) } if err != nil { @@ -751,7 +743,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s // separate each result. events = events.AppendEvents(msgEvents) - txMsgData.Data = append(txMsgData.Data, &sdk.MsgData{MsgType: msgType, Data: msgResult.Data}) + txMsgData.Data = append(txMsgData.Data, &sdk.MsgData{MsgType: msg.Type(), Data: msgResult.Data}) msgLogs = append(msgLogs, sdk.NewABCIMessageLog(uint32(i), msgResult.Log, msgEvents)) } diff --git a/types/msgservice/msg_service.go b/types/msgservice/msg_service.go index ddffed943e8a..7ddabd108172 100644 --- a/types/msgservice/msg_service.go +++ b/types/msgservice/msg_service.go @@ -5,7 +5,6 @@ import ( "fmt" "strings" - "github.com/gogo/protobuf/proto" "google.golang.org/grpc" codectypes "github.com/cosmos/cosmos-sdk/codec/types" @@ -24,7 +23,7 @@ func RegisterMsgServiceDesc(registry codectypes.InterfaceRegistry, sd *grpc.Serv // This approach is maybe a bit hacky, but less hacky than reflecting on the handler object itself. // We use a no-op interceptor to avoid actually calling into the handler itself. _, _ = methodHandler(nil, context.Background(), func(i interface{}) error { - msg, ok := i.(proto.Message) + msg, ok := i.(sdk.MsgRequest) if !ok { // We panic here because there is no other alternative and the app cannot be initialized correctly // this should only happen if there is a problem with code generation in which case the app won't @@ -32,6 +31,7 @@ func RegisterMsgServiceDesc(registry codectypes.InterfaceRegistry, sd *grpc.Serv panic(fmt.Errorf("can't register request type %T for service method %s", i, fqMethod)) } + registry.RegisterImplementations((*sdk.MsgRequest)(nil), msg) registry.RegisterCustomTypeURL((*sdk.MsgRequest)(nil), fqMethod, msg) return nil }, noopInterceptor) diff --git a/types/msgservice/service_msg_client.go b/types/msgservice/service_msg_client.go index b77997dba7fd..3287e4891c1a 100644 --- a/types/msgservice/service_msg_client.go +++ b/types/msgservice/service_msg_client.go @@ -4,6 +4,8 @@ import ( "context" "fmt" + "github.com/gogo/protobuf/proto" + gogogrpc "github.com/gogo/protobuf/grpc" grpc "google.golang.org/grpc" @@ -21,9 +23,9 @@ type ServiceMsgClientConn struct { // Invoke implements the grpc ClientConn.Invoke method func (t *ServiceMsgClientConn) Invoke(_ context.Context, _ string, args, _ interface{}, _ ...grpc.CallOption) error { - req, ok := args.(sdk.Msg) + req, ok := args.(sdk.MsgRequest) if !ok { - return fmt.Errorf("%T should implement %T", args, (*sdk.Msg)(nil)) + return fmt.Errorf("%T should implement %T", args, (*sdk.MsgRequest)(nil)) } err := req.ValidateBasic() @@ -31,7 +33,10 @@ func (t *ServiceMsgClientConn) Invoke(_ context.Context, _ string, args, _ inter return err } - t.msgs = append(t.msgs, req) + t.msgs = append(t.msgs, sdk.ServiceMsg{ + MethodName: proto.MessageName(req), + Request: req, + }) return nil } diff --git a/types/service_msg.go b/types/service_msg.go index 3a77764945e8..ee6cede2eb16 100644 --- a/types/service_msg.go +++ b/types/service_msg.go @@ -2,11 +2,22 @@ package types import ( "fmt" + + "github.com/gogo/protobuf/proto" ) // MsgRequest is the interface a transaction message, defined as a proto // service method, must fulfill. -type MsgRequest = Msg +type MsgRequest interface { + proto.Message + // ValidateBasic does a simple validation check that + // doesn't require access to any other information. + ValidateBasic() error + // Signers returns the addrs of signers that must sign. + // CONTRACT: All signatures must be present to be valid. + // CONTRACT: Returns addrs in some deterministic order. + GetSigners() []AccAddress +} // ServiceMsg is the struct into which an Any whose typeUrl matches a service // method format (ex. `/cosmos.gov.v1beta1.Msg/SubmitProposal`) unpacks. @@ -40,7 +51,7 @@ func (msg ServiceMsg) GetSignBytes() []byte { // ref: https://github.com/cosmos/cosmos-sdk/issues/8346 // If `msg` is a service Msg, then we cast its `Request` to a sdk.Msg // and call GetSignBytes on the `Request`. - msgRequest, ok := msg.Request.(LegacyMsg) + msgRequest, ok := msg.Request.(Msg) if !ok { panic(fmt.Errorf("cannot convert ServiceMsg request to sdk.Msg, got %T", msgRequest)) } diff --git a/types/tx_msg.go b/types/tx_msg.go index 9205d456c62d..b8a602d88c05 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -11,19 +11,6 @@ type ( Msg interface { proto.Message - // ValidateBasic does a simple validation check that - // doesn't require access to any other information. - ValidateBasic() error - - // Signers returns the addrs of signers that must sign. - // CONTRACT: All signatures must be present to be valid. - // CONTRACT: Returns addrs in some deterministic order. - GetSigners() []AccAddress - } - - LegacyMsg interface { - Msg - // Return the message type. // Must be alphanumeric or empty. Route() string @@ -32,8 +19,17 @@ type ( // within tags Type() string + // ValidateBasic does a simple validation check that + // doesn't require access to any other information. + ValidateBasic() error + // Get the canonical byte representation of the Msg. GetSignBytes() []byte + + // Signers returns the addrs of signers that must sign. + // CONTRACT: All signatures must be present to be valid. + // CONTRACT: Returns addrs in some deterministic order. + GetSigners() []AccAddress } // Fee defines an interface for an application application-defined concrete From 03a358d23280d8bdd911c47cc325b979948652f0 Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 12 Apr 2021 17:56:15 -0400 Subject: [PATCH 03/47] wip --- types/msgservice/service_msg_client.go | 5 +---- types/service_msg.go | 6 ++---- types/tx/types.go | 3 +-- x/auth/tx/builder.go | 2 +- x/authz/types/msgs_test.go | 1 - 5 files changed, 5 insertions(+), 12 deletions(-) diff --git a/types/msgservice/service_msg_client.go b/types/msgservice/service_msg_client.go index 3287e4891c1a..b33c785baf46 100644 --- a/types/msgservice/service_msg_client.go +++ b/types/msgservice/service_msg_client.go @@ -4,8 +4,6 @@ import ( "context" "fmt" - "github.com/gogo/protobuf/proto" - gogogrpc "github.com/gogo/protobuf/grpc" grpc "google.golang.org/grpc" @@ -34,8 +32,7 @@ func (t *ServiceMsgClientConn) Invoke(_ context.Context, _ string, args, _ inter } t.msgs = append(t.msgs, sdk.ServiceMsg{ - MethodName: proto.MessageName(req), - Request: req, + Request: req, }) return nil diff --git a/types/service_msg.go b/types/service_msg.go index ee6cede2eb16..00aad1ce11a5 100644 --- a/types/service_msg.go +++ b/types/service_msg.go @@ -22,8 +22,6 @@ type MsgRequest interface { // ServiceMsg is the struct into which an Any whose typeUrl matches a service // method format (ex. `/cosmos.gov.v1beta1.Msg/SubmitProposal`) unpacks. type ServiceMsg struct { - // MethodName is the fully-qualified service method name. - MethodName string // Request is the request payload. Request MsgRequest } @@ -36,7 +34,7 @@ func (msg ServiceMsg) String() string { return "ServiceMsg" } // Route implements Msg.Route method. func (msg ServiceMsg) Route() string { - return msg.MethodName + return proto.MessageName(msg.Request) } // ValidateBasic implements Msg.ValidateBasic method. @@ -66,5 +64,5 @@ func (msg ServiceMsg) GetSigners() []AccAddress { // Type implements Msg.Type method. func (msg ServiceMsg) Type() string { - return msg.MethodName + return proto.MessageName(msg.Request) } diff --git a/types/tx/types.go b/types/tx/types.go index 66fbb193ad96..ed16d76e7e44 100644 --- a/types/tx/types.go +++ b/types/tx/types.go @@ -33,8 +33,7 @@ func (t *Tx) GetMsgs() []sdk.Msg { panic("Any cached value is nil. Transaction messages must be correctly packed Any values.") } msg = sdk.ServiceMsg{ - MethodName: any.TypeUrl, - Request: any.GetCachedValue().(sdk.MsgRequest), + Request: any.GetCachedValue().(sdk.MsgRequest), } } else { msg = any.GetCachedValue().(sdk.Msg) diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index 31f4fdf8eca2..385caca9284f 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -206,7 +206,7 @@ func (w *wrapper) SetMsgs(msgs ...sdk.Msg) error { var err error switch msg := msg.(type) { case sdk.ServiceMsg: - anys[i], err = codectypes.NewAnyWithCustomTypeURL(msg.Request, msg.MethodName) + anys[i], err = codectypes.NewAnyWithValue(msg.Request) default: anys[i], err = codectypes.NewAnyWithValue(msg) } diff --git a/x/authz/types/msgs_test.go b/x/authz/types/msgs_test.go index c27c750b0d93..8c1b20ca6e6b 100644 --- a/x/authz/types/msgs_test.go +++ b/x/authz/types/msgs_test.go @@ -30,7 +30,6 @@ func TestMsgExecAuthorized(t *testing.T) { {"zero-messages test: should fail", grantee, []sdk.ServiceMsg{}, false}, {"valid test: msg type", grantee, []sdk.ServiceMsg{ { - MethodName: banktypes.SendAuthorization{}.MethodName(), Request: &banktypes.MsgSend{ Amount: sdk.NewCoins(sdk.NewInt64Coin("steak", 2)), FromAddress: granter.String(), From 2a6223eef46747288a7a099b97d77d795640796f Mon Sep 17 00:00:00 2001 From: Aaron Craelius Date: Mon, 12 Apr 2021 18:36:27 -0400 Subject: [PATCH 04/47] wip on refactoring adr 031 type URLs --- baseapp/baseapp.go | 20 ++++---- baseapp/msg_service_router.go | 21 ++++---- server/rosetta/converter.go | 24 --------- testutil/testdata/tx.go | 9 +--- types/codec.go | 3 -- types/msgservice/msg_service.go | 6 +-- types/msgservice/service_msg_client.go | 8 ++- types/service_msg.go | 68 ------------------------- types/simulation/types.go | 16 ++---- types/tx/types.go | 16 +----- types/tx_msg.go | 31 +++++++---- x/auth/legacy/legacytx/stdsign.go | 2 +- x/auth/legacy/legacytx/stdtx_builder.go | 20 +------- x/auth/tx/builder.go | 7 +-- x/authz/client/cli/tx.go | 12 +---- x/authz/exported/authorizations.go | 2 +- x/authz/keeper/keeper.go | 23 +++++---- x/authz/keeper/keeper_test.go | 6 +-- x/authz/keeper/msg_server.go | 4 +- x/authz/types/codec.go | 7 --- x/authz/types/generic_authorization.go | 2 +- x/authz/types/msgs.go | 34 +++++-------- x/bank/types/send_authorization.go | 31 +++++------ x/feegrant/simulation/operations.go | 4 +- x/feegrant/types/codec.go | 6 --- x/feegrant/types/filtered_fee.go | 2 +- x/feegrant/types/msgs.go | 2 +- x/gov/types/msgs.go | 6 --- x/staking/types/authz.go | 4 +- 29 files changed, 111 insertions(+), 285 deletions(-) delete mode 100644 types/service_msg.go diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 5ec243ba91b8..fc73c92c7a18 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -709,23 +709,21 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s err error ) - if svcMsg, ok := msg.(sdk.ServiceMsg); ok { - msgFqName = svcMsg.MethodName - handler := app.msgServiceRouter.Handler(msgFqName) - if handler == nil { - return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message service method: %s; message index: %d", msgFqName, i) - } - msgResult, err = handler(ctx, svcMsg.Request) - } else { + if handler := app.msgServiceRouter.Handler(msg); handler != nil { + // ADR 031 request type routing + msgResult, err = handler(ctx, msg) + } else if legacyMsg, ok := msg.(sdk.LegacyMsg); ok { // legacy sdk.Msg routing - msgRoute := msg.Route() - msgFqName = msg.Type() + msgRoute := legacyMsg.Route() + msgFqName = legacyMsg.Type() handler := app.router.Route(ctx, msgRoute) if handler == nil { return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message route: %s; message index: %d", msgRoute, i) } msgResult, err = handler(ctx, msg) + } else { + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "can't route message %+v", msg) } if err != nil { @@ -743,7 +741,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s // separate each result. events = events.AppendEvents(msgEvents) - txMsgData.Data = append(txMsgData.Data, &sdk.MsgData{MsgType: msg.Type(), Data: msgResult.Data}) + txMsgData.Data = append(txMsgData.Data, &sdk.MsgData{MsgType: proto.MessageName(msg), Data: msgResult.Data}) msgLogs = append(msgLogs, sdk.NewABCIMessageLog(uint32(i), msgResult.Log, msgEvents)) } diff --git a/baseapp/msg_service_router.go b/baseapp/msg_service_router.go index 153af46689f1..c3ad010e01d3 100644 --- a/baseapp/msg_service_router.go +++ b/baseapp/msg_service_router.go @@ -29,12 +29,17 @@ func NewMsgServiceRouter() *MsgServiceRouter { } // MsgServiceHandler defines a function type which handles Msg service message. -type MsgServiceHandler = func(ctx sdk.Context, req sdk.MsgRequest) (*sdk.Result, error) +type MsgServiceHandler = func(ctx sdk.Context, req sdk.Msg) (*sdk.Result, error) -// Handler returns the MsgServiceHandler for a given query route path or nil +// Handler returns the MsgServiceHandler for a given msg or nil if not found. +func (msr *MsgServiceRouter) Handler(msg sdk.Msg) MsgServiceHandler { + return msr.routes[proto.MessageName(msg)] +} + +// HandlerByName returns the MsgServiceHandler for a given query route path or nil // if not found. -func (msr *MsgServiceRouter) Handler(methodName string) MsgServiceHandler { - return msr.routes[methodName] +func (msr *MsgServiceRouter) HandlerByName(msgName string) MsgServiceHandler { + return msr.routes[msgName] } // RegisterService implements the gRPC Server.RegisterService method. sd is a gRPC @@ -85,7 +90,7 @@ func (msr *MsgServiceRouter) RegisterService(sd *grpc.ServiceDesc, handler inter "Before calling RegisterService, you must register all interfaces by calling the `RegisterInterfaces` "+ "method on module.BasicManager. Each module should call `msgservice.RegisterMsgServiceDesc` inside its "+ "`RegisterInterfaces` method with the `_Msg_serviceDesc` generated by proto-gen", - fqMethod, + requestTypeName, ), ) } @@ -94,7 +99,7 @@ func (msr *MsgServiceRouter) RegisterService(sd *grpc.ServiceDesc, handler inter // registered more than once, then we should error. Since we can't // return an error (`Server.RegisterService` interface restriction) we // panic (at startup). - _, found := msr.routes[fqMethod] + _, found := msr.routes[requestTypeName] if found { panic( fmt.Errorf( @@ -105,7 +110,7 @@ func (msr *MsgServiceRouter) RegisterService(sd *grpc.ServiceDesc, handler inter ) } - handler := func(ctx sdk.Context, req sdk.MsgRequest) (*sdk.Result, error) { + handler := func(ctx sdk.Context, req sdk.Msg) (*sdk.Result, error) { ctx = ctx.WithEventManager(sdk.NewEventManager()) interceptor := func(goCtx context.Context, _ interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { goCtx = context.WithValue(goCtx, sdk.SdkContextKey, ctx) @@ -127,8 +132,6 @@ func (msr *MsgServiceRouter) RegisterService(sd *grpc.ServiceDesc, handler inter } msr.routes[requestTypeName] = handler - // we register a handler for the fully-qualified type URL for cases where - msr.routes[fqMethod] = handler } } diff --git a/server/rosetta/converter.go b/server/rosetta/converter.go index e9e5db559e64..4b103ded38d1 100644 --- a/server/rosetta/converter.go +++ b/server/rosetta/converter.go @@ -242,30 +242,6 @@ func (c converter) Meta(msg sdk.Msg) (meta map[string]interface{}, err error) { // as metadata func (c converter) Ops(status string, msg sdk.Msg) ([]*rosettatypes.Operation, error) { opName := proto.MessageName(msg) - // in case proto does not recognize the message name - // then we should try to cast it to service msg, to - // check if it was wrapped or not, in case the cast - // from sdk.ServiceMsg to sdk.Msg fails, then a - // codec error is returned - if opName == "" { - unwrappedMsg, ok := msg.(sdk.ServiceMsg) - if !ok { - return nil, crgerrs.WrapError(crgerrs.ErrCodec, fmt.Sprintf("unrecognized message type: %T", msg)) - } - - msg, ok = unwrappedMsg.Request.(sdk.Msg) - if !ok { - return nil, crgerrs.WrapError( - crgerrs.ErrCodec, - fmt.Sprintf("unable to cast %T to sdk.Msg, method: %s", unwrappedMsg.Request, unwrappedMsg.MethodName), - ) - } - - opName = proto.MessageName(msg) - if opName == "" { - return nil, crgerrs.WrapError(crgerrs.ErrCodec, fmt.Sprintf("unrecognized message type: %T", msg)) - } - } meta, err := c.Meta(msg) if err != nil { diff --git a/testutil/testdata/tx.go b/testutil/testdata/tx.go index 4da80c9e4e8f..653400ffb67c 100644 --- a/testutil/testdata/tx.go +++ b/testutil/testdata/tx.go @@ -77,14 +77,7 @@ func (msg *TestMsg) GetSigners() []sdk.AccAddress { } func (msg *TestMsg) ValidateBasic() error { return nil } -var _ sdk.MsgRequest = &MsgCreateDog{} +var _ sdk.Msg = &MsgCreateDog{} func (msg *MsgCreateDog) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{} } func (msg *MsgCreateDog) ValidateBasic() error { return nil } - -func NewServiceMsgCreateDog(msg *MsgCreateDog) sdk.Msg { - return sdk.ServiceMsg{ - MethodName: "/testdata.Msg/CreateDog", - Request: msg, - } -} diff --git a/types/codec.go b/types/codec.go index 8123fc7d51a4..d6b6917085d5 100644 --- a/types/codec.go +++ b/types/codec.go @@ -21,7 +21,4 @@ func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { // RegisterInterfaces registers the sdk message type. func RegisterInterfaces(registry types.InterfaceRegistry) { registry.RegisterInterface(MsgInterfaceProtoName, (*Msg)(nil)) - // the interface name for MsgRequest is ServiceMsg because this is most useful for clients - // to understand - it will be the way for clients to introspect on available Msg service methods - registry.RegisterInterface(ServiceMsgInterfaceProtoName, (*MsgRequest)(nil)) } diff --git a/types/msgservice/msg_service.go b/types/msgservice/msg_service.go index 7ddabd108172..b2316a6424a2 100644 --- a/types/msgservice/msg_service.go +++ b/types/msgservice/msg_service.go @@ -23,7 +23,7 @@ func RegisterMsgServiceDesc(registry codectypes.InterfaceRegistry, sd *grpc.Serv // This approach is maybe a bit hacky, but less hacky than reflecting on the handler object itself. // We use a no-op interceptor to avoid actually calling into the handler itself. _, _ = methodHandler(nil, context.Background(), func(i interface{}) error { - msg, ok := i.(sdk.MsgRequest) + msg, ok := i.(sdk.Msg) if !ok { // We panic here because there is no other alternative and the app cannot be initialized correctly // this should only happen if there is a problem with code generation in which case the app won't @@ -31,8 +31,8 @@ func RegisterMsgServiceDesc(registry codectypes.InterfaceRegistry, sd *grpc.Serv panic(fmt.Errorf("can't register request type %T for service method %s", i, fqMethod)) } - registry.RegisterImplementations((*sdk.MsgRequest)(nil), msg) - registry.RegisterCustomTypeURL((*sdk.MsgRequest)(nil), fqMethod, msg) + registry.RegisterImplementations((*sdk.Msg)(nil), msg) + registry.RegisterCustomTypeURL((*sdk.Msg)(nil), fqMethod, msg) return nil }, noopInterceptor) diff --git a/types/msgservice/service_msg_client.go b/types/msgservice/service_msg_client.go index b33c785baf46..b77997dba7fd 100644 --- a/types/msgservice/service_msg_client.go +++ b/types/msgservice/service_msg_client.go @@ -21,9 +21,9 @@ type ServiceMsgClientConn struct { // Invoke implements the grpc ClientConn.Invoke method func (t *ServiceMsgClientConn) Invoke(_ context.Context, _ string, args, _ interface{}, _ ...grpc.CallOption) error { - req, ok := args.(sdk.MsgRequest) + req, ok := args.(sdk.Msg) if !ok { - return fmt.Errorf("%T should implement %T", args, (*sdk.MsgRequest)(nil)) + return fmt.Errorf("%T should implement %T", args, (*sdk.Msg)(nil)) } err := req.ValidateBasic() @@ -31,9 +31,7 @@ func (t *ServiceMsgClientConn) Invoke(_ context.Context, _ string, args, _ inter return err } - t.msgs = append(t.msgs, sdk.ServiceMsg{ - Request: req, - }) + t.msgs = append(t.msgs, req) return nil } diff --git a/types/service_msg.go b/types/service_msg.go deleted file mode 100644 index 00aad1ce11a5..000000000000 --- a/types/service_msg.go +++ /dev/null @@ -1,68 +0,0 @@ -package types - -import ( - "fmt" - - "github.com/gogo/protobuf/proto" -) - -// MsgRequest is the interface a transaction message, defined as a proto -// service method, must fulfill. -type MsgRequest interface { - proto.Message - // ValidateBasic does a simple validation check that - // doesn't require access to any other information. - ValidateBasic() error - // Signers returns the addrs of signers that must sign. - // CONTRACT: All signatures must be present to be valid. - // CONTRACT: Returns addrs in some deterministic order. - GetSigners() []AccAddress -} - -// ServiceMsg is the struct into which an Any whose typeUrl matches a service -// method format (ex. `/cosmos.gov.v1beta1.Msg/SubmitProposal`) unpacks. -type ServiceMsg struct { - // Request is the request payload. - Request MsgRequest -} - -var _ Msg = ServiceMsg{} - -func (msg ServiceMsg) ProtoMessage() {} -func (msg ServiceMsg) Reset() {} -func (msg ServiceMsg) String() string { return "ServiceMsg" } - -// Route implements Msg.Route method. -func (msg ServiceMsg) Route() string { - return proto.MessageName(msg.Request) -} - -// ValidateBasic implements Msg.ValidateBasic method. -func (msg ServiceMsg) ValidateBasic() error { - return msg.Request.ValidateBasic() -} - -// GetSignBytes implements Msg.GetSignBytes method. -func (msg ServiceMsg) GetSignBytes() []byte { - // Here, we're gracefully supporting Amino JSON for service - // Msgs. - // ref: https://github.com/cosmos/cosmos-sdk/issues/8346 - // If `msg` is a service Msg, then we cast its `Request` to a sdk.Msg - // and call GetSignBytes on the `Request`. - msgRequest, ok := msg.Request.(Msg) - if !ok { - panic(fmt.Errorf("cannot convert ServiceMsg request to sdk.Msg, got %T", msgRequest)) - } - - return msgRequest.GetSignBytes() -} - -// GetSigners implements Msg.GetSigners method. -func (msg ServiceMsg) GetSigners() []AccAddress { - return msg.Request.GetSigners() -} - -// Type implements Msg.Type method. -func (msg ServiceMsg) Type() string { - return proto.MessageName(msg.Request) -} diff --git a/types/simulation/types.go b/types/simulation/types.go index fabbcd47552b..77b251d02007 100644 --- a/types/simulation/types.go +++ b/types/simulation/types.go @@ -2,11 +2,11 @@ package simulation import ( "encoding/json" - "fmt" "math/rand" - "reflect" "time" + "github.com/gogo/protobuf/proto" + "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -78,16 +78,8 @@ func NewOperationMsgBasic(route, name, comment string, ok bool, msg []byte) Oper // NewOperationMsg - create a new operation message from sdk.Msg func NewOperationMsg(msg sdk.Msg, ok bool, comment string, cdc *codec.ProtoCodec) OperationMsg { - if reflect.TypeOf(msg) == reflect.TypeOf(sdk.ServiceMsg{}) { - srvMsg, ok := msg.(sdk.ServiceMsg) - if !ok { - panic(fmt.Sprintf("Expecting %T to implement sdk.ServiceMsg", msg)) - } - bz := cdc.MustMarshalJSON(srvMsg.Request) - - return NewOperationMsgBasic(srvMsg.MethodName, srvMsg.MethodName, comment, ok, bz) - } - return NewOperationMsgBasic(msg.Route(), msg.Type(), comment, ok, msg.GetSignBytes()) + msgName := proto.MessageName(msg) + return NewOperationMsgBasic(msgName, msgName, comment, ok, sdk.GetLegacySignBytes(msg)) } // NoOpMsg - create a no-operation message diff --git a/types/tx/types.go b/types/tx/types.go index ed16d76e7e44..bdd8aa1b5326 100644 --- a/types/tx/types.go +++ b/types/tx/types.go @@ -26,19 +26,7 @@ func (t *Tx) GetMsgs() []sdk.Msg { anys := t.Body.Messages res := make([]sdk.Msg, len(anys)) for i, any := range anys { - var msg sdk.Msg - if msgservice.IsServiceMsg(any.TypeUrl) { - req := any.GetCachedValue() - if req == nil { - panic("Any cached value is nil. Transaction messages must be correctly packed Any values.") - } - msg = sdk.ServiceMsg{ - Request: any.GetCachedValue().(sdk.MsgRequest), - } - } else { - msg = any.GetCachedValue().(sdk.Msg) - } - res[i] = msg + res[i] = any.GetCachedValue().(sdk.Msg) } return res } @@ -183,7 +171,7 @@ func (m *TxBody) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { // If the any's typeUrl contains 2 slashes, then we unpack the any into // a ServiceMsg struct as per ADR-031. if msgservice.IsServiceMsg(any.TypeUrl) { - var req sdk.MsgRequest + var req sdk.Msg err := unpacker.UnpackAny(any, &req) if err != nil { return err diff --git a/types/tx_msg.go b/types/tx_msg.go index b8a602d88c05..8ce551bc5d72 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -11,27 +11,31 @@ type ( Msg interface { proto.Message - // Return the message type. - // Must be alphanumeric or empty. - Route() string - - // Returns a human-readable string for the message, intended for utilization - // within tags - Type() string - // ValidateBasic does a simple validation check that // doesn't require access to any other information. ValidateBasic() error - // Get the canonical byte representation of the Msg. - GetSignBytes() []byte - // Signers returns the addrs of signers that must sign. // CONTRACT: All signatures must be present to be valid. // CONTRACT: Returns addrs in some deterministic order. GetSigners() []AccAddress } + LegacyMsg interface { + Msg + + // Get the canonical byte representation of the Msg. + GetSignBytes() []byte + + // Return the message type. + // Must be alphanumeric or empty. + Route() string + + // Returns a human-readable string for the message, intended for utilization + // within tags + Type() string + } + // Fee defines an interface for an application application-defined concrete // transaction type to be able to set and return the transaction fee. Fee interface { @@ -85,3 +89,8 @@ type TxDecoder func(txBytes []byte) (Tx, error) // TxEncoder marshals transaction to bytes type TxEncoder func(tx Tx) ([]byte, error) + +func GetLegacySignBytes(msg Msg) []byte { + legacyMsg := msg.(LegacyMsg) + return legacyMsg.GetSignBytes() +} diff --git a/x/auth/legacy/legacytx/stdsign.go b/x/auth/legacy/legacytx/stdsign.go index dbb71f1a9819..bb2b5cc10206 100644 --- a/x/auth/legacy/legacytx/stdsign.go +++ b/x/auth/legacy/legacytx/stdsign.go @@ -38,7 +38,7 @@ func StdSignBytes(chainID string, accnum, sequence, timeout uint64, fee StdFee, // If msg is a legacy Msg, then GetSignBytes is implemented. // If msg is a ServiceMsg, then GetSignBytes has graceful support of // calling GetSignBytes from its underlying Msg. - msgsBytes = append(msgsBytes, json.RawMessage(msg.GetSignBytes())) + msgsBytes = append(msgsBytes, json.RawMessage(sdk.GetLegacySignBytes(msg))) } bz, err := legacy.Cdc.MarshalJSON(StdSignDoc{ diff --git a/x/auth/legacy/legacytx/stdtx_builder.go b/x/auth/legacy/legacytx/stdtx_builder.go index ae0e97a3f34c..afc2f0208b9d 100644 --- a/x/auth/legacy/legacytx/stdtx_builder.go +++ b/x/auth/legacy/legacytx/stdtx_builder.go @@ -29,25 +29,7 @@ func (s *StdTxBuilder) GetTx() authsigning.Tx { // SetMsgs implements TxBuilder.SetMsgs func (s *StdTxBuilder) SetMsgs(msgs ...sdk.Msg) error { - stdTxMsgs := make([]sdk.Msg, len(msgs)) - - for i, msg := range msgs { - switch msg := msg.(type) { - case sdk.ServiceMsg: - // Since ServiceMsg isn't registered with amino, we unpack msg.Request - // into a Msg so that it's handled gracefully for the legacy - // GET /txs/{hash} and /txs endpoints. - sdkMsg, ok := msg.Request.(sdk.Msg) - if !ok { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "Expecting %T at %d to implement sdk.Msg", msg.Request, i) - } - stdTxMsgs[i] = sdkMsg - default: - // legacy sdk.Msg - stdTxMsgs[i] = msg - } - } - s.Msgs = stdTxMsgs + s.Msgs = msgs return nil } diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index 385caca9284f..32eacd0137f7 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -204,12 +204,7 @@ func (w *wrapper) SetMsgs(msgs ...sdk.Msg) error { for i, msg := range msgs { var err error - switch msg := msg.(type) { - case sdk.ServiceMsg: - anys[i], err = codectypes.NewAnyWithValue(msg.Request) - default: - anys[i], err = codectypes.NewAnyWithValue(msg) - } + anys[i], err = codectypes.NewAnyWithValue(msg) if err != nil { return err } diff --git a/x/authz/client/cli/tx.go b/x/authz/client/cli/tx.go index 44df6f5e9aa4..42f7e1fe4be8 100644 --- a/x/authz/client/cli/tx.go +++ b/x/authz/client/cli/tx.go @@ -254,17 +254,7 @@ Example: if err != nil { return err } - msgs := theTx.GetMsgs() - serviceMsgs := make([]sdk.ServiceMsg, len(msgs)) - for i, msg := range msgs { - srvMsg, ok := msg.(sdk.ServiceMsg) - if !ok { - return fmt.Errorf("tx contains %T which is not a sdk.ServiceMsg", msg) - } - serviceMsgs[i] = srvMsg - } - - msg := types.NewMsgExecAuthorized(grantee, serviceMsgs) + msg := types.NewMsgExecAuthorized(grantee, theTx.GetMsgs()) svcMsgClientConn := &msgservice.ServiceMsgClientConn{} msgClient := types.NewMsgClient(svcMsgClientConn) _, err = msgClient.ExecAuthorized(context.Background(), &msg) diff --git a/x/authz/exported/authorizations.go b/x/authz/exported/authorizations.go index 32fea0b0b3a8..c065c9f65072 100644 --- a/x/authz/exported/authorizations.go +++ b/x/authz/exported/authorizations.go @@ -15,7 +15,7 @@ type Authorization interface { // Accept determines whether this grant permits the provided sdk.ServiceMsg to be performed, and if // so provides an upgraded authorization instance. - Accept(ctx sdk.Context, msg sdk.ServiceMsg) (updated Authorization, delete bool, err error) + Accept(ctx sdk.Context, msg sdk.Msg) (updated Authorization, delete bool, err error) // ValidateBasic does a simple validation check that // doesn't require access to any other information. diff --git a/x/authz/keeper/keeper.go b/x/authz/keeper/keeper.go index dcbee170cb1e..059b771f34e6 100644 --- a/x/authz/keeper/keeper.go +++ b/x/authz/keeper/keeper.go @@ -73,26 +73,29 @@ func (k Keeper) update(ctx sdk.Context, grantee sdk.AccAddress, granter sdk.AccA // DispatchActions attempts to execute the provided messages via authorization // grants from the message signer to the grantee. -func (k Keeper) DispatchActions(ctx sdk.Context, grantee sdk.AccAddress, serviceMsgs []sdk.ServiceMsg) (*sdk.Result, error) { +func (k Keeper) DispatchActions(ctx sdk.Context, grantee sdk.AccAddress, msgs []sdk.Msg) (*sdk.Result, error) { var msgResult *sdk.Result var err error - for _, serviceMsg := range serviceMsgs { - signers := serviceMsg.GetSigners() + for _, msg := range msgs { + signers := msg.GetSigners() if len(signers) != 1 { return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "authorization can be given to msg with only one signer") } granter := signers[0] if !granter.Equals(grantee) { - authorization, _ := k.GetOrRevokeAuthorization(ctx, grantee, granter, serviceMsg.MethodName) + authorization, _ := k.GetOrRevokeAuthorization(ctx, grantee, granter, proto.MessageName(msg)) if authorization == nil { return nil, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "authorization not found") } - updated, del, err := authorization.Accept(ctx, serviceMsg) + updated, del, err := authorization.Accept(ctx, msg) if err != nil { return nil, err } if del { - k.Revoke(ctx, grantee, granter, serviceMsg.Type()) + err = k.Revoke(ctx, grantee, granter, proto.MessageName(msg)) + if err != nil { + return nil, err + } } else if updated != nil { err = k.update(ctx, grantee, granter, updated) if err != nil { @@ -100,15 +103,15 @@ func (k Keeper) DispatchActions(ctx sdk.Context, grantee sdk.AccAddress, service } } } - handler := k.router.Handler(serviceMsg.Route()) + handler := k.router.Handler(msg) if handler == nil { - return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message route: %s", serviceMsg.Route()) + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message route: %s", proto.MessageName(msg)) } - msgResult, err = handler(ctx, serviceMsg.Request) + msgResult, err = handler(ctx, msg) if err != nil { - return nil, sdkerrors.Wrapf(err, "failed to execute message; message %s", serviceMsg.MethodName) + return nil, sdkerrors.Wrapf(err, "failed to execute message; message %v", msg) } } diff --git a/x/authz/keeper/keeper_test.go b/x/authz/keeper/keeper_test.go index 161fed6e98bd..63156d6f2ed0 100644 --- a/x/authz/keeper/keeper_test.go +++ b/x/authz/keeper/keeper_test.go @@ -153,7 +153,7 @@ func (s *TestSuite) TestKeeperFees() { s.Require().NoError(msgs.UnpackInterfaces(app.AppCodec())) s.T().Log("verify dispatch fails with invalid authorization") - executeMsgs, err := msgs.GetServiceMsgs() + executeMsgs, err := msgs.GetMessages() s.Require().NoError(err) result, err := app.AuthzKeeper.DispatchActions(s.ctx, granteeAddr, executeMsgs) @@ -169,7 +169,7 @@ func (s *TestSuite) TestKeeperFees() { s.Require().Equal(authorization.MethodName(), banktypes.SendAuthorization{}.MethodName()) - executeMsgs, err = msgs.GetServiceMsgs() + executeMsgs, err = msgs.GetMessages() s.Require().NoError(err) result, err = app.AuthzKeeper.DispatchActions(s.ctx, granteeAddr, executeMsgs) @@ -194,7 +194,7 @@ func (s *TestSuite) TestKeeperFees() { }) s.Require().NoError(msgs.UnpackInterfaces(app.AppCodec())) - executeMsgs, err = msgs.GetServiceMsgs() + executeMsgs, err = msgs.GetMessages() s.Require().NoError(err) result, err = app.AuthzKeeper.DispatchActions(s.ctx, granteeAddr, executeMsgs) diff --git a/x/authz/keeper/msg_server.go b/x/authz/keeper/msg_server.go index 53beb129dfaa..9f3fe3b00ae6 100644 --- a/x/authz/keeper/msg_server.go +++ b/x/authz/keeper/msg_server.go @@ -24,7 +24,7 @@ func (k Keeper) GrantAuthorization(goCtx context.Context, msg *types.MsgGrantAut authorization := msg.GetGrantAuthorization() // If the granted service Msg doesn't exist, we throw an error. - if k.router.Handler(authorization.MethodName()) == nil { + if k.router.HandlerByName(authorization.MethodName()) == nil { return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "%s doesn't exist.", authorization.MethodName()) } @@ -63,7 +63,7 @@ func (k Keeper) ExecAuthorized(goCtx context.Context, msg *types.MsgExecAuthoriz if err != nil { return nil, err } - msgs, err := msg.GetServiceMsgs() + msgs, err := msg.GetMessages() if err != nil { return nil, err } diff --git a/x/authz/types/codec.go b/x/authz/types/codec.go index c6119f2a0bbb..bf2d4ef91d17 100644 --- a/x/authz/types/codec.go +++ b/x/authz/types/codec.go @@ -2,7 +2,6 @@ package types import ( types "github.com/cosmos/cosmos-sdk/codec/types" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/msgservice" "github.com/cosmos/cosmos-sdk/x/authz/exported" bank "github.com/cosmos/cosmos-sdk/x/bank/types" @@ -11,12 +10,6 @@ import ( // RegisterInterfaces registers the interfaces types with the interface registry func RegisterInterfaces(registry types.InterfaceRegistry) { - registry.RegisterImplementations((*sdk.MsgRequest)(nil), - &MsgGrantAuthorizationRequest{}, - &MsgRevokeAuthorizationRequest{}, - &MsgExecAuthorizedRequest{}, - ) - registry.RegisterInterface( "cosmos.authz.v1beta1.Authorization", (*exported.Authorization)(nil), diff --git a/x/authz/types/generic_authorization.go b/x/authz/types/generic_authorization.go index 101f3acf1664..809fd4d693aa 100644 --- a/x/authz/types/generic_authorization.go +++ b/x/authz/types/generic_authorization.go @@ -24,7 +24,7 @@ func (authorization GenericAuthorization) MethodName() string { } // Accept implements Authorization.Accept. -func (authorization GenericAuthorization) Accept(ctx sdk.Context, msg sdk.ServiceMsg) (updated exported.Authorization, delete bool, err error) { +func (authorization GenericAuthorization) Accept(ctx sdk.Context, msg sdk.Msg) (updated exported.Authorization, delete bool, err error) { return &authorization, false, nil } diff --git a/x/authz/types/msgs.go b/x/authz/types/msgs.go index 0b72d1a7e869..bdb38ccc915f 100644 --- a/x/authz/types/msgs.go +++ b/x/authz/types/msgs.go @@ -12,9 +12,9 @@ import ( ) var ( - _ sdk.MsgRequest = &MsgGrantAuthorizationRequest{} - _ sdk.MsgRequest = &MsgRevokeAuthorizationRequest{} - _ sdk.MsgRequest = &MsgExecAuthorizedRequest{} + _ sdk.Msg = &MsgGrantAuthorizationRequest{} + _ sdk.Msg = &MsgRevokeAuthorizationRequest{} + _ sdk.Msg = &MsgExecAuthorizedRequest{} _ types.UnpackInterfacesMessage = &MsgGrantAuthorizationRequest{} _ types.UnpackInterfacesMessage = &MsgExecAuthorizedRequest{} @@ -96,7 +96,7 @@ func (msg *MsgGrantAuthorizationRequest) SetAuthorization(authorization exported // UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces func (msg MsgExecAuthorizedRequest) UnpackInterfaces(unpacker types.AnyUnpacker) error { for _, x := range msg.Msgs { - var msgExecAuthorized sdk.MsgRequest + var msgExecAuthorized sdk.Msg err := unpacker.UnpackAny(x, &msgExecAuthorized) if err != nil { return err @@ -155,20 +155,15 @@ func (msg MsgRevokeAuthorizationRequest) ValidateBasic() error { // NewMsgExecAuthorized creates a new MsgExecAuthorized //nolint:interfacer -func NewMsgExecAuthorized(grantee sdk.AccAddress, msgs []sdk.ServiceMsg) MsgExecAuthorizedRequest { +func NewMsgExecAuthorized(grantee sdk.AccAddress, msgs []sdk.Msg) MsgExecAuthorizedRequest { msgsAny := make([]*types.Any, len(msgs)) for i, msg := range msgs { - bz, err := proto.Marshal(msg.Request) + any, err := types.NewAnyWithValue(msg) if err != nil { panic(err) } - anyMsg := &types.Any{ - TypeUrl: msg.MethodName, - Value: bz, - } - - msgsAny[i] = anyMsg + msgsAny[i] = any } return MsgExecAuthorizedRequest{ @@ -177,20 +172,15 @@ func NewMsgExecAuthorized(grantee sdk.AccAddress, msgs []sdk.ServiceMsg) MsgExec } } -// GetServiceMsgs returns the cache values from the MsgExecAuthorized.Msgs if present. -func (msg MsgExecAuthorizedRequest) GetServiceMsgs() ([]sdk.ServiceMsg, error) { - msgs := make([]sdk.ServiceMsg, len(msg.Msgs)) +// GetMessages returns the cache values from the MsgExecAuthorized.Msgs if present. +func (msg MsgExecAuthorizedRequest) GetMessages() ([]sdk.Msg, error) { + msgs := make([]sdk.Msg, len(msg.Msgs)) for i, msgAny := range msg.Msgs { - msgReq, ok := msgAny.GetCachedValue().(sdk.MsgRequest) + msg, ok := msgAny.GetCachedValue().(sdk.Msg) if !ok { return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "messages contains %T which is not a sdk.MsgRequest", msgAny) } - srvMsg := sdk.ServiceMsg{ - MethodName: msgAny.TypeUrl, - Request: msgReq, - } - - msgs[i] = srvMsg + msgs[i] = msg } return msgs, nil diff --git a/x/bank/types/send_authorization.go b/x/bank/types/send_authorization.go index 82d1bf1e630b..6202130e38b8 100644 --- a/x/bank/types/send_authorization.go +++ b/x/bank/types/send_authorization.go @@ -1,8 +1,6 @@ package types import ( - "reflect" - sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" authz "github.com/cosmos/cosmos-sdk/x/authz/exported" @@ -25,22 +23,21 @@ func (authorization SendAuthorization) MethodName() string { } // Accept implements Authorization.Accept. -func (authorization SendAuthorization) Accept(ctx sdk.Context, msg sdk.ServiceMsg) (updated authz.Authorization, delete bool, err error) { - if reflect.TypeOf(msg.Request) == reflect.TypeOf(&MsgSend{}) { - msg, ok := msg.Request.(*MsgSend) - if ok { - limitLeft, isNegative := authorization.SpendLimit.SafeSub(msg.Amount) - if isNegative { - return nil, false, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "requested amount is more than spend limit") - } - if limitLeft.IsZero() { - return nil, true, nil - } - - return &SendAuthorization{SpendLimit: limitLeft}, false, nil - } +func (authorization SendAuthorization) Accept(_ sdk.Context, msg sdk.Msg) (updated authz.Authorization, delete bool, err error) { + msgSend, ok := msg.(*MsgSend) + if !ok { + return nil, false, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "type mismatch") + } + + limitLeft, isNegative := authorization.SpendLimit.SafeSub(msgSend.Amount) + if isNegative { + return nil, false, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "requested amount is more than spend limit") } - return nil, false, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "type mismatch") + if limitLeft.IsZero() { + return nil, true, nil + } + + return &SendAuthorization{SpendLimit: limitLeft}, false, nil } // ValidateBasic implements Authorization.ValidateBasic. diff --git a/x/feegrant/simulation/operations.go b/x/feegrant/simulation/operations.go index 6e53738b2df6..798685fa81c2 100644 --- a/x/feegrant/simulation/operations.go +++ b/x/feegrant/simulation/operations.go @@ -5,6 +5,8 @@ import ( "math/rand" "time" + "github.com/gogo/protobuf/proto" + "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/simapp/helpers" @@ -121,7 +123,7 @@ func SimulateMsgGrantFeeAllowance(ak types.AccountKeeper, bk types.BankKeeper, k _, _, err = app.Deliver(txGen.TxEncoder(), tx) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, svcMsgClientConn.GetMsgs()[0].Type(), "unable to deliver tx"), nil, err + return simtypes.NoOpMsg(types.ModuleName, proto.MessageName(svcMsgClientConn.GetMsgs()[0]), "unable to deliver tx"), nil, err } return simtypes.NewOperationMsg(svcMsgClientConn.GetMsgs()[0], true, "", protoCdc), nil, err } diff --git a/x/feegrant/types/codec.go b/x/feegrant/types/codec.go index 5557d96ac3d2..80ff7525b2f1 100644 --- a/x/feegrant/types/codec.go +++ b/x/feegrant/types/codec.go @@ -2,17 +2,11 @@ package types import ( "github.com/cosmos/cosmos-sdk/codec/types" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/msgservice" ) // RegisterInterfaces registers the interfaces types with the interface registry func RegisterInterfaces(registry types.InterfaceRegistry) { - registry.RegisterImplementations((*sdk.MsgRequest)(nil), - &MsgGrantFeeAllowance{}, - &MsgRevokeFeeAllowance{}, - ) - registry.RegisterInterface( "cosmos.feegrant.v1beta1.FeeAllowanceI", (*FeeAllowanceI)(nil), diff --git a/x/feegrant/types/filtered_fee.go b/x/feegrant/types/filtered_fee.go index 99cf5f1731a9..07f9ce271caf 100644 --- a/x/feegrant/types/filtered_fee.go +++ b/x/feegrant/types/filtered_fee.go @@ -80,7 +80,7 @@ func (a *AllowedMsgFeeAllowance) allMsgTypesAllowed(ctx sdk.Context, msgs []sdk. for _, msg := range msgs { ctx.GasMeter().ConsumeGas(gasCostPerIteration, "check msg") - if !msgsMap[msg.Type()] { + if !msgsMap[proto.MessageName(msg)] { return false } } diff --git a/x/feegrant/types/msgs.go b/x/feegrant/types/msgs.go index 1f2be972d831..b71cbbc747dc 100644 --- a/x/feegrant/types/msgs.go +++ b/x/feegrant/types/msgs.go @@ -9,7 +9,7 @@ import ( ) var ( - _, _ sdk.MsgRequest = &MsgGrantFeeAllowance{}, &MsgRevokeFeeAllowance{} + _, _ sdk.Msg = &MsgGrantFeeAllowance{}, &MsgRevokeFeeAllowance{} _ types.UnpackInterfacesMessage = &MsgGrantFeeAllowance{} ) diff --git a/x/gov/types/msgs.go b/x/gov/types/msgs.go index 098aad437857..f1c351e6ddca 100644 --- a/x/gov/types/msgs.go +++ b/x/gov/types/msgs.go @@ -18,12 +18,6 @@ const ( TypeMsgVote = "vote" TypeMsgVoteWeighted = "weighted_vote" TypeMsgSubmitProposal = "submit_proposal" - - // These are used for querying events by action. - TypeSvcMsgDeposit = "/cosmos.gov.v1beta1.Msg/Deposit" - TypeSvcMsgVote = "/cosmos.gov.v1beta1.Msg/Vote" - TypeSvcMsgVoteWeighted = "/cosmos.gov.v1beta1.Msg/VoteWeighted" - TypeSvcMsgSubmitProposal = "/cosmos.gov.v1beta1.Msg/SubmitProposal" ) var ( diff --git a/x/staking/types/authz.go b/x/staking/types/authz.go index 32e8b719d1f2..e7cf7a92c5d1 100644 --- a/x/staking/types/authz.go +++ b/x/staking/types/authz.go @@ -61,11 +61,11 @@ func (authorization StakeAuthorization) ValidateBasic() error { } // Accept implements Authorization.Accept. -func (authorization StakeAuthorization) Accept(ctx sdk.Context, msg sdk.ServiceMsg) (updated authz.Authorization, delete bool, err error) { +func (authorization StakeAuthorization) Accept(ctx sdk.Context, msg sdk.Msg) (updated authz.Authorization, delete bool, err error) { var validatorAddress string var amount sdk.Coin - switch msg := msg.Request.(type) { + switch msg := msg.(type) { case *MsgDelegate: validatorAddress = msg.ValidatorAddress amount = msg.Amount From 559fc54f64501ed70d4f6cf488ae2f2cbf7b349c Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Mon, 19 Apr 2021 11:44:28 +0200 Subject: [PATCH 05/47] Fix msg_service_router --- baseapp/msg_service_router.go | 2 +- baseapp/msg_service_router_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/baseapp/msg_service_router.go b/baseapp/msg_service_router.go index c3ad010e01d3..fb2582e7011f 100644 --- a/baseapp/msg_service_router.go +++ b/baseapp/msg_service_router.go @@ -33,7 +33,7 @@ type MsgServiceHandler = func(ctx sdk.Context, req sdk.Msg) (*sdk.Result, error) // Handler returns the MsgServiceHandler for a given msg or nil if not found. func (msr *MsgServiceRouter) Handler(msg sdk.Msg) MsgServiceHandler { - return msr.routes[proto.MessageName(msg)] + return msr.routes[fmt.Sprintf("/%s", proto.MessageName(msg))] } // HandlerByName returns the MsgServiceHandler for a given query route path or nil diff --git a/baseapp/msg_service_router_test.go b/baseapp/msg_service_router_test.go index 34f9c080277e..d599d0cbe47a 100644 --- a/baseapp/msg_service_router_test.go +++ b/baseapp/msg_service_router_test.go @@ -80,11 +80,11 @@ func TestMsgService(t *testing.T) { ) _ = app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: 1}}) - msg := testdata.NewServiceMsgCreateDog(&testdata.MsgCreateDog{Dog: &testdata.Dog{Name: "Spot"}}) + msg := testdata.MsgCreateDog{Dog: &testdata.Dog{Name: "Spot"}} txBuilder := encCfg.TxConfig.NewTxBuilder() txBuilder.SetFeeAmount(testdata.NewTestFeeAmount()) txBuilder.SetGasLimit(testdata.NewTestGasLimit()) - err := txBuilder.SetMsgs(msg) + err := txBuilder.SetMsgs(&msg) require.NoError(t, err) // First round: we gather all the signer infos. We use the "set empty From f2d83558f269802785c463b6a2f32d452a85e664 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Mon, 19 Apr 2021 11:53:47 +0200 Subject: [PATCH 06/47] Fix gov client queries --- x/gov/client/utils/query.go | 128 +++++-------------------------- x/gov/client/utils/query_test.go | 2 +- 2 files changed, 22 insertions(+), 108 deletions(-) diff --git a/x/gov/client/utils/query.go b/x/gov/client/utils/query.go index 9b103e3468c9..67b97444a06a 100644 --- a/x/gov/client/utils/query.go +++ b/x/gov/client/utils/query.go @@ -39,16 +39,10 @@ func (p Proposer) String() string { func QueryDepositsByTxQuery(clientCtx client.Context, params types.QueryProposalParams) ([]byte, error) { searchResult, err := combineEvents( clientCtx, defaultPage, - // Query old Msgs []string{ fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgDeposit), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalDeposit, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), }, - // Query service Msgs - []string{ - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeSvcMsgDeposit), - fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalDeposit, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), - }, ) if err != nil { return nil, err @@ -58,14 +52,7 @@ func QueryDepositsByTxQuery(clientCtx client.Context, params types.QueryProposal for _, info := range searchResult.Txs { for _, msg := range info.GetTx().GetMsgs() { - var depMsg *types.MsgDeposit - if msg.Type() == types.TypeSvcMsgDeposit { - depMsg = msg.(sdk.ServiceMsg).Request.(*types.MsgDeposit) - } else if protoDepMsg, ok := msg.(*types.MsgDeposit); ok { - depMsg = protoDepMsg - } - - if depMsg != nil { + if depMsg, ok := msg.(*types.MsgDeposit); ok { deposits = append(deposits, types.Deposit{ Depositor: depMsg.Depositor, ProposalId: params.ProposalID, @@ -95,29 +82,19 @@ func QueryVotesByTxQuery(clientCtx client.Context, params types.QueryProposalVot // query interrupted either if we collected enough votes or tx indexer run out of relevant txs for len(votes) < totalLimit { - // Search for both (old) votes and weighted votes. + // Search for both votes and weighted votes. searchResult, err := combineEvents( clientCtx, nextTxPage, - // Query old Vote Msgs + // Query Vote Msgs []string{ fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgVote), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), }, - // Query Vote service Msgs - []string{ - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeSvcMsgVote), - fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), - }, - // Query old VoteWeighted Msgs + // Query VoteWeighted Msgs []string{ fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgVoteWeighted), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), }, - // Query VoteWeighted service Msgs - []string{ - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeSvcMsgVoteWeighted), - fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), - }, ) if err != nil { return nil, err @@ -125,14 +102,7 @@ func QueryVotesByTxQuery(clientCtx client.Context, params types.QueryProposalVot for _, info := range searchResult.Txs { for _, msg := range info.GetTx().GetMsgs() { - var voteMsg *types.MsgVote - if msg.Type() == types.TypeSvcMsgVote { - voteMsg = msg.(sdk.ServiceMsg).Request.(*types.MsgVote) - } else if protoVoteMsg, ok := msg.(*types.MsgVote); ok { - voteMsg = protoVoteMsg - } - - if voteMsg != nil { + if voteMsg, ok := msg.(*types.MsgVote); ok { votes = append(votes, types.Vote{ Voter: voteMsg.Voter, ProposalId: params.ProposalID, @@ -140,14 +110,7 @@ func QueryVotesByTxQuery(clientCtx client.Context, params types.QueryProposalVot }) } - var voteWeightedMsg *types.MsgVoteWeighted - if msg.Type() == types.TypeSvcMsgVoteWeighted { - voteWeightedMsg = msg.(sdk.ServiceMsg).Request.(*types.MsgVoteWeighted) - } else if protoVoteWeightedMsg, ok := msg.(*types.MsgVoteWeighted); ok { - voteWeightedMsg = protoVoteWeightedMsg - } - - if voteWeightedMsg != nil { + if voteWeightedMsg, ok := msg.(*types.MsgVoteWeighted); ok { votes = append(votes, types.Vote{ Voter: voteWeightedMsg.Voter, ProposalId: params.ProposalID, @@ -181,30 +144,18 @@ func QueryVotesByTxQuery(clientCtx client.Context, params types.QueryProposalVot func QueryVoteByTxQuery(clientCtx client.Context, params types.QueryVoteParams) ([]byte, error) { searchResult, err := combineEvents( clientCtx, defaultPage, - // Query old Vote Msgs + // Query Vote Msgs []string{ fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgVote), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, []byte(params.Voter.String())), }, - // Query Vote service Msgs - []string{ - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeSvcMsgVote), - fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, []byte(params.Voter.String())), - }, - // Query old VoteWeighted Msgs + // Query VoteWeighted Msgs []string{ fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgVoteWeighted), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, []byte(params.Voter.String())), }, - // Query VoteWeighted service Msgs - []string{ - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeSvcMsgVoteWeighted), - fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, []byte(params.Voter.String())), - }, ) if err != nil { return nil, err @@ -212,37 +163,26 @@ func QueryVoteByTxQuery(clientCtx client.Context, params types.QueryVoteParams) for _, info := range searchResult.Txs { for _, msg := range info.GetTx().GetMsgs() { - var voteMsg *types.MsgVote // there should only be a single vote under the given conditions - if msg.Type() == types.TypeSvcMsgVote { - voteMsg = msg.(sdk.ServiceMsg).Request.(*types.MsgVote) - } else if protoVoteMsg, ok := msg.(*types.MsgVote); ok { - voteMsg = protoVoteMsg - } - - if voteMsg != nil { - vote := types.Vote{ + var vote *types.Vote + if voteMsg, ok := msg.(*types.MsgVote); ok { + vote = &types.Vote{ Voter: voteMsg.Voter, ProposalId: params.ProposalID, Options: types.NewNonSplitVoteOption(voteMsg.Option), } + } - bz, err := clientCtx.JSONMarshaler.MarshalJSON(&vote) - if err != nil { - return nil, err - } - - return bz, nil - } else if msg.Type() == types.TypeMsgVoteWeighted { - voteMsg := msg.(*types.MsgVoteWeighted) - - vote := types.Vote{ - Voter: voteMsg.Voter, + if voteWeightedMsg, ok := msg.(*types.MsgVoteWeighted); ok { + vote = &types.Vote{ + Voter: voteWeightedMsg.Voter, ProposalId: params.ProposalID, - Options: voteMsg.Options, + Options: voteWeightedMsg.Options, } + } - bz, err := clientCtx.JSONMarshaler.MarshalJSON(&vote) + if vote != nil { + bz, err := clientCtx.JSONMarshaler.MarshalJSON(vote) if err != nil { return nil, err } @@ -260,18 +200,11 @@ func QueryVoteByTxQuery(clientCtx client.Context, params types.QueryVoteParams) func QueryDepositByTxQuery(clientCtx client.Context, params types.QueryDepositParams) ([]byte, error) { searchResult, err := combineEvents( clientCtx, defaultPage, - // Query old Msgs []string{ fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgDeposit), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalDeposit, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, []byte(params.Depositor.String())), }, - // Query service Msgs - []string{ - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeSvcMsgDeposit), - fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalDeposit, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, []byte(params.Depositor.String())), - }, ) if err != nil { return nil, err @@ -279,15 +212,8 @@ func QueryDepositByTxQuery(clientCtx client.Context, params types.QueryDepositPa for _, info := range searchResult.Txs { for _, msg := range info.GetTx().GetMsgs() { - var depMsg *types.MsgDeposit // there should only be a single deposit under the given conditions - if msg.Type() == types.TypeSvcMsgDeposit { - depMsg = msg.(sdk.ServiceMsg).Request.(*types.MsgDeposit) - } else if protoDepMsg, ok := msg.(*types.MsgDeposit); ok { - depMsg = protoDepMsg - } - - if depMsg != nil { + if depMsg, ok := msg.(*types.MsgDeposit); ok { deposit := types.Deposit{ Depositor: depMsg.Depositor, ProposalId: params.ProposalID, @@ -313,16 +239,10 @@ func QueryProposerByTxQuery(clientCtx client.Context, proposalID uint64) (Propos searchResult, err := combineEvents( clientCtx, defaultPage, - // Query old Msgs []string{ fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgSubmitProposal), fmt.Sprintf("%s.%s='%s'", types.EventTypeSubmitProposal, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", proposalID))), }, - // Query service Msgs - []string{ - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeSvcMsgSubmitProposal), - fmt.Sprintf("%s.%s='%s'", types.EventTypeSubmitProposal, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", proposalID))), - }, ) if err != nil { return Proposer{}, err @@ -331,13 +251,7 @@ func QueryProposerByTxQuery(clientCtx client.Context, proposalID uint64) (Propos for _, info := range searchResult.Txs { for _, msg := range info.GetTx().GetMsgs() { // there should only be a single proposal under the given conditions - if msg.Type() == types.TypeSvcMsgSubmitProposal { - subMsg := msg.(sdk.ServiceMsg).Request.(*types.MsgSubmitProposal) - - return NewProposer(proposalID, subMsg.Proposer), nil - } else if protoSubMsg, ok := msg.(*types.MsgSubmitProposal); ok { - subMsg := protoSubMsg - + if subMsg, ok := msg.(*types.MsgSubmitProposal); ok { return NewProposer(proposalID, subMsg.Proposer), nil } } diff --git a/x/gov/client/utils/query_test.go b/x/gov/client/utils/query_test.go index fb7d002c8583..8649f9878307 100644 --- a/x/gov/client/utils/query_test.go +++ b/x/gov/client/utils/query_test.go @@ -44,7 +44,7 @@ func (mock TxSearchMock) TxSearch(ctx context.Context, query string, prove bool, return nil, err } for _, msg := range sdkTx.GetMsgs() { - if msg.Type() == msgType { + if msg.(sdk.LegacyMsg).Type() == msgType { matchingTxs = append(matchingTxs, tx) break } From d5c6ce9211684c37ef9408c764ba47eaca8f2ad7 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Mon, 19 Apr 2021 12:18:17 +0200 Subject: [PATCH 07/47] Fix some modules tests --- x/authz/types/msgs_test.go | 18 ++++---- x/bank/client/cli/cli_test.go | 5 +-- x/bank/types/send_authorization_test.go | 15 ++----- x/evidence/types/msgs_test.go | 4 +- x/genutil/client/cli/gentx_test.go | 2 +- x/staking/types/authz_test.go | 56 +++++++------------------ 6 files changed, 32 insertions(+), 68 deletions(-) diff --git a/x/authz/types/msgs_test.go b/x/authz/types/msgs_test.go index 8c1b20ca6e6b..9062cc9b0967 100644 --- a/x/authz/types/msgs_test.go +++ b/x/authz/types/msgs_test.go @@ -23,18 +23,16 @@ func TestMsgExecAuthorized(t *testing.T) { tests := []struct { title string grantee sdk.AccAddress - msgs []sdk.ServiceMsg + msgs []sdk.Msg expectPass bool }{ - {"nil grantee address", nil, []sdk.ServiceMsg{}, false}, - {"zero-messages test: should fail", grantee, []sdk.ServiceMsg{}, false}, - {"valid test: msg type", grantee, []sdk.ServiceMsg{ - { - Request: &banktypes.MsgSend{ - Amount: sdk.NewCoins(sdk.NewInt64Coin("steak", 2)), - FromAddress: granter.String(), - ToAddress: grantee.String(), - }, + {"nil grantee address", nil, []sdk.Msg{}, false}, + {"zero-messages test: should fail", grantee, []sdk.Msg{}, false}, + {"valid test: msg type", grantee, []sdk.Msg{ + &banktypes.MsgSend{ + Amount: sdk.NewCoins(sdk.NewInt64Coin("steak", 2)), + FromAddress: granter.String(), + ToAddress: grantee.String(), }, }, true}, } diff --git a/x/bank/client/cli/cli_test.go b/x/bank/client/cli/cli_test.go index 60b8c504b57d..a431908f4bd7 100644 --- a/x/bank/client/cli/cli_test.go +++ b/x/bank/client/cli/cli_test.go @@ -384,10 +384,7 @@ func (s *IntegrationTestSuite) TestNewSendTxCmdGenOnly() { s.Require().NoError(err) tx, err := s.cfg.TxConfig.TxJSONDecoder()(bz.Bytes()) s.Require().NoError(err) - s.Require().Equal([]sdk.Msg{sdk.ServiceMsg{ - MethodName: "/cosmos.bank.v1beta1.Msg/Send", - Request: types.NewMsgSend(from, to, amount), - }}, tx.GetMsgs()) + s.Require().Equal([]sdk.Msg{types.NewMsgSend(from, to, amount)}, tx.GetMsgs()) } func (s *IntegrationTestSuite) TestNewSendTxCmd() { diff --git a/x/bank/types/send_authorization_test.go b/x/bank/types/send_authorization_test.go index 055a0b1fb0aa..1c1522b8dd6d 100644 --- a/x/bank/types/send_authorization_test.go +++ b/x/bank/types/send_authorization_test.go @@ -26,14 +26,11 @@ func TestSendAuthorization(t *testing.T) { require.Equal(t, authorization.MethodName(), "/cosmos.bank.v1beta1.Msg/Send") require.NoError(t, authorization.ValidateBasic()) send := types.NewMsgSend(fromAddr, toAddr, coins1000) - srvMsg := sdk.ServiceMsg{ - MethodName: "/cosmos.bank.v1beta1.Msg/Send", - Request: send, - } + require.NoError(t, authorization.ValidateBasic()) t.Log("verify updated authorization returns nil") - updated, del, err := authorization.Accept(ctx, srvMsg) + updated, del, err := authorization.Accept(ctx, send) require.NoError(t, err) require.True(t, del) require.Nil(t, updated) @@ -42,12 +39,8 @@ func TestSendAuthorization(t *testing.T) { require.Equal(t, authorization.MethodName(), "/cosmos.bank.v1beta1.Msg/Send") require.NoError(t, authorization.ValidateBasic()) send = types.NewMsgSend(fromAddr, toAddr, coins500) - srvMsg = sdk.ServiceMsg{ - MethodName: "/cosmos.bank.v1beta1.Msg/Send", - Request: send, - } require.NoError(t, authorization.ValidateBasic()) - updated, del, err = authorization.Accept(ctx, srvMsg) + updated, del, err = authorization.Accept(ctx, send) t.Log("verify updated authorization returns remaining spent limit") require.NoError(t, err) @@ -57,7 +50,7 @@ func TestSendAuthorization(t *testing.T) { require.Equal(t, sendAuth.String(), updated.String()) t.Log("expect updated authorization nil after spending remaining amount") - updated, del, err = updated.Accept(ctx, srvMsg) + updated, del, err = updated.Accept(ctx, send) require.NoError(t, err) require.True(t, del) require.Nil(t, updated) diff --git a/x/evidence/types/msgs_test.go b/x/evidence/types/msgs_test.go index 4248b0dfba94..6f5d6ab522c4 100644 --- a/x/evidence/types/msgs_test.go +++ b/x/evidence/types/msgs_test.go @@ -50,8 +50,8 @@ func TestMsgSubmitEvidence(t *testing.T) { } for i, tc := range testCases { - require.Equal(t, tc.msg.Route(), types.RouterKey, "unexpected result for tc #%d", i) - require.Equal(t, tc.msg.Type(), types.TypeMsgSubmitEvidence, "unexpected result for tc #%d", i) + require.Equal(t, tc.msg.(sdk.LegacyMsg).Route(), types.RouterKey, "unexpected result for tc #%d", i) + require.Equal(t, tc.msg.(sdk.LegacyMsg).Type(), types.TypeMsgSubmitEvidence, "unexpected result for tc #%d", i) require.Equal(t, tc.expectErr, tc.msg.ValidateBasic() != nil, "unexpected result for tc #%d", i) if !tc.expectErr { diff --git a/x/genutil/client/cli/gentx_test.go b/x/genutil/client/cli/gentx_test.go index b5733675951a..6f1be047472b 100644 --- a/x/genutil/client/cli/gentx_test.go +++ b/x/genutil/client/cli/gentx_test.go @@ -85,7 +85,7 @@ func (s *IntegrationTestSuite) TestGenTxCmd() { msgs := tx.GetMsgs() s.Require().Len(msgs, 1) - s.Require().Equal(types.TypeMsgCreateValidator, msgs[0].Type()) + s.Require().Equal(types.TypeMsgCreateValidator, msgs[0].(sdk.LegacyMsg).Type()) s.Require().Equal([]sdk.AccAddress{val.Address}, msgs[0].GetSigners()) s.Require().Equal(amount, msgs[0].(*types.MsgCreateValidator).Value) err = tx.ValidateBasic() diff --git a/x/staking/types/authz_test.go b/x/staking/types/authz_test.go index c43ef9e1fb48..884fb9bb5ca6 100644 --- a/x/staking/types/authz_test.go +++ b/x/staking/types/authz_test.go @@ -55,7 +55,7 @@ func TestAuthzAuthorizations(t *testing.T) { denied []sdk.ValAddress msgType stakingtypes.AuthorizationType limit *sdk.Coin - srvMsg sdk.ServiceMsg + srvMsg sdk.Msg expectErr bool isDelete bool updatedAuthorization *stakingtypes.StakeAuthorization @@ -66,7 +66,7 @@ func TestAuthzAuthorizations(t *testing.T) { []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE, &coin100, - createSrvMsgDelegate(delAuth.MethodName(), delAddr, val1, coin100), + stakingtypes.NewMsgDelegate(delAddr, val1, coin100), false, true, nil, @@ -77,7 +77,7 @@ func TestAuthzAuthorizations(t *testing.T) { []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE, &coin100, - createSrvMsgDelegate(delAuth.MethodName(), delAddr, val1, coin50), + stakingtypes.NewMsgDelegate(delAddr, val1, coin50), false, false, &stakingtypes.StakeAuthorization{ @@ -91,7 +91,7 @@ func TestAuthzAuthorizations(t *testing.T) { []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE, &coin100, - createSrvMsgDelegate(delAuth.MethodName(), delAddr, val3, coin100), + stakingtypes.NewMsgDelegate(delAddr, val3, coin100), true, false, nil, @@ -102,7 +102,7 @@ func TestAuthzAuthorizations(t *testing.T) { []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE, nil, - createSrvMsgDelegate(delAuth.MethodName(), delAddr, val2, coin100), + stakingtypes.NewMsgDelegate(delAddr, val2, coin100), false, false, &stakingtypes.StakeAuthorization{ @@ -116,7 +116,7 @@ func TestAuthzAuthorizations(t *testing.T) { []sdk.ValAddress{val1}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE, nil, - createSrvMsgDelegate(delAuth.MethodName(), delAddr, val1, coin100), + stakingtypes.NewMsgDelegate(delAddr, val1, coin100), true, false, nil, @@ -128,7 +128,7 @@ func TestAuthzAuthorizations(t *testing.T) { []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_UNDELEGATE, &coin100, - createSrvMsgUndelegate(undelAuth.MethodName(), delAddr, val1, coin100), + stakingtypes.NewMsgUndelegate(delAddr, val1, coin100), false, true, nil, @@ -139,7 +139,7 @@ func TestAuthzAuthorizations(t *testing.T) { []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_UNDELEGATE, &coin100, - createSrvMsgUndelegate(undelAuth.MethodName(), delAddr, val1, coin50), + stakingtypes.NewMsgUndelegate(delAddr, val1, coin50), false, false, &stakingtypes.StakeAuthorization{ @@ -153,7 +153,7 @@ func TestAuthzAuthorizations(t *testing.T) { []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_UNDELEGATE, &coin100, - createSrvMsgUndelegate(undelAuth.MethodName(), delAddr, val3, coin100), + stakingtypes.NewMsgUndelegate(delAddr, val3, coin100), true, false, nil, @@ -164,7 +164,7 @@ func TestAuthzAuthorizations(t *testing.T) { []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_UNDELEGATE, nil, - createSrvMsgUndelegate(undelAuth.MethodName(), delAddr, val2, coin100), + stakingtypes.NewMsgUndelegate(delAddr, val2, coin100), false, false, &stakingtypes.StakeAuthorization{ @@ -178,7 +178,7 @@ func TestAuthzAuthorizations(t *testing.T) { []sdk.ValAddress{val1}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_UNDELEGATE, &coin100, - createSrvMsgUndelegate(undelAuth.MethodName(), delAddr, val1, coin100), + stakingtypes.NewMsgUndelegate(delAddr, val1, coin100), true, false, nil, @@ -190,7 +190,7 @@ func TestAuthzAuthorizations(t *testing.T) { []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_REDELEGATE, &coin100, - createSrvMsgUndelegate(undelAuth.MethodName(), delAddr, val1, coin100), + stakingtypes.NewMsgUndelegate(delAddr, val1, coin100), false, true, nil, @@ -201,7 +201,7 @@ func TestAuthzAuthorizations(t *testing.T) { []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_REDELEGATE, &coin100, - createSrvMsgReDelegate(undelAuth.MethodName(), delAddr, val1, coin50), + stakingtypes.NewMsgBeginRedelegate(delAddr, val1, val1, coin50), false, false, &stakingtypes.StakeAuthorization{ @@ -215,7 +215,7 @@ func TestAuthzAuthorizations(t *testing.T) { []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_REDELEGATE, &coin100, - createSrvMsgReDelegate(undelAuth.MethodName(), delAddr, val3, coin100), + stakingtypes.NewMsgBeginRedelegate(delAddr, val3, val3, coin100), true, false, nil, @@ -226,7 +226,7 @@ func TestAuthzAuthorizations(t *testing.T) { []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_REDELEGATE, nil, - createSrvMsgReDelegate(undelAuth.MethodName(), delAddr, val2, coin100), + stakingtypes.NewMsgBeginRedelegate(delAddr, val2, val2, coin100), false, false, &stakingtypes.StakeAuthorization{ @@ -240,7 +240,7 @@ func TestAuthzAuthorizations(t *testing.T) { []sdk.ValAddress{val1}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_REDELEGATE, &coin100, - createSrvMsgReDelegate(undelAuth.MethodName(), delAddr, val1, coin100), + stakingtypes.NewMsgBeginRedelegate(delAddr, val1, val1, coin100), true, false, nil, @@ -266,27 +266,3 @@ func TestAuthzAuthorizations(t *testing.T) { }) } } - -func createSrvMsgUndelegate(methodName string, delAddr sdk.AccAddress, valAddr sdk.ValAddress, amount sdk.Coin) sdk.ServiceMsg { - msg := stakingtypes.NewMsgUndelegate(delAddr, valAddr, amount) - return sdk.ServiceMsg{ - MethodName: methodName, - Request: msg, - } -} - -func createSrvMsgReDelegate(methodName string, delAddr sdk.AccAddress, valAddr sdk.ValAddress, amount sdk.Coin) sdk.ServiceMsg { - msg := stakingtypes.NewMsgBeginRedelegate(delAddr, valAddr, valAddr, amount) - return sdk.ServiceMsg{ - MethodName: methodName, - Request: msg, - } -} - -func createSrvMsgDelegate(methodName string, delAddr sdk.AccAddress, valAddr sdk.ValAddress, amount sdk.Coin) sdk.ServiceMsg { - msg := stakingtypes.NewMsgDelegate(delAddr, valAddr, amount) - return sdk.ServiceMsg{ - MethodName: methodName, - Request: msg, - } -} From 569f7770fd41e338eaaf573a68ddbfd5637f1b42 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Mon, 19 Apr 2021 12:54:28 +0200 Subject: [PATCH 08/47] Remove all instances of "*.Msg/*" --- codec/types/interface_registry.go | 20 -- codec/types/types_test.go | 62 ----- types/msgservice/msg_service.go | 8 - types/tx/types.go | 19 +- x/auth/client/cli/cli_test.go | 8 +- x/auth/tx/service_test.go | 20 +- x/authz/client/cli/tx.go | 2 +- x/authz/client/cli/tx_test.go | 30 +- x/authz/client/rest/grpc_query_test.go | 4 +- x/authz/keeper/keeper_test.go | 35 +-- x/authz/simulation/operations.go | 36 +-- x/authz/types/generic_authorization.go | 5 - x/bank/types/send_authorization.go | 4 +- x/bank/types/send_authorization_test.go | 4 +- x/feegrant/client/cli/tx.go | 2 +- x/feegrant/module.go | 9 +- x/feegrant/simulation/operations.go | 7 +- x/feegrant/simulation/operations_test.go | 334 +++++++++++------------ x/staking/client/rest/query.go | 8 +- x/staking/types/authz.go | 11 +- x/staking/types/authz_test.go | 10 +- x/staking/types/msg.go | 7 - 22 files changed, 258 insertions(+), 387 deletions(-) diff --git a/codec/types/interface_registry.go b/codec/types/interface_registry.go index 0f9eb760beca..5c345d347cdc 100644 --- a/codec/types/interface_registry.go +++ b/codec/types/interface_registry.go @@ -46,17 +46,6 @@ type InterfaceRegistry interface { // registry.RegisterImplementations((*sdk.Msg)(nil), &MsgSend{}, &MsgMultiSend{}) RegisterImplementations(iface interface{}, impls ...proto.Message) - // RegisterCustomTypeURL allows a protobuf message to be registered as a - // google.protobuf.Any with a custom typeURL (besides its own canonical - // typeURL). iface should be an interface as type, as in RegisterInterface - // and RegisterImplementations. - // - // Ex: - // This will allow us to pack service methods in Any's using the full method name - // as the type URL and the request body as the value, and allow us to unpack - // such packed methods using the normal UnpackAny method for the interface iface. - RegisterCustomTypeURL(iface interface{}, typeURL string, impl proto.Message) - // ListAllInterfaces list the type URLs of all registered interfaces. ListAllInterfaces() []string @@ -127,15 +116,6 @@ func (registry *interfaceRegistry) RegisterImplementations(iface interface{}, im } } -// RegisterCustomTypeURL registers a concrete type which implements the given -// interface under `typeURL`. -// -// This function PANICs if different concrete types are registered under the -// same typeURL. -func (registry *interfaceRegistry) RegisterCustomTypeURL(iface interface{}, typeURL string, impl proto.Message) { - registry.registerImpl(iface, typeURL, impl) -} - // registerImpl registers a concrete type which implements the given // interface under `typeURL`. // diff --git a/codec/types/types_test.go b/codec/types/types_test.go index 43f85375508e..24c83b4eb5f9 100644 --- a/codec/types/types_test.go +++ b/codec/types/types_test.go @@ -1,18 +1,13 @@ package types_test import ( - "context" - "fmt" "strings" "testing" - "github.com/gogo/protobuf/grpc" "github.com/gogo/protobuf/jsonpb" "github.com/gogo/protobuf/proto" "github.com/stretchr/testify/require" - grpc2 "google.golang.org/grpc" - "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/testutil/testdata" ) @@ -185,60 +180,3 @@ func TestAny_ProtoJSON(t *testing.T) { require.NoError(t, err) require.Equal(t, spot, ha2.Animal.GetCachedValue()) } - -// this instance of grpc.ClientConn is used to test packing service method -// requests into Any's -type testAnyPackClient struct { - any types.Any - interfaceRegistry types.InterfaceRegistry -} - -var _ grpc.ClientConn = &testAnyPackClient{} - -func (t *testAnyPackClient) Invoke(_ context.Context, method string, args, _ interface{}, _ ...grpc2.CallOption) error { - reqMsg, ok := args.(proto.Message) - if !ok { - return fmt.Errorf("can't proto marshal %T", args) - } - - // registry the method request type with the interface registry - t.interfaceRegistry.RegisterCustomTypeURL((*interface{})(nil), method, reqMsg) - - bz, err := proto.Marshal(reqMsg) - if err != nil { - return err - } - - t.any.TypeUrl = method - t.any.Value = bz - - return nil -} - -func (t *testAnyPackClient) NewStream(context.Context, *grpc2.StreamDesc, string, ...grpc2.CallOption) (grpc2.ClientStream, error) { - return nil, fmt.Errorf("not supported") -} - -func TestAny_ServiceRequestProtoJSON(t *testing.T) { - interfaceRegistry := types.NewInterfaceRegistry() - anyPacker := &testAnyPackClient{interfaceRegistry: interfaceRegistry} - dogMsgClient := testdata.NewMsgClient(anyPacker) - _, err := dogMsgClient.CreateDog(context.Background(), &testdata.MsgCreateDog{Dog: &testdata.Dog{ - Name: "spot", - }}) - require.NoError(t, err) - - // marshal JSON - cdc := codec.NewProtoCodec(interfaceRegistry) - bz, err := cdc.MarshalJSON(&anyPacker.any) - require.NoError(t, err) - require.Equal(t, - `{"@type":"/testdata.Msg/CreateDog","dog":{"size":"","name":"spot"}}`, - string(bz)) - - // unmarshal JSON - var any2 types.Any - err = cdc.UnmarshalJSON(bz, &any2) - require.NoError(t, err) - require.Equal(t, anyPacker.any, any2) -} diff --git a/types/msgservice/msg_service.go b/types/msgservice/msg_service.go index b2316a6424a2..694a27b03f40 100644 --- a/types/msgservice/msg_service.go +++ b/types/msgservice/msg_service.go @@ -3,7 +3,6 @@ package msgservice import ( "context" "fmt" - "strings" "google.golang.org/grpc" @@ -32,7 +31,6 @@ func RegisterMsgServiceDesc(registry codectypes.InterfaceRegistry, sd *grpc.Serv } registry.RegisterImplementations((*sdk.Msg)(nil), msg) - registry.RegisterCustomTypeURL((*sdk.Msg)(nil), fqMethod, msg) return nil }, noopInterceptor) @@ -43,9 +41,3 @@ func RegisterMsgServiceDesc(registry codectypes.InterfaceRegistry, sd *grpc.Serv func noopInterceptor(_ context.Context, _ interface{}, _ *grpc.UnaryServerInfo, _ grpc.UnaryHandler) (interface{}, error) { return nil, nil } - -// IsServiceMsg checks if a type URL corresponds to a service method name, -// i.e. /cosmos.bank.Msg/Send vs /cosmos.bank.MsgSend -func IsServiceMsg(typeURL string) bool { - return strings.Count(typeURL, "/") >= 2 -} diff --git a/types/tx/types.go b/types/tx/types.go index bdd8aa1b5326..9fe923aa20cb 100644 --- a/types/tx/types.go +++ b/types/tx/types.go @@ -7,7 +7,6 @@ import ( cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/types/msgservice" ) // MaxGasWanted defines the max gas allowed. @@ -168,20 +167,10 @@ func (t *Tx) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { // UnpackInterfaces implements the UnpackInterfaceMessages.UnpackInterfaces method func (m *TxBody) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { for _, any := range m.Messages { - // If the any's typeUrl contains 2 slashes, then we unpack the any into - // a ServiceMsg struct as per ADR-031. - if msgservice.IsServiceMsg(any.TypeUrl) { - var req sdk.Msg - err := unpacker.UnpackAny(any, &req) - if err != nil { - return err - } - } else { - var msg sdk.Msg - err := unpacker.UnpackAny(any, &msg) - if err != nil { - return err - } + var msg sdk.Msg + err := unpacker.UnpackAny(any, &msg) + if err != nil { + return err } } diff --git a/x/auth/client/cli/cli_test.go b/x/auth/client/cli/cli_test.go index 15f95d92b163..a0ed1572ab1c 100644 --- a/x/auth/client/cli/cli_test.go +++ b/x/auth/client/cli/cli_test.go @@ -1,5 +1,3 @@ -// +build norace - package cli_test import ( @@ -293,16 +291,16 @@ func (s *IntegrationTestSuite) TestCLIQueryTxCmd() { "", }, { - "happy case (legacy Msg)", + "happy case (sdk.LegacyMsg)", []string{legacyTxRes.TxResponse.TxHash, fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, false, "", }, { - "happy case (service Msg)", + "happy case (sdk.Msg)", []string{txRes.TxHash, fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, false, - "/cosmos.bank.v1beta1.Msg/Send", + "/cosmos.bank.v1beta1.MsgSend", }, } diff --git a/x/auth/tx/service_test.go b/x/auth/tx/service_test.go index 68c474facc36..49f2d2828776 100644 --- a/x/auth/tx/service_test.go +++ b/x/auth/tx/service_test.go @@ -192,7 +192,7 @@ func (s IntegrationTestSuite) TestGetTxEvents_GRPC() { { "request with order-by", &tx.GetTxsEventRequest{ - Events: []string{"message.action='/cosmos.bank.v1beta1.Msg/Send'"}, + Events: []string{"message.action='/cosmos.bank.v1beta1.MsgSend'"}, OrderBy: tx.OrderBy_ORDER_BY_ASC, }, false, "", @@ -200,14 +200,14 @@ func (s IntegrationTestSuite) TestGetTxEvents_GRPC() { { "without pagination", &tx.GetTxsEventRequest{ - Events: []string{"message.action='/cosmos.bank.v1beta1.Msg/Send'"}, + Events: []string{"message.action='/cosmos.bank.v1beta1.MsgSend'"}, }, false, "", }, { "with pagination", &tx.GetTxsEventRequest{ - Events: []string{"message.action='/cosmos.bank.v1beta1.Msg/Send'"}, + Events: []string{"message.action='/cosmos.bank.v1beta1.MsgSend'"}, Pagination: &query.PageRequest{ CountTotal: false, Offset: 0, @@ -219,7 +219,7 @@ func (s IntegrationTestSuite) TestGetTxEvents_GRPC() { { "with multi events", &tx.GetTxsEventRequest{ - Events: []string{"message.action='/cosmos.bank.v1beta1.Msg/Send'", "message.module='bank'"}, + Events: []string{"message.action='/cosmos.bank.v1beta1.MsgSend'", "message.module='bank'"}, }, false, "", }, @@ -262,37 +262,37 @@ func (s IntegrationTestSuite) TestGetTxEvents_GRPCGateway() { }, { "without pagination", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s", val.APIAddress, "message.action='/cosmos.bank.v1beta1.Msg/Send'"), + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s", val.APIAddress, "message.action='/cosmos.bank.v1beta1.MsgSend'"), false, "", }, { "with pagination", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&pagination.offset=%d&pagination.limit=%d", val.APIAddress, "message.action='/cosmos.bank.v1beta1.Msg/Send'", 0, 10), + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&pagination.offset=%d&pagination.limit=%d", val.APIAddress, "message.action='/cosmos.bank.v1beta1.MsgSend'", 0, 10), false, "", }, { "valid request: order by asc", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s&order_by=ORDER_BY_ASC", val.APIAddress, "message.action='/cosmos.bank.v1beta1.Msg/Send'", "message.module='bank'"), + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s&order_by=ORDER_BY_ASC", val.APIAddress, "message.action='/cosmos.bank.v1beta1.MsgSend'", "message.module='bank'"), false, "", }, { "valid request: order by desc", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s&order_by=ORDER_BY_DESC", val.APIAddress, "message.action='/cosmos.bank.v1beta1.Msg/Send'", "message.module='bank'"), + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s&order_by=ORDER_BY_DESC", val.APIAddress, "message.action='/cosmos.bank.v1beta1.MsgSend'", "message.module='bank'"), false, "", }, { "invalid request: invalid order by", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s&order_by=invalid_order", val.APIAddress, "message.action='/cosmos.bank.v1beta1.Msg/Send'", "message.module='bank'"), + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s&order_by=invalid_order", val.APIAddress, "message.action='/cosmos.bank.v1beta1.MsgSend'", "message.module='bank'"), true, "is not a valid tx.OrderBy", }, { "expect pass with multiple-events", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s", val.APIAddress, "message.action='/cosmos.bank.v1beta1.Msg/Send'", "message.module='bank'"), + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s", val.APIAddress, "message.action='/cosmos.bank.v1beta1.MsgSend'", "message.module='bank'"), false, "", }, diff --git a/x/authz/client/cli/tx.go b/x/authz/client/cli/tx.go index 42f7e1fe4be8..1bbebf6d1287 100644 --- a/x/authz/client/cli/tx.go +++ b/x/authz/client/cli/tx.go @@ -60,7 +60,7 @@ func NewCmdGrantAuthorization() *cobra.Command { Examples: $ %s tx %s grant cosmos1skjw.. send %s --spend-limit=1000stake --from=cosmos1skl.. - $ %s tx %s grant cosmos1skjw.. generic --msg-type=/cosmos.gov.v1beta1.Msg/Vote --from=cosmos1sk.. + $ %s tx %s grant cosmos1skjw.. generic --msg-type=/cosmos.gov.v1beta1.MsgVote --from=cosmos1sk.. `, version.AppName, types.ModuleName, bank.SendAuthorization{}.MethodName(), version.AppName, types.ModuleName), ), Args: cobra.ExactArgs(2), diff --git a/x/authz/client/cli/tx_test.go b/x/authz/client/cli/tx_test.go index b7dad15e65b2..7285ffed4200 100644 --- a/x/authz/client/cli/tx_test.go +++ b/x/authz/client/cli/tx_test.go @@ -1,5 +1,3 @@ -// +build norace - package cli_test import ( @@ -8,27 +6,23 @@ import ( "time" "github.com/gogo/protobuf/proto" - "github.com/stretchr/testify/suite" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/crypto/keyring" - - "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/testutil" clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" "github.com/cosmos/cosmos-sdk/testutil/network" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/authz/client/cli" + authztestutil "github.com/cosmos/cosmos-sdk/x/authz/client/testutil" + banktestutil "github.com/cosmos/cosmos-sdk/x/bank/client/testutil" + bank "github.com/cosmos/cosmos-sdk/x/bank/types" govcli "github.com/cosmos/cosmos-sdk/x/gov/client/cli" govtestutil "github.com/cosmos/cosmos-sdk/x/gov/client/testutil" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" stakingcli "github.com/cosmos/cosmos-sdk/x/staking/client/cli" - - authztestutil "github.com/cosmos/cosmos-sdk/x/authz/client/testutil" - bankcli "github.com/cosmos/cosmos-sdk/x/bank/client/testutil" - banktestutil "github.com/cosmos/cosmos-sdk/x/bank/client/testutil" - bank "github.com/cosmos/cosmos-sdk/x/bank/types" ) type IntegrationTestSuite struct { @@ -86,7 +80,7 @@ func (s *IntegrationTestSuite) TearDownSuite() { } var typeMsgSend = bank.SendAuthorization{}.MethodName() -var typeMsgVote = "/cosmos.gov.v1beta1.Msg/Vote" +var typeMsgVote = proto.MessageName(&govtypes.MsgVote{}) var commonFlags = []string{} @@ -434,7 +428,7 @@ func (s *IntegrationTestSuite) TestExecAuthorizationWithExpiration() { ) s.Require().NoError(err) // msg vote - voteTx := fmt.Sprintf(`{"body":{"messages":[{"@type":"/cosmos.gov.v1beta1.Msg/Vote","proposal_id":"1","voter":"%s","option":"VOTE_OPTION_YES"}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[],"gas_limit":"200000","payer":"","granter":""}},"signatures":[]}`, val.Address.String()) + voteTx := fmt.Sprintf(`{"body":{"messages":[{"@type":"/cosmos.gov.v1beta1.MsgVote","proposal_id":"1","voter":"%s","option":"VOTE_OPTION_YES"}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[],"gas_limit":"200000","payer":"","granter":""}},"signatures":[]}`, val.Address.String()) execMsg := testutil.WriteToNewTempFile(s.T(), voteTx) // waiting for authorization to expires @@ -475,7 +469,7 @@ func (s *IntegrationTestSuite) TestNewExecGenericAuthorized() { s.Require().NoError(err) // msg vote - voteTx := fmt.Sprintf(`{"body":{"messages":[{"@type":"/cosmos.gov.v1beta1.Msg/Vote","proposal_id":"1","voter":"%s","option":"VOTE_OPTION_YES"}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[],"gas_limit":"200000","payer":"","granter":""}},"signatures":[]}`, val.Address.String()) + voteTx := fmt.Sprintf(`{"body":{"messages":[{"@type":"/cosmos.gov.v1beta1.MsgVote","proposal_id":"1","voter":"%s","option":"VOTE_OPTION_YES"}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[],"gas_limit":"200000","payer":"","granter":""}},"signatures":[]}`, val.Address.String()) execMsg := testutil.WriteToNewTempFile(s.T(), voteTx) testCases := []struct { @@ -565,7 +559,7 @@ func (s *IntegrationTestSuite) TestNewExecGrantAuthorized() { tokens := sdk.NewCoins( sdk.NewCoin(fmt.Sprintf("%stoken", val.Moniker), sdk.NewInt(12)), ) - normalGeneratedTx, err := bankcli.MsgSendExec( + normalGeneratedTx, err := banktestutil.MsgSendExec( val.ClientCtx, val.Address, grantee, @@ -653,7 +647,7 @@ func (s *IntegrationTestSuite) TestExecDelegateAuthorization() { sdk.NewCoin("stake", sdk.NewInt(50)), ) - delegateTx := fmt.Sprintf(`{"body":{"messages":[{"@type":"/cosmos.staking.v1beta1.Msg/Delegate","delegator_address":"%s","validator_address":"%s","amount":{"denom":"%s","amount":"%s"}}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[],"gas_limit":"200000","payer":"","granter":""}},"signatures":[]}`, val.Address.String(), val.ValAddress.String(), + delegateTx := fmt.Sprintf(`{"body":{"messages":[{"@type":"/cosmos.staking.v1beta1.MsgDelegate","delegator_address":"%s","validator_address":"%s","amount":{"denom":"%s","amount":"%s"}}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[],"gas_limit":"200000","payer":"","granter":""}},"signatures":[]}`, val.Address.String(), val.ValAddress.String(), tokens.GetDenomByIndex(0), tokens[0].Amount) execMsg := testutil.WriteToNewTempFile(s.T(), delegateTx) @@ -743,7 +737,7 @@ func (s *IntegrationTestSuite) TestExecDelegateAuthorization() { sdk.NewCoin("stake", sdk.NewInt(50)), ) - delegateTx = fmt.Sprintf(`{"body":{"messages":[{"@type":"/cosmos.staking.v1beta1.Msg/Delegate","delegator_address":"%s","validator_address":"%s","amount":{"denom":"%s","amount":"%s"}}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[],"gas_limit":"200000","payer":"","granter":""}},"signatures":[]}`, val.Address.String(), val.ValAddress.String(), + delegateTx = fmt.Sprintf(`{"body":{"messages":[{"@type":"/cosmos.staking.v1beta1.MsgDelegate","delegator_address":"%s","validator_address":"%s","amount":{"denom":"%s","amount":"%s"}}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[],"gas_limit":"200000","payer":"","granter":""}},"signatures":[]}`, val.Address.String(), val.ValAddress.String(), tokens.GetDenomByIndex(0), tokens[0].Amount) execMsg = testutil.WriteToNewTempFile(s.T(), delegateTx) @@ -872,7 +866,7 @@ func (s *IntegrationTestSuite) TestExecUndelegateAuthorization() { sdk.NewCoin("stake", sdk.NewInt(50)), ) - undelegateTx := fmt.Sprintf(`{"body":{"messages":[{"@type":"/cosmos.staking.v1beta1.Msg/Undelegate","delegator_address":"%s","validator_address":"%s","amount":{"denom":"%s","amount":"%s"}}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[],"gas_limit":"200000","payer":"","granter":""}},"signatures":[]}`, val.Address.String(), val.ValAddress.String(), + undelegateTx := fmt.Sprintf(`{"body":{"messages":[{"@type":"/cosmos.staking.v1beta1.MsgUndelegate","delegator_address":"%s","validator_address":"%s","amount":{"denom":"%s","amount":"%s"}}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[],"gas_limit":"200000","payer":"","granter":""}},"signatures":[]}`, val.Address.String(), val.ValAddress.String(), tokens.GetDenomByIndex(0), tokens[0].Amount) execMsg := testutil.WriteToNewTempFile(s.T(), undelegateTx) @@ -962,7 +956,7 @@ func (s *IntegrationTestSuite) TestExecUndelegateAuthorization() { sdk.NewCoin("stake", sdk.NewInt(50)), ) - undelegateTx = fmt.Sprintf(`{"body":{"messages":[{"@type":"/cosmos.staking.v1beta1.Msg/Undelegate","delegator_address":"%s","validator_address":"%s","amount":{"denom":"%s","amount":"%s"}}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[],"gas_limit":"200000","payer":"","granter":""}},"signatures":[]}`, val.Address.String(), val.ValAddress.String(), + undelegateTx = fmt.Sprintf(`{"body":{"messages":[{"@type":"/cosmos.staking.v1beta1.MsgUndelegate","delegator_address":"%s","validator_address":"%s","amount":{"denom":"%s","amount":"%s"}}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[],"gas_limit":"200000","payer":"","granter":""}},"signatures":[]}`, val.Address.String(), val.ValAddress.String(), tokens.GetDenomByIndex(0), tokens[0].Amount) execMsg = testutil.WriteToNewTempFile(s.T(), undelegateTx) diff --git a/x/authz/client/rest/grpc_query_test.go b/x/authz/client/rest/grpc_query_test.go index 676af3915b01..3136ba72fb62 100644 --- a/x/authz/client/rest/grpc_query_test.go +++ b/x/authz/client/rest/grpc_query_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/gogo/protobuf/proto" "github.com/stretchr/testify/suite" "github.com/cosmos/cosmos-sdk/client/flags" @@ -20,6 +21,7 @@ import ( types "github.com/cosmos/cosmos-sdk/x/authz/types" banktestutil "github.com/cosmos/cosmos-sdk/x/bank/client/testutil" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" ) type IntegrationTestSuite struct { @@ -30,7 +32,7 @@ type IntegrationTestSuite struct { } var typeMsgSend = banktypes.SendAuthorization{}.MethodName() -var typeMsgVote = "/cosmos.gov.v1beta1.Msg/Vote" +var typeMsgVote = proto.MessageName(&govtypes.MsgVote{}) func (s *IntegrationTestSuite) SetupSuite() { s.T().Log("setting up integration test suite") diff --git a/x/authz/keeper/keeper_test.go b/x/authz/keeper/keeper_test.go index 63156d6f2ed0..42a3edef6f4c 100644 --- a/x/authz/keeper/keeper_test.go +++ b/x/authz/keeper/keeper_test.go @@ -1,18 +1,16 @@ package keeper_test import ( + "fmt" "testing" "time" proto "github.com/gogo/protobuf/proto" - - "github.com/cosmos/cosmos-sdk/baseapp" - + "github.com/stretchr/testify/suite" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" - "github.com/stretchr/testify/suite" - + "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/authz/types" @@ -139,14 +137,11 @@ func (s *TestSuite) TestKeeperFees() { smallCoin := sdk.NewCoins(sdk.NewInt64Coin("steak", 20)) someCoin := sdk.NewCoins(sdk.NewInt64Coin("steak", 123)) - msgs := types.NewMsgExecAuthorized(granteeAddr, []sdk.ServiceMsg{ - { - MethodName: banktypes.SendAuthorization{}.MethodName(), - Request: &banktypes.MsgSend{ - Amount: sdk.NewCoins(sdk.NewInt64Coin("steak", 2)), - FromAddress: granterAddr.String(), - ToAddress: recipientAddr.String(), - }, + msgs := types.NewMsgExecAuthorized(granteeAddr, []sdk.Msg{ + &banktypes.MsgSend{ + Amount: sdk.NewCoins(sdk.NewInt64Coin("steak", 2)), + FromAddress: granterAddr.String(), + ToAddress: recipientAddr.String(), }, }) @@ -172,6 +167,7 @@ func (s *TestSuite) TestKeeperFees() { executeMsgs, err = msgs.GetMessages() s.Require().NoError(err) + fmt.Println("TEST granteeAddr=", granteeAddr, "executeMsgs=", executeMsgs) result, err = app.AuthzKeeper.DispatchActions(s.ctx, granteeAddr, executeMsgs) s.Require().NoError(err) s.Require().NotNil(result) @@ -182,14 +178,11 @@ func (s *TestSuite) TestKeeperFees() { s.T().Log("verify dispatch fails with overlimit") // grant authorization - msgs = types.NewMsgExecAuthorized(granteeAddr, []sdk.ServiceMsg{ - { - MethodName: banktypes.SendAuthorization{}.MethodName(), - Request: &banktypes.MsgSend{ - Amount: someCoin, - FromAddress: granterAddr.String(), - ToAddress: recipientAddr.String(), - }, + msgs = types.NewMsgExecAuthorized(granteeAddr, []sdk.Msg{ + &banktypes.MsgSend{ + Amount: someCoin, + FromAddress: granterAddr.String(), + ToAddress: recipientAddr.String(), }, }) diff --git a/x/authz/simulation/operations.go b/x/authz/simulation/operations.go index 8fef468b06db..ffa833e6f24a 100644 --- a/x/authz/simulation/operations.go +++ b/x/authz/simulation/operations.go @@ -5,6 +5,8 @@ import ( "math/rand" "strings" + "github.com/gogo/protobuf/proto" + "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" cdctypes "github.com/cosmos/cosmos-sdk/codec/types" @@ -20,10 +22,11 @@ import ( ) // authz message types -const ( - TypeMsgGrantAuthorization = "/cosmos.authz.v1beta1.Msg/GrantAuthorization" - TypeMsgRevokeAuthorization = "/cosmos.authz.v1beta1.Msg/RevokeAuthorization" - TypeMsgExecDelegated = "/cosmos.authz.v1beta1.Msg/ExecAuthorized" +var ( + // FIXME Remove `Request` suffix + TypeMsgGrantAuthorization = proto.MessageName(&types.MsgGrantAuthorizationRequest{}) + TypeMsgRevokeAuthorization = proto.MessageName(&types.MsgRevokeAuthorizationRequest{}) + TypeMsgExecDelegated = proto.MessageName(&types.MsgExecAuthorizedRequest{}) ) // Simulation operation weights constants @@ -70,10 +73,10 @@ func WeightedOperations( weightRevokeAuthorization, SimulateMsgRevokeAuthorization(ak, bk, k, protoCdc), ), - simulation.NewWeightedOperation( - weightExecAuthorized, - SimulateMsgExecuteAuthorized(ak, bk, k, appCdc, protoCdc), - ), + // simulation.NewWeightedOperation( + // weightExecAuthorized, + // SimulateMsgExecuteAuthorized(ak, bk, k, appCdc, protoCdc), + // ), } } @@ -130,7 +133,7 @@ func SimulateMsgGrantAuthorization(ak types.AccountKeeper, bk types.BankKeeper, _, _, err = app.Deliver(txGen.TxEncoder(), tx) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, svcMsgClientConn.GetMsgs()[0].Type(), "unable to deliver tx"), nil, err + return simtypes.NoOpMsg(types.ModuleName, proto.MessageName(svcMsgClientConn.GetMsgs()[0]), "unable to deliver tx"), nil, err } return simtypes.NewOperationMsg(svcMsgClientConn.GetMsgs()[0], true, "", protoCdc), nil, err } @@ -242,16 +245,13 @@ func SimulateMsgExecuteAuthorized(ak types.AccountKeeper, bk types.BankKeeper, k } sendCoins := sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(10))) - execMsg := sdk.ServiceMsg{ - MethodName: banktype.SendAuthorization{}.MethodName(), - Request: banktype.NewMsgSend( - granterAddr, - granteeAddr, - sendCoins, - ), - } + execMsg := banktype.NewMsgSend( + granterAddr, + granteeAddr, + sendCoins, + ) - msg := types.NewMsgExecAuthorized(grantee.Address, []sdk.ServiceMsg{execMsg}) + msg := types.NewMsgExecAuthorized(grantee.Address, []sdk.Msg{execMsg}) sendGrant := targetGrant.Authorization.GetCachedValue().(*banktype.SendAuthorization) _, _, err = sendGrant.Accept(ctx, execMsg) if err != nil { diff --git a/x/authz/types/generic_authorization.go b/x/authz/types/generic_authorization.go index 809fd4d693aa..67af64c99dcf 100644 --- a/x/authz/types/generic_authorization.go +++ b/x/authz/types/generic_authorization.go @@ -2,8 +2,6 @@ package types import ( sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/types/msgservice" "github.com/cosmos/cosmos-sdk/x/authz/exported" ) @@ -30,8 +28,5 @@ func (authorization GenericAuthorization) Accept(ctx sdk.Context, msg sdk.Msg) ( // ValidateBasic implements Authorization.ValidateBasic. func (authorization GenericAuthorization) ValidateBasic() error { - if !msgservice.IsServiceMsg(authorization.MessageName) { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidType, " %s is not a valid service msg", authorization.MessageName) - } return nil } diff --git a/x/bank/types/send_authorization.go b/x/bank/types/send_authorization.go index 6202130e38b8..e3255a926d96 100644 --- a/x/bank/types/send_authorization.go +++ b/x/bank/types/send_authorization.go @@ -1,6 +1,8 @@ package types import ( + "github.com/gogo/protobuf/proto" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" authz "github.com/cosmos/cosmos-sdk/x/authz/exported" @@ -19,7 +21,7 @@ func NewSendAuthorization(spendLimit sdk.Coins) *SendAuthorization { // MethodName implements Authorization.MethodName. func (authorization SendAuthorization) MethodName() string { - return "/cosmos.bank.v1beta1.Msg/Send" + return proto.MessageName(&MsgSend{}) } // Accept implements Authorization.Accept. diff --git a/x/bank/types/send_authorization_test.go b/x/bank/types/send_authorization_test.go index 1c1522b8dd6d..df0e4941eee2 100644 --- a/x/bank/types/send_authorization_test.go +++ b/x/bank/types/send_authorization_test.go @@ -23,7 +23,7 @@ func TestSendAuthorization(t *testing.T) { authorization := types.NewSendAuthorization(coins1000) t.Log("verify authorization returns valid method name") - require.Equal(t, authorization.MethodName(), "/cosmos.bank.v1beta1.Msg/Send") + require.Equal(t, authorization.MethodName(), "cosmos.bank.v1beta1.MsgSend") require.NoError(t, authorization.ValidateBasic()) send := types.NewMsgSend(fromAddr, toAddr, coins1000) @@ -36,7 +36,7 @@ func TestSendAuthorization(t *testing.T) { require.Nil(t, updated) authorization = types.NewSendAuthorization(coins1000) - require.Equal(t, authorization.MethodName(), "/cosmos.bank.v1beta1.Msg/Send") + require.Equal(t, authorization.MethodName(), "cosmos.bank.v1beta1.MsgSend") require.NoError(t, authorization.ValidateBasic()) send = types.NewMsgSend(fromAddr, toAddr, coins500) require.NoError(t, authorization.ValidateBasic()) diff --git a/x/feegrant/client/cli/tx.go b/x/feegrant/client/cli/tx.go index c355ca4b5acd..354b3ff1d50e 100644 --- a/x/feegrant/client/cli/tx.go +++ b/x/feegrant/client/cli/tx.go @@ -58,7 +58,7 @@ Examples: %s tx %s grant cosmos1skjw... cosmos1skjw... --spend-limit 100stake --expiration 36000 or %s tx %s grant cosmos1skjw... cosmos1skjw... --spend-limit 100stake --period 3600 --period-limit 10stake --expiration 36000 or %s tx %s grant cosmos1skjw... cosmos1skjw... --spend-limit 100stake --expiration 36000 - --allowed-messages "/cosmos.gov.v1beta1.Msg/SubmitProposal,/cosmos.gov.v1beta1.Msg/Vote" + --allowed-messages "/cosmos.gov.v1beta1.MsgSubmitProposal,/cosmos.gov.v1beta1.MsgVote" `, version.AppName, types.ModuleName, version.AppName, types.ModuleName, version.AppName, types.ModuleName, ), ), diff --git a/x/feegrant/module.go b/x/feegrant/module.go index a6f1f4f62000..b3e99084d294 100644 --- a/x/feegrant/module.go +++ b/x/feegrant/module.go @@ -204,8 +204,9 @@ func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { // WeightedOperations returns all the feegrant module operations with their respective weights. func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - protoCdc := codec.NewProtoCodec(am.registry) - return simulation.WeightedOperations( - simState.AppParams, simState.Cdc, am.accountKeeper, am.bankKeeper, am.keeper, protoCdc, - ) + return nil + // protoCdc := codec.NewProtoCodec(am.registry) + // return simulation.WeightedOperations( + // simState.AppParams, simState.Cdc, am.accountKeeper, am.bankKeeper, am.keeper, protoCdc, + // ) } diff --git a/x/feegrant/simulation/operations.go b/x/feegrant/simulation/operations.go index 798685fa81c2..c7a4a239b0d2 100644 --- a/x/feegrant/simulation/operations.go +++ b/x/feegrant/simulation/operations.go @@ -23,8 +23,11 @@ import ( const ( OpWeightMsgGrantFeeAllowance = "op_weight_msg_grant_fee_allowance" OpWeightMsgRevokeFeeAllowance = "op_weight_msg_grant_revoke_allowance" - TypeMsgGrantFeeAllowance = "/cosmos.feegrant.v1beta1.Msg/GrantFeeAllowance" - TypeMsgRevokeFeeAllowance = "/cosmos.feegrant.v1beta1.Msg/RevokeFeeAllowance" +) + +var ( + TypeMsgGrantFeeAllowance = proto.MessageName(&types.MsgGrantFeeAllowance{}) + TypeMsgRevokeFeeAllowance = proto.MessageName(&types.MsgRevokeFeeAllowance{}) ) func WeightedOperations( diff --git a/x/feegrant/simulation/operations_test.go b/x/feegrant/simulation/operations_test.go index d9c0fffaebd7..aa543205dbb9 100644 --- a/x/feegrant/simulation/operations_test.go +++ b/x/feegrant/simulation/operations_test.go @@ -1,169 +1,169 @@ package simulation_test -import ( - "math/rand" - "testing" - "time" - - "github.com/stretchr/testify/suite" - - abci "github.com/tendermint/tendermint/abci/types" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/simapp" - simappparams "github.com/cosmos/cosmos-sdk/simapp/params" - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/feegrant/simulation" - "github.com/cosmos/cosmos-sdk/x/feegrant/types" -) - -type SimTestSuite struct { - suite.Suite - - ctx sdk.Context - app *simapp.SimApp - protoCdc *codec.ProtoCodec -} - -func (suite *SimTestSuite) SetupTest() { - checkTx := false - app := simapp.Setup(checkTx) - suite.app = app - suite.ctx = app.BaseApp.NewContext(checkTx, tmproto.Header{}) - suite.protoCdc = codec.NewProtoCodec(suite.app.InterfaceRegistry()) - -} - -func (suite *SimTestSuite) getTestingAccounts(r *rand.Rand, n int) []simtypes.Account { - accounts := simtypes.RandomAccounts(r, n) - - initAmt := sdk.TokensFromConsensusPower(200, sdk.DefaultPowerReduction) - initCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initAmt)) - - // add coins to the accounts - for _, account := range accounts { - err := simapp.FundAccount(suite.app, suite.ctx, account.Address, initCoins) - suite.Require().NoError(err) - } - - return accounts -} - -func (suite *SimTestSuite) TestWeightedOperations() { - app, ctx := suite.app, suite.ctx - require := suite.Require() - - ctx.WithChainID("test-chain") - - cdc := app.AppCodec() - appParams := make(simtypes.AppParams) - - weightesOps := simulation.WeightedOperations( - appParams, cdc, app.AccountKeeper, - app.BankKeeper, app.FeeGrantKeeper, - suite.protoCdc, - ) - - s := rand.NewSource(1) - r := rand.New(s) - accs := suite.getTestingAccounts(r, 3) - - expected := []struct { - weight int - opMsgRoute string - opMsgName string - }{ - { - simappparams.DefaultWeightGrantFeeAllowance, - types.ModuleName, - simulation.TypeMsgGrantFeeAllowance, - }, - { - simappparams.DefaultWeightRevokeFeeAllowance, - types.ModuleName, - simulation.TypeMsgRevokeFeeAllowance, - }, - } - - for i, w := range weightesOps { - operationMsg, _, _ := w.Op()(r, app.BaseApp, ctx, accs, ctx.ChainID()) - // the following checks are very much dependent from the ordering of the output given - // by WeightedOperations. if the ordering in WeightedOperations changes some tests - // will fail - require.Equal(expected[i].weight, w.Weight(), "weight should be the same") - require.Equal(expected[i].opMsgRoute, operationMsg.Route, "route should be the same") - require.Equal(expected[i].opMsgName, operationMsg.Name, "operation Msg name should be the same") - } -} - -func (suite *SimTestSuite) TestSimulateMsgGrantFeeAllowance() { - app, ctx := suite.app, suite.ctx - require := suite.Require() - - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 3) - - // begin a new block - app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash}}) - - // execute operation - op := simulation.SimulateMsgGrantFeeAllowance(app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, suite.protoCdc) - operationMsg, futureOperations, err := op(r, app.BaseApp, ctx, accounts, "") - require.NoError(err) - - var msg types.MsgGrantFeeAllowance - suite.app.AppCodec().UnmarshalJSON(operationMsg.Msg, &msg) - - require.True(operationMsg.OK) - require.Equal("cosmos1ghekyjucln7y67ntx7cf27m9dpuxxemn4c8g4r", msg.Granter) - require.Equal("cosmos1p8wcgrjr4pjju90xg6u9cgq55dxwq8j7u4x9a0", msg.Grantee) - require.Len(futureOperations, 0) -} - -func (suite *SimTestSuite) TestSimulateMsgRevokeFeeAllowance() { - app, ctx := suite.app, suite.ctx - require := suite.Require() - - s := rand.NewSource(1) - r := rand.New(s) - accounts := suite.getTestingAccounts(r, 3) - - // begin a new block - app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: suite.app.LastBlockHeight() + 1, AppHash: suite.app.LastCommitID().Hash}}) - - feeAmt := app.StakingKeeper.TokensFromConsensusPower(ctx, 200000) - feeCoins := sdk.NewCoins(sdk.NewCoin("foo", feeAmt)) - - granter, grantee := accounts[0], accounts[1] - - err := app.FeeGrantKeeper.GrantFeeAllowance( - ctx, - granter.Address, - grantee.Address, - &types.BasicFeeAllowance{ - SpendLimit: feeCoins, - Expiration: types.ExpiresAtTime(ctx.BlockTime().Add(30 * time.Hour)), - }, - ) - require.NoError(err) - - // execute operation - op := simulation.SimulateMsgRevokeFeeAllowance(app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, suite.protoCdc) - operationMsg, futureOperations, err := op(r, app.BaseApp, ctx, accounts, "") - require.NoError(err) - - var msg types.MsgRevokeFeeAllowance - suite.app.AppCodec().UnmarshalJSON(operationMsg.Msg, &msg) - - require.True(operationMsg.OK) - require.Equal(granter.Address.String(), msg.Granter) - require.Equal(grantee.Address.String(), msg.Grantee) - require.Len(futureOperations, 0) -} - -func TestSimTestSuite(t *testing.T) { - suite.Run(t, new(SimTestSuite)) -} +// import ( +// "math/rand" +// "testing" +// "time" + +// "github.com/stretchr/testify/suite" + +// abci "github.com/tendermint/tendermint/abci/types" +// tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + +// "github.com/cosmos/cosmos-sdk/codec" +// "github.com/cosmos/cosmos-sdk/simapp" +// simappparams "github.com/cosmos/cosmos-sdk/simapp/params" +// sdk "github.com/cosmos/cosmos-sdk/types" +// simtypes "github.com/cosmos/cosmos-sdk/types/simulation" +// "github.com/cosmos/cosmos-sdk/x/feegrant/simulation" +// "github.com/cosmos/cosmos-sdk/x/feegrant/types" +// ) + +// type SimTestSuite struct { +// suite.Suite + +// ctx sdk.Context +// app *simapp.SimApp +// protoCdc *codec.ProtoCodec +// } + +// func (suite *SimTestSuite) SetupTest() { +// checkTx := false +// app := simapp.Setup(checkTx) +// suite.app = app +// suite.ctx = app.BaseApp.NewContext(checkTx, tmproto.Header{}) +// suite.protoCdc = codec.NewProtoCodec(suite.app.InterfaceRegistry()) + +// } + +// func (suite *SimTestSuite) getTestingAccounts(r *rand.Rand, n int) []simtypes.Account { +// accounts := simtypes.RandomAccounts(r, n) + +// initAmt := sdk.TokensFromConsensusPower(200, sdk.DefaultPowerReduction) +// initCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initAmt)) + +// // add coins to the accounts +// for _, account := range accounts { +// err := simapp.FundAccount(suite.app, suite.ctx, account.Address, initCoins) +// suite.Require().NoError(err) +// } + +// return accounts +// } + +// func (suite *SimTestSuite) TestWeightedOperations() { +// app, ctx := suite.app, suite.ctx +// require := suite.Require() + +// ctx.WithChainID("test-chain") + +// cdc := app.AppCodec() +// appParams := make(simtypes.AppParams) + +// weightesOps := simulation.WeightedOperations( +// appParams, cdc, app.AccountKeeper, +// app.BankKeeper, app.FeeGrantKeeper, +// suite.protoCdc, +// ) + +// s := rand.NewSource(1) +// r := rand.New(s) +// accs := suite.getTestingAccounts(r, 3) + +// expected := []struct { +// weight int +// opMsgRoute string +// opMsgName string +// }{ +// { +// simappparams.DefaultWeightGrantFeeAllowance, +// types.ModuleName, +// simulation.TypeMsgGrantFeeAllowance, +// }, +// { +// simappparams.DefaultWeightRevokeFeeAllowance, +// types.ModuleName, +// simulation.TypeMsgRevokeFeeAllowance, +// }, +// } + +// for i, w := range weightesOps { +// operationMsg, _, _ := w.Op()(r, app.BaseApp, ctx, accs, ctx.ChainID()) +// // the following checks are very much dependent from the ordering of the output given +// // by WeightedOperations. if the ordering in WeightedOperations changes some tests +// // will fail +// require.Equal(expected[i].weight, w.Weight(), "weight should be the same") +// require.Equal(expected[i].opMsgRoute, operationMsg.Route, "route should be the same") +// require.Equal(expected[i].opMsgName, operationMsg.Name, "operation Msg name should be the same") +// } +// } + +// func (suite *SimTestSuite) TestSimulateMsgGrantFeeAllowance() { +// app, ctx := suite.app, suite.ctx +// require := suite.Require() + +// s := rand.NewSource(1) +// r := rand.New(s) +// accounts := suite.getTestingAccounts(r, 3) + +// // begin a new block +// app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash}}) + +// // execute operation +// op := simulation.SimulateMsgGrantFeeAllowance(app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, suite.protoCdc) +// operationMsg, futureOperations, err := op(r, app.BaseApp, ctx, accounts, "") +// require.NoError(err) + +// var msg types.MsgGrantFeeAllowance +// suite.app.AppCodec().UnmarshalJSON(operationMsg.Msg, &msg) + +// require.True(operationMsg.OK) +// require.Equal("cosmos1ghekyjucln7y67ntx7cf27m9dpuxxemn4c8g4r", msg.Granter) +// require.Equal("cosmos1p8wcgrjr4pjju90xg6u9cgq55dxwq8j7u4x9a0", msg.Grantee) +// require.Len(futureOperations, 0) +// } + +// func (suite *SimTestSuite) TestSimulateMsgRevokeFeeAllowance() { +// app, ctx := suite.app, suite.ctx +// require := suite.Require() + +// s := rand.NewSource(1) +// r := rand.New(s) +// accounts := suite.getTestingAccounts(r, 3) + +// // begin a new block +// app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: suite.app.LastBlockHeight() + 1, AppHash: suite.app.LastCommitID().Hash}}) + +// feeAmt := app.StakingKeeper.TokensFromConsensusPower(ctx, 200000) +// feeCoins := sdk.NewCoins(sdk.NewCoin("foo", feeAmt)) + +// granter, grantee := accounts[0], accounts[1] + +// err := app.FeeGrantKeeper.GrantFeeAllowance( +// ctx, +// granter.Address, +// grantee.Address, +// &types.BasicFeeAllowance{ +// SpendLimit: feeCoins, +// Expiration: types.ExpiresAtTime(ctx.BlockTime().Add(30 * time.Hour)), +// }, +// ) +// require.NoError(err) + +// // execute operation +// op := simulation.SimulateMsgRevokeFeeAllowance(app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, suite.protoCdc) +// operationMsg, futureOperations, err := op(r, app.BaseApp, ctx, accounts, "") +// require.NoError(err) + +// var msg types.MsgRevokeFeeAllowance +// suite.app.AppCodec().UnmarshalJSON(operationMsg.Msg, &msg) + +// require.True(operationMsg.OK) +// require.Equal(granter.Address.String(), msg.Granter) +// require.Equal(grantee.Address.String(), msg.Grantee) +// require.Len(futureOperations, 0) +// } + +// func TestSimTestSuite(t *testing.T) { +// suite.Run(t, new(SimTestSuite)) +// } diff --git a/x/staking/client/rest/query.go b/x/staking/client/rest/query.go index 172ab477b7a7..b833766c2f91 100644 --- a/x/staking/client/rest/query.go +++ b/x/staking/client/rest/query.go @@ -153,28 +153,22 @@ func delegatorTxsHandlerFn(clientCtx client.Context) http.HandlerFunc { // For each case, we search txs for both: // - legacy messages: their Type() is a custom string, e.g. "delegate" - // - service Msgs: their Type() is their FQ method name, e.g. "/cosmos.staking.v1beta1.Msg/Delegate" + // - service Msgs: their Type() is their FQ method name, e.g. "/cosmos.staking.v1beta1.MsgDelegate" // and we combine the results. switch { case isBondTx: actions = append(actions, types.TypeMsgDelegate) - actions = append(actions, types.TypeSvcMsgDelegate) case isUnbondTx: actions = append(actions, types.TypeMsgUndelegate) - actions = append(actions, types.TypeSvcMsgUndelegate) case isRedTx: actions = append(actions, types.TypeMsgBeginRedelegate) - actions = append(actions, types.TypeSvcMsgBeginRedelegate) case noQuery: actions = append(actions, types.TypeMsgDelegate) - actions = append(actions, types.TypeSvcMsgDelegate) actions = append(actions, types.TypeMsgUndelegate) - actions = append(actions, types.TypeSvcMsgUndelegate) actions = append(actions, types.TypeMsgBeginRedelegate) - actions = append(actions, types.TypeSvcMsgBeginRedelegate) default: w.WriteHeader(http.StatusNoContent) diff --git a/x/staking/types/authz.go b/x/staking/types/authz.go index e7cf7a92c5d1..d76c40b1c65a 100644 --- a/x/staking/types/authz.go +++ b/x/staking/types/authz.go @@ -4,6 +4,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" authz "github.com/cosmos/cosmos-sdk/x/authz/exported" + "github.com/gogo/protobuf/proto" ) // TODO: Revisit this once we have propoer gas fee framework. @@ -12,10 +13,6 @@ const gasCostPerIteration = uint64(10) var ( _ authz.Authorization = &StakeAuthorization{} - - TypeDelegate = "/cosmos.staking.v1beta1.Msg/Delegate" - TypeUndelegate = "/cosmos.staking.v1beta1.Msg/Undelegate" - TypeBeginRedelegate = "/cosmos.staking.v1beta1.Msg/BeginRedelegate" ) // NewStakeAuthorization creates a new StakeAuthorization object. @@ -142,11 +139,11 @@ func validateAndBech32fy(allowed []sdk.ValAddress, denied []sdk.ValAddress) ([]s func normalizeAuthzType(authzType AuthorizationType) (string, error) { switch authzType { case AuthorizationType_AUTHORIZATION_TYPE_DELEGATE: - return TypeDelegate, nil + return proto.MessageName(&MsgDelegate{}), nil case AuthorizationType_AUTHORIZATION_TYPE_UNDELEGATE: - return TypeUndelegate, nil + return proto.MessageName(&MsgUndelegate{}), nil case AuthorizationType_AUTHORIZATION_TYPE_REDELEGATE: - return TypeBeginRedelegate, nil + return proto.MessageName(&MsgBeginRedelegate{}), nil default: return "", sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "unknown authorization type %T", authzType) } diff --git a/x/staking/types/authz_test.go b/x/staking/types/authz_test.go index 884fb9bb5ca6..c6fb9f3ed709 100644 --- a/x/staking/types/authz_test.go +++ b/x/staking/types/authz_test.go @@ -3,9 +3,9 @@ package types_test import ( "testing" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - + "github.com/gogo/protobuf/proto" "github.com/stretchr/testify/require" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" @@ -33,7 +33,7 @@ func TestAuthzAuthorizations(t *testing.T) { // verify MethodName delAuth, err = stakingtypes.NewStakeAuthorization([]sdk.ValAddress{val1, val2}, []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE, &coin100) require.NoError(t, err) - require.Equal(t, delAuth.MethodName(), stakingtypes.TypeDelegate) + require.Equal(t, delAuth.MethodName(), proto.MessageName(&stakingtypes.MsgDelegate{})) // error both allow & deny list _, err = stakingtypes.NewStakeAuthorization([]sdk.ValAddress{val1, val2}, []sdk.ValAddress{val1}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE, &coin100) @@ -41,11 +41,11 @@ func TestAuthzAuthorizations(t *testing.T) { // verify MethodName undelAuth, _ := stakingtypes.NewStakeAuthorization([]sdk.ValAddress{val1, val2}, []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_UNDELEGATE, &coin100) - require.Equal(t, undelAuth.MethodName(), stakingtypes.TypeUndelegate) + require.Equal(t, undelAuth.MethodName(), proto.MessageName(&stakingtypes.MsgUndelegate{})) // verify MethodName beginRedelAuth, _ := stakingtypes.NewStakeAuthorization([]sdk.ValAddress{val1, val2}, []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_REDELEGATE, &coin100) - require.Equal(t, beginRedelAuth.MethodName(), stakingtypes.TypeBeginRedelegate) + require.Equal(t, beginRedelAuth.MethodName(), proto.MessageName(&stakingtypes.MsgBeginRedelegate{})) validators1_2 := []string{val1.String(), val2.String()} diff --git a/x/staking/types/msg.go b/x/staking/types/msg.go index e397046e5779..2525aa66468f 100644 --- a/x/staking/types/msg.go +++ b/x/staking/types/msg.go @@ -16,13 +16,6 @@ const ( TypeMsgCreateValidator = "create_validator" TypeMsgDelegate = "delegate" TypeMsgBeginRedelegate = "begin_redelegate" - - // These are used for querying events by action. - TypeSvcMsgUndelegate = "/cosmos.staking.v1beta1.Msg/Undelegate" - TypeSvcMsgEditValidator = "/cosmos.staking.v1beta1.Msg/EditValidator" - TypeSvcMsgCreateValidator = "/cosmos.staking.v1beta1.Msg/CreateValidator" - TypeSvcMsgDelegate = "/cosmos.staking.v1beta1.Msg/Delegate" - TypeSvcMsgBeginRedelegate = "/cosmos.staking.v1beta1.Msg/BeginRedelegate" ) var ( From c52587c9376757f08040921cec16462e05cad26e Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Mon, 19 Apr 2021 12:56:14 +0200 Subject: [PATCH 09/47] Uncomment code --- x/feegrant/simulation/operations_test.go | 334 +++++++++++------------ 1 file changed, 167 insertions(+), 167 deletions(-) diff --git a/x/feegrant/simulation/operations_test.go b/x/feegrant/simulation/operations_test.go index aa543205dbb9..d9c0fffaebd7 100644 --- a/x/feegrant/simulation/operations_test.go +++ b/x/feegrant/simulation/operations_test.go @@ -1,169 +1,169 @@ package simulation_test -// import ( -// "math/rand" -// "testing" -// "time" - -// "github.com/stretchr/testify/suite" - -// abci "github.com/tendermint/tendermint/abci/types" -// tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - -// "github.com/cosmos/cosmos-sdk/codec" -// "github.com/cosmos/cosmos-sdk/simapp" -// simappparams "github.com/cosmos/cosmos-sdk/simapp/params" -// sdk "github.com/cosmos/cosmos-sdk/types" -// simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -// "github.com/cosmos/cosmos-sdk/x/feegrant/simulation" -// "github.com/cosmos/cosmos-sdk/x/feegrant/types" -// ) - -// type SimTestSuite struct { -// suite.Suite - -// ctx sdk.Context -// app *simapp.SimApp -// protoCdc *codec.ProtoCodec -// } - -// func (suite *SimTestSuite) SetupTest() { -// checkTx := false -// app := simapp.Setup(checkTx) -// suite.app = app -// suite.ctx = app.BaseApp.NewContext(checkTx, tmproto.Header{}) -// suite.protoCdc = codec.NewProtoCodec(suite.app.InterfaceRegistry()) - -// } - -// func (suite *SimTestSuite) getTestingAccounts(r *rand.Rand, n int) []simtypes.Account { -// accounts := simtypes.RandomAccounts(r, n) - -// initAmt := sdk.TokensFromConsensusPower(200, sdk.DefaultPowerReduction) -// initCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initAmt)) - -// // add coins to the accounts -// for _, account := range accounts { -// err := simapp.FundAccount(suite.app, suite.ctx, account.Address, initCoins) -// suite.Require().NoError(err) -// } - -// return accounts -// } - -// func (suite *SimTestSuite) TestWeightedOperations() { -// app, ctx := suite.app, suite.ctx -// require := suite.Require() - -// ctx.WithChainID("test-chain") - -// cdc := app.AppCodec() -// appParams := make(simtypes.AppParams) - -// weightesOps := simulation.WeightedOperations( -// appParams, cdc, app.AccountKeeper, -// app.BankKeeper, app.FeeGrantKeeper, -// suite.protoCdc, -// ) - -// s := rand.NewSource(1) -// r := rand.New(s) -// accs := suite.getTestingAccounts(r, 3) - -// expected := []struct { -// weight int -// opMsgRoute string -// opMsgName string -// }{ -// { -// simappparams.DefaultWeightGrantFeeAllowance, -// types.ModuleName, -// simulation.TypeMsgGrantFeeAllowance, -// }, -// { -// simappparams.DefaultWeightRevokeFeeAllowance, -// types.ModuleName, -// simulation.TypeMsgRevokeFeeAllowance, -// }, -// } - -// for i, w := range weightesOps { -// operationMsg, _, _ := w.Op()(r, app.BaseApp, ctx, accs, ctx.ChainID()) -// // the following checks are very much dependent from the ordering of the output given -// // by WeightedOperations. if the ordering in WeightedOperations changes some tests -// // will fail -// require.Equal(expected[i].weight, w.Weight(), "weight should be the same") -// require.Equal(expected[i].opMsgRoute, operationMsg.Route, "route should be the same") -// require.Equal(expected[i].opMsgName, operationMsg.Name, "operation Msg name should be the same") -// } -// } - -// func (suite *SimTestSuite) TestSimulateMsgGrantFeeAllowance() { -// app, ctx := suite.app, suite.ctx -// require := suite.Require() - -// s := rand.NewSource(1) -// r := rand.New(s) -// accounts := suite.getTestingAccounts(r, 3) - -// // begin a new block -// app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash}}) - -// // execute operation -// op := simulation.SimulateMsgGrantFeeAllowance(app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, suite.protoCdc) -// operationMsg, futureOperations, err := op(r, app.BaseApp, ctx, accounts, "") -// require.NoError(err) - -// var msg types.MsgGrantFeeAllowance -// suite.app.AppCodec().UnmarshalJSON(operationMsg.Msg, &msg) - -// require.True(operationMsg.OK) -// require.Equal("cosmos1ghekyjucln7y67ntx7cf27m9dpuxxemn4c8g4r", msg.Granter) -// require.Equal("cosmos1p8wcgrjr4pjju90xg6u9cgq55dxwq8j7u4x9a0", msg.Grantee) -// require.Len(futureOperations, 0) -// } - -// func (suite *SimTestSuite) TestSimulateMsgRevokeFeeAllowance() { -// app, ctx := suite.app, suite.ctx -// require := suite.Require() - -// s := rand.NewSource(1) -// r := rand.New(s) -// accounts := suite.getTestingAccounts(r, 3) - -// // begin a new block -// app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: suite.app.LastBlockHeight() + 1, AppHash: suite.app.LastCommitID().Hash}}) - -// feeAmt := app.StakingKeeper.TokensFromConsensusPower(ctx, 200000) -// feeCoins := sdk.NewCoins(sdk.NewCoin("foo", feeAmt)) - -// granter, grantee := accounts[0], accounts[1] - -// err := app.FeeGrantKeeper.GrantFeeAllowance( -// ctx, -// granter.Address, -// grantee.Address, -// &types.BasicFeeAllowance{ -// SpendLimit: feeCoins, -// Expiration: types.ExpiresAtTime(ctx.BlockTime().Add(30 * time.Hour)), -// }, -// ) -// require.NoError(err) - -// // execute operation -// op := simulation.SimulateMsgRevokeFeeAllowance(app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, suite.protoCdc) -// operationMsg, futureOperations, err := op(r, app.BaseApp, ctx, accounts, "") -// require.NoError(err) - -// var msg types.MsgRevokeFeeAllowance -// suite.app.AppCodec().UnmarshalJSON(operationMsg.Msg, &msg) - -// require.True(operationMsg.OK) -// require.Equal(granter.Address.String(), msg.Granter) -// require.Equal(grantee.Address.String(), msg.Grantee) -// require.Len(futureOperations, 0) -// } - -// func TestSimTestSuite(t *testing.T) { -// suite.Run(t, new(SimTestSuite)) -// } +import ( + "math/rand" + "testing" + "time" + + "github.com/stretchr/testify/suite" + + abci "github.com/tendermint/tendermint/abci/types" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simapp" + simappparams "github.com/cosmos/cosmos-sdk/simapp/params" + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/feegrant/simulation" + "github.com/cosmos/cosmos-sdk/x/feegrant/types" +) + +type SimTestSuite struct { + suite.Suite + + ctx sdk.Context + app *simapp.SimApp + protoCdc *codec.ProtoCodec +} + +func (suite *SimTestSuite) SetupTest() { + checkTx := false + app := simapp.Setup(checkTx) + suite.app = app + suite.ctx = app.BaseApp.NewContext(checkTx, tmproto.Header{}) + suite.protoCdc = codec.NewProtoCodec(suite.app.InterfaceRegistry()) + +} + +func (suite *SimTestSuite) getTestingAccounts(r *rand.Rand, n int) []simtypes.Account { + accounts := simtypes.RandomAccounts(r, n) + + initAmt := sdk.TokensFromConsensusPower(200, sdk.DefaultPowerReduction) + initCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initAmt)) + + // add coins to the accounts + for _, account := range accounts { + err := simapp.FundAccount(suite.app, suite.ctx, account.Address, initCoins) + suite.Require().NoError(err) + } + + return accounts +} + +func (suite *SimTestSuite) TestWeightedOperations() { + app, ctx := suite.app, suite.ctx + require := suite.Require() + + ctx.WithChainID("test-chain") + + cdc := app.AppCodec() + appParams := make(simtypes.AppParams) + + weightesOps := simulation.WeightedOperations( + appParams, cdc, app.AccountKeeper, + app.BankKeeper, app.FeeGrantKeeper, + suite.protoCdc, + ) + + s := rand.NewSource(1) + r := rand.New(s) + accs := suite.getTestingAccounts(r, 3) + + expected := []struct { + weight int + opMsgRoute string + opMsgName string + }{ + { + simappparams.DefaultWeightGrantFeeAllowance, + types.ModuleName, + simulation.TypeMsgGrantFeeAllowance, + }, + { + simappparams.DefaultWeightRevokeFeeAllowance, + types.ModuleName, + simulation.TypeMsgRevokeFeeAllowance, + }, + } + + for i, w := range weightesOps { + operationMsg, _, _ := w.Op()(r, app.BaseApp, ctx, accs, ctx.ChainID()) + // the following checks are very much dependent from the ordering of the output given + // by WeightedOperations. if the ordering in WeightedOperations changes some tests + // will fail + require.Equal(expected[i].weight, w.Weight(), "weight should be the same") + require.Equal(expected[i].opMsgRoute, operationMsg.Route, "route should be the same") + require.Equal(expected[i].opMsgName, operationMsg.Name, "operation Msg name should be the same") + } +} + +func (suite *SimTestSuite) TestSimulateMsgGrantFeeAllowance() { + app, ctx := suite.app, suite.ctx + require := suite.Require() + + s := rand.NewSource(1) + r := rand.New(s) + accounts := suite.getTestingAccounts(r, 3) + + // begin a new block + app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash}}) + + // execute operation + op := simulation.SimulateMsgGrantFeeAllowance(app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, suite.protoCdc) + operationMsg, futureOperations, err := op(r, app.BaseApp, ctx, accounts, "") + require.NoError(err) + + var msg types.MsgGrantFeeAllowance + suite.app.AppCodec().UnmarshalJSON(operationMsg.Msg, &msg) + + require.True(operationMsg.OK) + require.Equal("cosmos1ghekyjucln7y67ntx7cf27m9dpuxxemn4c8g4r", msg.Granter) + require.Equal("cosmos1p8wcgrjr4pjju90xg6u9cgq55dxwq8j7u4x9a0", msg.Grantee) + require.Len(futureOperations, 0) +} + +func (suite *SimTestSuite) TestSimulateMsgRevokeFeeAllowance() { + app, ctx := suite.app, suite.ctx + require := suite.Require() + + s := rand.NewSource(1) + r := rand.New(s) + accounts := suite.getTestingAccounts(r, 3) + + // begin a new block + app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: suite.app.LastBlockHeight() + 1, AppHash: suite.app.LastCommitID().Hash}}) + + feeAmt := app.StakingKeeper.TokensFromConsensusPower(ctx, 200000) + feeCoins := sdk.NewCoins(sdk.NewCoin("foo", feeAmt)) + + granter, grantee := accounts[0], accounts[1] + + err := app.FeeGrantKeeper.GrantFeeAllowance( + ctx, + granter.Address, + grantee.Address, + &types.BasicFeeAllowance{ + SpendLimit: feeCoins, + Expiration: types.ExpiresAtTime(ctx.BlockTime().Add(30 * time.Hour)), + }, + ) + require.NoError(err) + + // execute operation + op := simulation.SimulateMsgRevokeFeeAllowance(app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, suite.protoCdc) + operationMsg, futureOperations, err := op(r, app.BaseApp, ctx, accounts, "") + require.NoError(err) + + var msg types.MsgRevokeFeeAllowance + suite.app.AppCodec().UnmarshalJSON(operationMsg.Msg, &msg) + + require.True(operationMsg.OK) + require.Equal(granter.Address.String(), msg.Granter) + require.Equal(grantee.Address.String(), msg.Grantee) + require.Len(futureOperations, 0) +} + +func TestSimTestSuite(t *testing.T) { + suite.Run(t, new(SimTestSuite)) +} From aa3086873e2802b3702725883e8d642625d846ea Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Mon, 19 Apr 2021 12:57:39 +0200 Subject: [PATCH 10/47] Remove commented code --- x/feegrant/module.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/x/feegrant/module.go b/x/feegrant/module.go index b3e99084d294..a6f1f4f62000 100644 --- a/x/feegrant/module.go +++ b/x/feegrant/module.go @@ -204,9 +204,8 @@ func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { // WeightedOperations returns all the feegrant module operations with their respective weights. func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - return nil - // protoCdc := codec.NewProtoCodec(am.registry) - // return simulation.WeightedOperations( - // simState.AppParams, simState.Cdc, am.accountKeeper, am.bankKeeper, am.keeper, protoCdc, - // ) + protoCdc := codec.NewProtoCodec(am.registry) + return simulation.WeightedOperations( + simState.AppParams, simState.Cdc, am.accountKeeper, am.bankKeeper, am.keeper, protoCdc, + ) } From 2919375b154a155efded129a68e96028b90f32d2 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Mon, 19 Apr 2021 12:59:26 +0200 Subject: [PATCH 11/47] // simulation.NewWeightedOperation( --- x/authz/simulation/operations.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x/authz/simulation/operations.go b/x/authz/simulation/operations.go index ffa833e6f24a..a6e60e150247 100644 --- a/x/authz/simulation/operations.go +++ b/x/authz/simulation/operations.go @@ -73,10 +73,10 @@ func WeightedOperations( weightRevokeAuthorization, SimulateMsgRevokeAuthorization(ak, bk, k, protoCdc), ), - // simulation.NewWeightedOperation( - // weightExecAuthorized, - // SimulateMsgExecuteAuthorized(ak, bk, k, appCdc, protoCdc), - // ), + simulation.NewWeightedOperation( + weightExecAuthorized, + SimulateMsgExecuteAuthorized(ak, bk, k, appCdc, protoCdc), + ), } } From 488dc62918dd7d3d6ec25d4d9f1e9b5fbef21eca Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Mon, 19 Apr 2021 13:06:49 +0200 Subject: [PATCH 12/47] Fix CopyTx --- client/tx/legacy_test.go | 12 ++++-------- x/authz/client/cli/tx_test.go | 2 ++ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/client/tx/legacy_test.go b/client/tx/legacy_test.go index 5dcab771c495..e3ac1e630ed1 100644 --- a/client/tx/legacy_test.go +++ b/client/tx/legacy_test.go @@ -13,7 +13,6 @@ import ( "github.com/cosmos/cosmos-sdk/simapp/params" "github.com/cosmos/cosmos-sdk/testutil/testdata" "github.com/cosmos/cosmos-sdk/types" - sdk "github.com/cosmos/cosmos-sdk/types" signing2 "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth/legacy/legacytx" "github.com/cosmos/cosmos-sdk/x/auth/signing" @@ -39,10 +38,7 @@ var ( }, } msg0 = banktypes.NewMsgSend(addr1, addr2, types.NewCoins(types.NewInt64Coin("wack", 1))) - msg1 = sdk.ServiceMsg{ - MethodName: "/cosmos.bank.v1beta1.Msg/Send", - Request: banktypes.NewMsgSend(addr1, addr2, types.NewCoins(types.NewInt64Coin("wack", 2))), - } + msg1 = banktypes.NewMsgSend(addr1, addr2, types.NewCoins(types.NewInt64Coin("wack", 2))) ) func buildTestTx(t *testing.T, builder client.TxBuilder) { @@ -88,7 +84,7 @@ func (s *TestSuite) TestCopyTx() { s.Require().Equal(sigsV2_1, sigsV2_2) s.Require().Equal(protoBuilder.GetTx().GetSigners(), protoBuilder2.GetTx().GetSigners()) s.Require().Equal(protoBuilder.GetTx().GetMsgs()[0], protoBuilder2.GetTx().GetMsgs()[0]) - s.Require().Equal(protoBuilder.GetTx().GetMsgs()[1].(sdk.ServiceMsg).Request, protoBuilder2.GetTx().GetMsgs()[1]) // We lose the "ServiceMsg" information + s.Require().Equal(protoBuilder.GetTx().GetMsgs()[1], protoBuilder2.GetTx().GetMsgs()[1]) // amino -> proto -> amino aminoBuilder = s.aminoCfg.NewTxBuilder() @@ -120,7 +116,7 @@ func (s *TestSuite) TestConvertTxToStdTx() { s.Require().Equal(gas, stdTx.Fee.Gas) s.Require().Equal(fee, stdTx.Fee.Amount) s.Require().Equal(msg0, stdTx.Msgs[0]) - s.Require().Equal(msg1.Request, stdTx.Msgs[1]) + s.Require().Equal(msg1, stdTx.Msgs[1]) s.Require().Equal(timeoutHeight, stdTx.TimeoutHeight) s.Require().Equal(sig.PubKey, stdTx.Signatures[0].PubKey) s.Require().Equal(sig.Data.(*signing2.SingleSignatureData).Signature, stdTx.Signatures[0].Signature) @@ -140,7 +136,7 @@ func (s *TestSuite) TestConvertTxToStdTx() { s.Require().Equal(gas, stdTx.Fee.Gas) s.Require().Equal(fee, stdTx.Fee.Amount) s.Require().Equal(msg0, stdTx.Msgs[0]) - s.Require().Equal(msg1.Request, stdTx.Msgs[1]) + s.Require().Equal(msg1, stdTx.Msgs[1]) s.Require().Equal(timeoutHeight, stdTx.TimeoutHeight) s.Require().Empty(stdTx.Signatures) diff --git a/x/authz/client/cli/tx_test.go b/x/authz/client/cli/tx_test.go index 7285ffed4200..c49dfe5dd226 100644 --- a/x/authz/client/cli/tx_test.go +++ b/x/authz/client/cli/tx_test.go @@ -1,3 +1,5 @@ +// +build norace + package cli_test import ( From 72c0cbc7b1206f2a5ba06cd654d710efb293f944 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Mon, 19 Apr 2021 13:42:31 +0200 Subject: [PATCH 13/47] Fix more tests --- baseapp/baseapp.go | 14 ++++++------ server/grpc/reflection/v2alpha1/reflection.go | 6 ++--- types/codec.go | 2 -- x/auth/tx/service_test.go | 22 +++++++++---------- x/feegrant/client/cli/cli_test.go | 2 +- 5 files changed, 22 insertions(+), 24 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index fc73c92c7a18..42980b07eee7 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -703,19 +703,19 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s } var ( - msgEvents sdk.Events - msgResult *sdk.Result - msgFqName string - err error + msgResult *sdk.Result + msgEventAction string // name to use as value in event `message.action` + err error ) if handler := app.msgServiceRouter.Handler(msg); handler != nil { // ADR 031 request type routing msgResult, err = handler(ctx, msg) + msgEventAction = proto.MessageName(msg) } else if legacyMsg, ok := msg.(sdk.LegacyMsg); ok { // legacy sdk.Msg routing msgRoute := legacyMsg.Route() - msgFqName = legacyMsg.Type() + msgEventAction = legacyMsg.Type() handler := app.router.Route(ctx, msgRoute) if handler == nil { return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message route: %s; message index: %d", msgRoute, i) @@ -730,8 +730,8 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s return nil, sdkerrors.Wrapf(err, "failed to execute message; message index: %d", i) } - msgEvents = sdk.Events{ - sdk.NewEvent(sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyAction, msgFqName)), + msgEvents := sdk.Events{ + sdk.NewEvent(sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyAction, msgEventAction)), } msgEvents = msgEvents.AppendEvents(msgResult.GetEvents()) diff --git a/server/grpc/reflection/v2alpha1/reflection.go b/server/grpc/reflection/v2alpha1/reflection.go index 728d54ad4bbc..e18295c90b33 100644 --- a/server/grpc/reflection/v2alpha1/reflection.go +++ b/server/grpc/reflection/v2alpha1/reflection.go @@ -169,12 +169,12 @@ func newTxDescriptor(ir codectypes.InterfaceRegistry) (*TxDescriptor, error) { return nil, fmt.Errorf("unable to get *tx.Tx protobuf name") } // get msgs - svcMsgImplementers := ir.ListImplementations(sdk.ServiceMsgInterfaceProtoName) + sdkMsgImplementers := ir.ListImplementations(sdk.MsgInterfaceProtoName) - msgsDesc := make([]*MsgDescriptor, 0, len(svcMsgImplementers)) + msgsDesc := make([]*MsgDescriptor, 0, len(sdkMsgImplementers)) // process sdk.ServiceMsg - for _, svcMsg := range svcMsgImplementers { + for _, svcMsg := range sdkMsgImplementers { resolved, err := ir.Resolve(svcMsg) if err != nil { return nil, fmt.Errorf("unable to resolve sdk.ServiceMsg %s: %w", svcMsg, err) diff --git a/types/codec.go b/types/codec.go index d6b6917085d5..69202d488fc6 100644 --- a/types/codec.go +++ b/types/codec.go @@ -8,8 +8,6 @@ import ( const ( // MsgInterfaceProtoName defines the protobuf name of the cosmos Msg interface MsgInterfaceProtoName = "cosmos.base.v1beta1.Msg" - // ServiceMsgInterfaceProtoName defines the protobuf name of the cosmos MsgRequest interface - ServiceMsgInterfaceProtoName = "cosmos.base.v1beta1.ServiceMsg" ) // RegisterLegacyAminoCodec registers the sdk message type. diff --git a/x/auth/tx/service_test.go b/x/auth/tx/service_test.go index 49f2d2828776..71eee62a759e 100644 --- a/x/auth/tx/service_test.go +++ b/x/auth/tx/service_test.go @@ -192,7 +192,7 @@ func (s IntegrationTestSuite) TestGetTxEvents_GRPC() { { "request with order-by", &tx.GetTxsEventRequest{ - Events: []string{"message.action='/cosmos.bank.v1beta1.MsgSend'"}, + Events: []string{"message.action='cosmos.bank.v1beta1.MsgSend'"}, OrderBy: tx.OrderBy_ORDER_BY_ASC, }, false, "", @@ -200,14 +200,14 @@ func (s IntegrationTestSuite) TestGetTxEvents_GRPC() { { "without pagination", &tx.GetTxsEventRequest{ - Events: []string{"message.action='/cosmos.bank.v1beta1.MsgSend'"}, + Events: []string{"message.action='cosmos.bank.v1beta1.MsgSend'"}, }, false, "", }, { "with pagination", &tx.GetTxsEventRequest{ - Events: []string{"message.action='/cosmos.bank.v1beta1.MsgSend'"}, + Events: []string{"message.action='cosmos.bank.v1beta1.MsgSend'"}, Pagination: &query.PageRequest{ CountTotal: false, Offset: 0, @@ -219,7 +219,7 @@ func (s IntegrationTestSuite) TestGetTxEvents_GRPC() { { "with multi events", &tx.GetTxsEventRequest{ - Events: []string{"message.action='/cosmos.bank.v1beta1.MsgSend'", "message.module='bank'"}, + Events: []string{"message.action='cosmos.bank.v1beta1.MsgSend'", "message.module='bank'"}, }, false, "", }, @@ -262,43 +262,43 @@ func (s IntegrationTestSuite) TestGetTxEvents_GRPCGateway() { }, { "without pagination", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s", val.APIAddress, "message.action='/cosmos.bank.v1beta1.MsgSend'"), + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s", val.APIAddress, "message.action='cosmos.bank.v1beta1.MsgSend'"), false, "", }, { "with pagination", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&pagination.offset=%d&pagination.limit=%d", val.APIAddress, "message.action='/cosmos.bank.v1beta1.MsgSend'", 0, 10), + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&pagination.offset=%d&pagination.limit=%d", val.APIAddress, "message.action='cosmos.bank.v1beta1.MsgSend'", 0, 10), false, "", }, { "valid request: order by asc", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s&order_by=ORDER_BY_ASC", val.APIAddress, "message.action='/cosmos.bank.v1beta1.MsgSend'", "message.module='bank'"), + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s&order_by=ORDER_BY_ASC", val.APIAddress, "message.action='cosmos.bank.v1beta1.MsgSend'", "message.module='bank'"), false, "", }, { "valid request: order by desc", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s&order_by=ORDER_BY_DESC", val.APIAddress, "message.action='/cosmos.bank.v1beta1.MsgSend'", "message.module='bank'"), + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s&order_by=ORDER_BY_DESC", val.APIAddress, "message.action='cosmos.bank.v1beta1.MsgSend'", "message.module='bank'"), false, "", }, { "invalid request: invalid order by", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s&order_by=invalid_order", val.APIAddress, "message.action='/cosmos.bank.v1beta1.MsgSend'", "message.module='bank'"), + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s&order_by=invalid_order", val.APIAddress, "message.action='cosmos.bank.v1beta1.MsgSend'", "message.module='bank'"), true, "is not a valid tx.OrderBy", }, { "expect pass with multiple-events", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s", val.APIAddress, "message.action='/cosmos.bank.v1beta1.MsgSend'", "message.module='bank'"), + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s", val.APIAddress, "message.action='cosmos.bank.v1beta1.MsgSend'", "message.module='bank'"), false, "", }, { "expect pass with escape event", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s", val.APIAddress, "message.action%3D'%2Fcosmos.bank.v1beta1.Msg%2FSend'"), + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s", val.APIAddress, "message.action%3D'cosmos.bank.v1beta1.MsgSend'"), false, "", }, diff --git a/x/feegrant/client/cli/cli_test.go b/x/feegrant/client/cli/cli_test.go index 787a342d18c3..94dff512bc25 100644 --- a/x/feegrant/client/cli/cli_test.go +++ b/x/feegrant/client/cli/cli_test.go @@ -639,7 +639,7 @@ func (s *IntegrationTestSuite) TestFilteredFeeAllowance() { } spendLimit := sdk.NewCoin("stake", sdk.NewInt(1000)) - allowMsgs := "/cosmos.gov.v1beta1.Msg/SubmitProposal" + allowMsgs := proto.MessageName(&govtypes.MsgSubmitProposal{}) testCases := []struct { name string From 04ba707772fc822b65e63c5d75d6ff98f61a552b Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Mon, 19 Apr 2021 14:15:56 +0200 Subject: [PATCH 14/47] Fix x/gov test --- types/tx_msg.go | 3 ++ x/gov/client/utils/query.go | 55 +++++++++++++++++++++++++++++++++---- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/types/tx_msg.go b/types/tx_msg.go index 8ce551bc5d72..91f86ec0294b 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -21,6 +21,9 @@ type ( GetSigners() []AccAddress } + // LegacyMsg defines the old interface a message must fulfill, containing + // Amino signing method and legacy router info. + // Deprecated: Please use `Msg` instead. LegacyMsg interface { Msg diff --git a/x/gov/client/utils/query.go b/x/gov/client/utils/query.go index 67b97444a06a..9c6c80420382 100644 --- a/x/gov/client/utils/query.go +++ b/x/gov/client/utils/query.go @@ -3,6 +3,8 @@ package utils import ( "fmt" + "github.com/gogo/protobuf/proto" + "github.com/cosmos/cosmos-sdk/client" sdk "github.com/cosmos/cosmos-sdk/types" authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" @@ -39,10 +41,16 @@ func (p Proposer) String() string { func QueryDepositsByTxQuery(clientCtx client.Context, params types.QueryProposalParams) ([]byte, error) { searchResult, err := combineEvents( clientCtx, defaultPage, + // Query legacy Msgs event action []string{ fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgDeposit), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalDeposit, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), }, + // Query proto Msgs event action + []string{ + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, proto.MessageName(&types.MsgDeposit{})), + fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalDeposit, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), + }, ) if err != nil { return nil, err @@ -82,19 +90,29 @@ func QueryVotesByTxQuery(clientCtx client.Context, params types.QueryProposalVot // query interrupted either if we collected enough votes or tx indexer run out of relevant txs for len(votes) < totalLimit { - // Search for both votes and weighted votes. + // Search for both (legacy) votes and weighted votes. searchResult, err := combineEvents( clientCtx, nextTxPage, - // Query Vote Msgs + // Query legacy Vote Msgs []string{ fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgVote), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), }, - // Query VoteWeighted Msgs + // Query Vote proto Msgs + []string{ + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, proto.MessageName(&types.MsgVote{})), + fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), + }, + // Query legacy VoteWeighted Msgs []string{ fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgVoteWeighted), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), }, + // Query VoteWeighted proto Msgs + []string{ + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, proto.MessageName(&types.MsgVoteWeighted{})), + fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), + }, ) if err != nil { return nil, err @@ -144,18 +162,30 @@ func QueryVotesByTxQuery(clientCtx client.Context, params types.QueryProposalVot func QueryVoteByTxQuery(clientCtx client.Context, params types.QueryVoteParams) ([]byte, error) { searchResult, err := combineEvents( clientCtx, defaultPage, - // Query Vote Msgs + // Query legacy Vote Msgs []string{ fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgVote), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, []byte(params.Voter.String())), }, - // Query VoteWeighted Msgs + // Query Vote proto Msgs + []string{ + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, proto.MessageName(&types.MsgVote{})), + fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, []byte(params.Voter.String())), + }, + // Query legacy VoteWeighted Msgs []string{ fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgVoteWeighted), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, []byte(params.Voter.String())), }, + // Query VoteWeighted proto Msgs + []string{ + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, proto.MessageName(&types.MsgVoteWeighted{})), + fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, []byte(params.Voter.String())), + }, ) if err != nil { return nil, err @@ -200,11 +230,18 @@ func QueryVoteByTxQuery(clientCtx client.Context, params types.QueryVoteParams) func QueryDepositByTxQuery(clientCtx client.Context, params types.QueryDepositParams) ([]byte, error) { searchResult, err := combineEvents( clientCtx, defaultPage, + // Query legacy Msgs event action []string{ fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgDeposit), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalDeposit, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, []byte(params.Depositor.String())), }, + // Query proto Msgs event action + []string{ + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, proto.MessageName(&types.MsgDeposit{})), + fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalDeposit, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, []byte(params.Depositor.String())), + }, ) if err != nil { return nil, err @@ -239,10 +276,16 @@ func QueryProposerByTxQuery(clientCtx client.Context, proposalID uint64) (Propos searchResult, err := combineEvents( clientCtx, defaultPage, + // Query legacy Msgs event action []string{ fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, types.TypeMsgSubmitProposal), fmt.Sprintf("%s.%s='%s'", types.EventTypeSubmitProposal, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", proposalID))), }, + // Query proto Msgs event action + []string{ + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, proto.MessageName(&types.MsgSubmitProposal{})), + fmt.Sprintf("%s.%s='%s'", types.EventTypeSubmitProposal, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", proposalID))), + }, ) if err != nil { return Proposer{}, err @@ -281,7 +324,7 @@ func QueryProposalByID(proposalID uint64, clientCtx client.Context, queryRoute s // // Tx are indexed in tendermint via their Msgs `Type()`, which can be: // - via legacy Msgs (amino or proto), their `Type()` is a custom string, -// - via ADR-031 service msgs, their `Type()` is the protobuf FQ method name. +// - via ADR-031 proto msgs, their `Type()` is the protobuf FQ method name. // In searching for events, we search for both `Type()`s, and we use the // `combineEvents` function here to merge events. func combineEvents(clientCtx client.Context, page int, eventGroups ...[]string) (*sdk.SearchTxsResult, error) { From 5f543a4317353497e3d2c898e8f65d01412c5a71 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Tue, 20 Apr 2021 11:43:20 +0200 Subject: [PATCH 15/47] proto.MessageName->sdk.MsgName --- baseapp/baseapp.go | 4 ++-- baseapp/msg_service_router.go | 6 +++--- server/rosetta/converter.go | 3 +-- types/simulation/types.go | 4 +--- types/tx_msg.go | 5 +++++ x/authz/client/cli/tx_test.go | 2 +- x/authz/client/rest/grpc_query_test.go | 3 +-- x/authz/keeper/keeper.go | 6 +++--- x/authz/keeper/keeper_test.go | 3 +-- x/authz/simulation/operations.go | 10 ++++------ x/bank/types/send_authorization.go | 4 +--- x/feegrant/client/cli/cli_test.go | 2 +- x/feegrant/simulation/operations.go | 8 +++----- x/feegrant/types/filtered_fee.go | 2 +- x/gov/client/utils/query.go | 16 +++++++--------- x/staking/types/authz.go | 7 +++---- x/staking/types/authz_test.go | 7 +++---- 17 files changed, 41 insertions(+), 51 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 42980b07eee7..7804aaa9b77b 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -711,7 +711,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s if handler := app.msgServiceRouter.Handler(msg); handler != nil { // ADR 031 request type routing msgResult, err = handler(ctx, msg) - msgEventAction = proto.MessageName(msg) + msgEventAction = sdk.MsgName(msg) } else if legacyMsg, ok := msg.(sdk.LegacyMsg); ok { // legacy sdk.Msg routing msgRoute := legacyMsg.Route() @@ -741,7 +741,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s // separate each result. events = events.AppendEvents(msgEvents) - txMsgData.Data = append(txMsgData.Data, &sdk.MsgData{MsgType: proto.MessageName(msg), Data: msgResult.Data}) + txMsgData.Data = append(txMsgData.Data, &sdk.MsgData{MsgType: sdk.MsgName(msg), Data: msgResult.Data}) msgLogs = append(msgLogs, sdk.NewABCIMessageLog(uint32(i), msgResult.Log, msgEvents)) } diff --git a/baseapp/msg_service_router.go b/baseapp/msg_service_router.go index fb2582e7011f..fd70e7381662 100644 --- a/baseapp/msg_service_router.go +++ b/baseapp/msg_service_router.go @@ -33,7 +33,7 @@ type MsgServiceHandler = func(ctx sdk.Context, req sdk.Msg) (*sdk.Result, error) // Handler returns the MsgServiceHandler for a given msg or nil if not found. func (msr *MsgServiceRouter) Handler(msg sdk.Msg) MsgServiceHandler { - return msr.routes[fmt.Sprintf("/%s", proto.MessageName(msg))] + return msr.routes[fmt.Sprintf("/%s", sdk.MsgName(msg))] } // HandlerByName returns the MsgServiceHandler for a given query route path or nil @@ -61,7 +61,7 @@ func (msr *MsgServiceRouter) RegisterService(sd *grpc.ServiceDesc, handler inter // This approach is maybe a bit hacky, but less hacky than reflecting on the handler object itself. // We use a no-op interceptor to avoid actually calling into the handler itself. _, _ = methodHandler(nil, context.Background(), func(i interface{}) error { - msg, ok := i.(proto.Message) + msg, ok := i.(sdk.Msg) if !ok { // We panic here because there is no other alternative and the app cannot be initialized correctly // this should only happen if there is a problem with code generation in which case the app won't @@ -69,7 +69,7 @@ func (msr *MsgServiceRouter) RegisterService(sd *grpc.ServiceDesc, handler inter panic(fmt.Errorf("can't register request type %T for service method %s", i, fqMethod)) } - msgName := proto.MessageName(msg) + msgName := sdk.MsgName(msg) requestTypeName = fmt.Sprintf("/%s", msgName) return nil }, func(_ context.Context, _ interface{}, _ *grpc.UnaryServerInfo, _ grpc.UnaryHandler) (interface{}, error) { diff --git a/server/rosetta/converter.go b/server/rosetta/converter.go index 4b103ded38d1..8351df0b0cc6 100644 --- a/server/rosetta/converter.go +++ b/server/rosetta/converter.go @@ -18,7 +18,6 @@ import ( "github.com/cosmos/cosmos-sdk/types/tx/signing" rosettatypes "github.com/coinbase/rosetta-sdk-go/types" - "github.com/gogo/protobuf/proto" crgerrs "github.com/tendermint/cosmos-rosetta-gateway/errors" abci "github.com/tendermint/tendermint/abci/types" tmtypes "github.com/tendermint/tendermint/types" @@ -241,7 +240,7 @@ func (c converter) Meta(msg sdk.Msg) (meta map[string]interface{}, err error) { // with the message proto name as type, and the raw fields // as metadata func (c converter) Ops(status string, msg sdk.Msg) ([]*rosettatypes.Operation, error) { - opName := proto.MessageName(msg) + opName := sdk.MsgName(msg) meta, err := c.Meta(msg) if err != nil { diff --git a/types/simulation/types.go b/types/simulation/types.go index 77b251d02007..d4838e09de01 100644 --- a/types/simulation/types.go +++ b/types/simulation/types.go @@ -5,8 +5,6 @@ import ( "math/rand" "time" - "github.com/gogo/protobuf/proto" - "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -78,7 +76,7 @@ func NewOperationMsgBasic(route, name, comment string, ok bool, msg []byte) Oper // NewOperationMsg - create a new operation message from sdk.Msg func NewOperationMsg(msg sdk.Msg, ok bool, comment string, cdc *codec.ProtoCodec) OperationMsg { - msgName := proto.MessageName(msg) + msgName := sdk.MsgName(msg) return NewOperationMsgBasic(msgName, msgName, comment, ok, sdk.GetLegacySignBytes(msg)) } diff --git a/types/tx_msg.go b/types/tx_msg.go index 91f86ec0294b..1cf439bd8274 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -97,3 +97,8 @@ func GetLegacySignBytes(msg Msg) []byte { legacyMsg := msg.(LegacyMsg) return legacyMsg.GetSignBytes() } + +// MsgName returns the protobuf MessageName of a sdk.Msg. +func MsgName(msg Msg) string { + return proto.MessageName(msg) +} diff --git a/x/authz/client/cli/tx_test.go b/x/authz/client/cli/tx_test.go index c49dfe5dd226..2e011788d390 100644 --- a/x/authz/client/cli/tx_test.go +++ b/x/authz/client/cli/tx_test.go @@ -82,7 +82,7 @@ func (s *IntegrationTestSuite) TearDownSuite() { } var typeMsgSend = bank.SendAuthorization{}.MethodName() -var typeMsgVote = proto.MessageName(&govtypes.MsgVote{}) +var typeMsgVote = sdk.MsgName(&govtypes.MsgVote{}) var commonFlags = []string{} diff --git a/x/authz/client/rest/grpc_query_test.go b/x/authz/client/rest/grpc_query_test.go index 3136ba72fb62..20a8643cfb14 100644 --- a/x/authz/client/rest/grpc_query_test.go +++ b/x/authz/client/rest/grpc_query_test.go @@ -7,7 +7,6 @@ import ( "testing" "time" - "github.com/gogo/protobuf/proto" "github.com/stretchr/testify/suite" "github.com/cosmos/cosmos-sdk/client/flags" @@ -32,7 +31,7 @@ type IntegrationTestSuite struct { } var typeMsgSend = banktypes.SendAuthorization{}.MethodName() -var typeMsgVote = proto.MessageName(&govtypes.MsgVote{}) +var typeMsgVote = sdk.MsgName(&govtypes.MsgVote{}) func (s *IntegrationTestSuite) SetupSuite() { s.T().Log("setting up integration test suite") diff --git a/x/authz/keeper/keeper.go b/x/authz/keeper/keeper.go index 059b771f34e6..7e20e0a90168 100644 --- a/x/authz/keeper/keeper.go +++ b/x/authz/keeper/keeper.go @@ -83,7 +83,7 @@ func (k Keeper) DispatchActions(ctx sdk.Context, grantee sdk.AccAddress, msgs [] } granter := signers[0] if !granter.Equals(grantee) { - authorization, _ := k.GetOrRevokeAuthorization(ctx, grantee, granter, proto.MessageName(msg)) + authorization, _ := k.GetOrRevokeAuthorization(ctx, grantee, granter, sdk.MsgName(msg)) if authorization == nil { return nil, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "authorization not found") } @@ -92,7 +92,7 @@ func (k Keeper) DispatchActions(ctx sdk.Context, grantee sdk.AccAddress, msgs [] return nil, err } if del { - err = k.Revoke(ctx, grantee, granter, proto.MessageName(msg)) + err = k.Revoke(ctx, grantee, granter, sdk.MsgName(msg)) if err != nil { return nil, err } @@ -106,7 +106,7 @@ func (k Keeper) DispatchActions(ctx sdk.Context, grantee sdk.AccAddress, msgs [] handler := k.router.Handler(msg) if handler == nil { - return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message route: %s", proto.MessageName(msg)) + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message route: %s", sdk.MsgName(msg)) } msgResult, err = handler(ctx, msg) diff --git a/x/authz/keeper/keeper_test.go b/x/authz/keeper/keeper_test.go index 42a3edef6f4c..9daac3ad783d 100644 --- a/x/authz/keeper/keeper_test.go +++ b/x/authz/keeper/keeper_test.go @@ -5,7 +5,6 @@ import ( "testing" "time" - proto "github.com/gogo/protobuf/proto" "github.com/stretchr/testify/suite" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" @@ -74,7 +73,7 @@ func (s *TestSuite) TestKeeper() { s.Require().Equal(authorization.MethodName(), banktypes.SendAuthorization{}.MethodName()) s.T().Log("verify fetching authorization with wrong msg type fails") - authorization, _ = app.AuthzKeeper.GetOrRevokeAuthorization(ctx, granteeAddr, granterAddr, proto.MessageName(&banktypes.MsgMultiSend{})) + authorization, _ = app.AuthzKeeper.GetOrRevokeAuthorization(ctx, granteeAddr, granterAddr, sdk.MsgName(&banktypes.MsgMultiSend{})) s.Require().Nil(authorization) s.T().Log("verify fetching authorization with wrong grantee fails") diff --git a/x/authz/simulation/operations.go b/x/authz/simulation/operations.go index a6e60e150247..692e6d656d3a 100644 --- a/x/authz/simulation/operations.go +++ b/x/authz/simulation/operations.go @@ -5,8 +5,6 @@ import ( "math/rand" "strings" - "github.com/gogo/protobuf/proto" - "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" cdctypes "github.com/cosmos/cosmos-sdk/codec/types" @@ -24,9 +22,9 @@ import ( // authz message types var ( // FIXME Remove `Request` suffix - TypeMsgGrantAuthorization = proto.MessageName(&types.MsgGrantAuthorizationRequest{}) - TypeMsgRevokeAuthorization = proto.MessageName(&types.MsgRevokeAuthorizationRequest{}) - TypeMsgExecDelegated = proto.MessageName(&types.MsgExecAuthorizedRequest{}) + TypeMsgGrantAuthorization = sdk.MsgName(&types.MsgGrantAuthorizationRequest{}) + TypeMsgRevokeAuthorization = sdk.MsgName(&types.MsgRevokeAuthorizationRequest{}) + TypeMsgExecDelegated = sdk.MsgName(&types.MsgExecAuthorizedRequest{}) ) // Simulation operation weights constants @@ -133,7 +131,7 @@ func SimulateMsgGrantAuthorization(ak types.AccountKeeper, bk types.BankKeeper, _, _, err = app.Deliver(txGen.TxEncoder(), tx) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, proto.MessageName(svcMsgClientConn.GetMsgs()[0]), "unable to deliver tx"), nil, err + return simtypes.NoOpMsg(types.ModuleName, sdk.MsgName(svcMsgClientConn.GetMsgs()[0]), "unable to deliver tx"), nil, err } return simtypes.NewOperationMsg(svcMsgClientConn.GetMsgs()[0], true, "", protoCdc), nil, err } diff --git a/x/bank/types/send_authorization.go b/x/bank/types/send_authorization.go index e3255a926d96..902259cad6e2 100644 --- a/x/bank/types/send_authorization.go +++ b/x/bank/types/send_authorization.go @@ -1,8 +1,6 @@ package types import ( - "github.com/gogo/protobuf/proto" - sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" authz "github.com/cosmos/cosmos-sdk/x/authz/exported" @@ -21,7 +19,7 @@ func NewSendAuthorization(spendLimit sdk.Coins) *SendAuthorization { // MethodName implements Authorization.MethodName. func (authorization SendAuthorization) MethodName() string { - return proto.MessageName(&MsgSend{}) + return sdk.MsgName(&MsgSend{}) } // Accept implements Authorization.Accept. diff --git a/x/feegrant/client/cli/cli_test.go b/x/feegrant/client/cli/cli_test.go index 94dff512bc25..157a1e0122a8 100644 --- a/x/feegrant/client/cli/cli_test.go +++ b/x/feegrant/client/cli/cli_test.go @@ -639,7 +639,7 @@ func (s *IntegrationTestSuite) TestFilteredFeeAllowance() { } spendLimit := sdk.NewCoin("stake", sdk.NewInt(1000)) - allowMsgs := proto.MessageName(&govtypes.MsgSubmitProposal{}) + allowMsgs := sdk.MsgName(&govtypes.MsgSubmitProposal{}) testCases := []struct { name string diff --git a/x/feegrant/simulation/operations.go b/x/feegrant/simulation/operations.go index c7a4a239b0d2..36fd91e5318a 100644 --- a/x/feegrant/simulation/operations.go +++ b/x/feegrant/simulation/operations.go @@ -5,8 +5,6 @@ import ( "math/rand" "time" - "github.com/gogo/protobuf/proto" - "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/simapp/helpers" @@ -26,8 +24,8 @@ const ( ) var ( - TypeMsgGrantFeeAllowance = proto.MessageName(&types.MsgGrantFeeAllowance{}) - TypeMsgRevokeFeeAllowance = proto.MessageName(&types.MsgRevokeFeeAllowance{}) + TypeMsgGrantFeeAllowance = sdk.MsgName(&types.MsgGrantFeeAllowance{}) + TypeMsgRevokeFeeAllowance = sdk.MsgName(&types.MsgRevokeFeeAllowance{}) ) func WeightedOperations( @@ -126,7 +124,7 @@ func SimulateMsgGrantFeeAllowance(ak types.AccountKeeper, bk types.BankKeeper, k _, _, err = app.Deliver(txGen.TxEncoder(), tx) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, proto.MessageName(svcMsgClientConn.GetMsgs()[0]), "unable to deliver tx"), nil, err + return simtypes.NoOpMsg(types.ModuleName, sdk.MsgName(svcMsgClientConn.GetMsgs()[0]), "unable to deliver tx"), nil, err } return simtypes.NewOperationMsg(svcMsgClientConn.GetMsgs()[0], true, "", protoCdc), nil, err } diff --git a/x/feegrant/types/filtered_fee.go b/x/feegrant/types/filtered_fee.go index dc4caad2601e..cb55c1ed24d9 100644 --- a/x/feegrant/types/filtered_fee.go +++ b/x/feegrant/types/filtered_fee.go @@ -81,7 +81,7 @@ func (a *AllowedMsgFeeAllowance) allMsgTypesAllowed(ctx sdk.Context, msgs []sdk. for _, msg := range msgs { ctx.GasMeter().ConsumeGas(gasCostPerIteration, "check msg") - if !msgsMap[proto.MessageName(msg)] { + if !msgsMap[sdk.MsgName(msg)] { return false } } diff --git a/x/gov/client/utils/query.go b/x/gov/client/utils/query.go index 9c6c80420382..f87058c2e13c 100644 --- a/x/gov/client/utils/query.go +++ b/x/gov/client/utils/query.go @@ -3,8 +3,6 @@ package utils import ( "fmt" - "github.com/gogo/protobuf/proto" - "github.com/cosmos/cosmos-sdk/client" sdk "github.com/cosmos/cosmos-sdk/types" authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" @@ -48,7 +46,7 @@ func QueryDepositsByTxQuery(clientCtx client.Context, params types.QueryProposal }, // Query proto Msgs event action []string{ - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, proto.MessageName(&types.MsgDeposit{})), + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgName(&types.MsgDeposit{})), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalDeposit, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), }, ) @@ -100,7 +98,7 @@ func QueryVotesByTxQuery(clientCtx client.Context, params types.QueryProposalVot }, // Query Vote proto Msgs []string{ - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, proto.MessageName(&types.MsgVote{})), + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgName(&types.MsgVote{})), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), }, // Query legacy VoteWeighted Msgs @@ -110,7 +108,7 @@ func QueryVotesByTxQuery(clientCtx client.Context, params types.QueryProposalVot }, // Query VoteWeighted proto Msgs []string{ - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, proto.MessageName(&types.MsgVoteWeighted{})), + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgName(&types.MsgVoteWeighted{})), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), }, ) @@ -170,7 +168,7 @@ func QueryVoteByTxQuery(clientCtx client.Context, params types.QueryVoteParams) }, // Query Vote proto Msgs []string{ - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, proto.MessageName(&types.MsgVote{})), + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgName(&types.MsgVote{})), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, []byte(params.Voter.String())), }, @@ -182,7 +180,7 @@ func QueryVoteByTxQuery(clientCtx client.Context, params types.QueryVoteParams) }, // Query VoteWeighted proto Msgs []string{ - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, proto.MessageName(&types.MsgVoteWeighted{})), + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgName(&types.MsgVoteWeighted{})), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, []byte(params.Voter.String())), }, @@ -238,7 +236,7 @@ func QueryDepositByTxQuery(clientCtx client.Context, params types.QueryDepositPa }, // Query proto Msgs event action []string{ - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, proto.MessageName(&types.MsgDeposit{})), + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgName(&types.MsgDeposit{})), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalDeposit, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, []byte(params.Depositor.String())), }, @@ -283,7 +281,7 @@ func QueryProposerByTxQuery(clientCtx client.Context, proposalID uint64) (Propos }, // Query proto Msgs event action []string{ - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, proto.MessageName(&types.MsgSubmitProposal{})), + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgName(&types.MsgSubmitProposal{})), fmt.Sprintf("%s.%s='%s'", types.EventTypeSubmitProposal, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", proposalID))), }, ) diff --git a/x/staking/types/authz.go b/x/staking/types/authz.go index d76c40b1c65a..bdec4e8bf3e7 100644 --- a/x/staking/types/authz.go +++ b/x/staking/types/authz.go @@ -4,7 +4,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" authz "github.com/cosmos/cosmos-sdk/x/authz/exported" - "github.com/gogo/protobuf/proto" ) // TODO: Revisit this once we have propoer gas fee framework. @@ -139,11 +138,11 @@ func validateAndBech32fy(allowed []sdk.ValAddress, denied []sdk.ValAddress) ([]s func normalizeAuthzType(authzType AuthorizationType) (string, error) { switch authzType { case AuthorizationType_AUTHORIZATION_TYPE_DELEGATE: - return proto.MessageName(&MsgDelegate{}), nil + return sdk.MsgName(&MsgDelegate{}), nil case AuthorizationType_AUTHORIZATION_TYPE_UNDELEGATE: - return proto.MessageName(&MsgUndelegate{}), nil + return sdk.MsgName(&MsgUndelegate{}), nil case AuthorizationType_AUTHORIZATION_TYPE_REDELEGATE: - return proto.MessageName(&MsgBeginRedelegate{}), nil + return sdk.MsgName(&MsgBeginRedelegate{}), nil default: return "", sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "unknown authorization type %T", authzType) } diff --git a/x/staking/types/authz_test.go b/x/staking/types/authz_test.go index c6fb9f3ed709..6cada8b8091c 100644 --- a/x/staking/types/authz_test.go +++ b/x/staking/types/authz_test.go @@ -3,7 +3,6 @@ package types_test import ( "testing" - "github.com/gogo/protobuf/proto" "github.com/stretchr/testify/require" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" @@ -33,7 +32,7 @@ func TestAuthzAuthorizations(t *testing.T) { // verify MethodName delAuth, err = stakingtypes.NewStakeAuthorization([]sdk.ValAddress{val1, val2}, []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE, &coin100) require.NoError(t, err) - require.Equal(t, delAuth.MethodName(), proto.MessageName(&stakingtypes.MsgDelegate{})) + require.Equal(t, delAuth.MethodName(), sdk.MsgName(&stakingtypes.MsgDelegate{})) // error both allow & deny list _, err = stakingtypes.NewStakeAuthorization([]sdk.ValAddress{val1, val2}, []sdk.ValAddress{val1}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE, &coin100) @@ -41,11 +40,11 @@ func TestAuthzAuthorizations(t *testing.T) { // verify MethodName undelAuth, _ := stakingtypes.NewStakeAuthorization([]sdk.ValAddress{val1, val2}, []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_UNDELEGATE, &coin100) - require.Equal(t, undelAuth.MethodName(), proto.MessageName(&stakingtypes.MsgUndelegate{})) + require.Equal(t, undelAuth.MethodName(), sdk.MsgName(&stakingtypes.MsgUndelegate{})) // verify MethodName beginRedelAuth, _ := stakingtypes.NewStakeAuthorization([]sdk.ValAddress{val1, val2}, []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_REDELEGATE, &coin100) - require.Equal(t, beginRedelAuth.MethodName(), proto.MessageName(&stakingtypes.MsgBeginRedelegate{})) + require.Equal(t, beginRedelAuth.MethodName(), sdk.MsgName(&stakingtypes.MsgBeginRedelegate{})) validators1_2 := []string{val1.String(), val2.String()} From 6d1b1cc9f2ae02881e4ab3715253900a3a7cc7b4 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Tue, 20 Apr 2021 12:46:20 +0200 Subject: [PATCH 16/47] Fix authz tests --- baseapp/msg_service_router.go | 5 +- types/tx_msg.go | 7 +++ x/authz/client/cli/tx_test.go | 57 ++++++++++----------- x/authz/client/rest/grpc_query_test.go | 13 ++--- x/authz/keeper/keeper.go | 6 +-- x/authz/keeper/keeper_test.go | 1 - x/authz/simulation/operations.go | 8 +-- x/authz/types/generic_authorization_test.go | 6 +-- x/bank/types/send_authorization.go | 2 +- x/staking/types/authz.go | 6 +-- 10 files changed, 54 insertions(+), 57 deletions(-) diff --git a/baseapp/msg_service_router.go b/baseapp/msg_service_router.go index fd70e7381662..f681e8f0b466 100644 --- a/baseapp/msg_service_router.go +++ b/baseapp/msg_service_router.go @@ -33,7 +33,7 @@ type MsgServiceHandler = func(ctx sdk.Context, req sdk.Msg) (*sdk.Result, error) // Handler returns the MsgServiceHandler for a given msg or nil if not found. func (msr *MsgServiceRouter) Handler(msg sdk.Msg) MsgServiceHandler { - return msr.routes[fmt.Sprintf("/%s", sdk.MsgName(msg))] + return msr.routes[sdk.MsgRoute(msg)] } // HandlerByName returns the MsgServiceHandler for a given query route path or nil @@ -69,8 +69,7 @@ func (msr *MsgServiceRouter) RegisterService(sd *grpc.ServiceDesc, handler inter panic(fmt.Errorf("can't register request type %T for service method %s", i, fqMethod)) } - msgName := sdk.MsgName(msg) - requestTypeName = fmt.Sprintf("/%s", msgName) + requestTypeName = sdk.MsgRoute(msg) return nil }, func(_ context.Context, _ interface{}, _ *grpc.UnaryServerInfo, _ grpc.UnaryHandler) (interface{}, error) { return nil, nil diff --git a/types/tx_msg.go b/types/tx_msg.go index 1cf439bd8274..60ec576e8a16 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -1,6 +1,8 @@ package types import ( + fmt "fmt" + "github.com/gogo/protobuf/proto" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" @@ -102,3 +104,8 @@ func GetLegacySignBytes(msg Msg) []byte { func MsgName(msg Msg) string { return proto.MessageName(msg) } + +// MsgRoute returns the the key of a sdk.Msg in the BaseApp msg_service router. +func MsgRoute(msg Msg) string { + return fmt.Sprintf("/%s", MsgName(msg)) +} diff --git a/x/authz/client/cli/tx_test.go b/x/authz/client/cli/tx_test.go index 2e011788d390..b59cb4d0836a 100644 --- a/x/authz/client/cli/tx_test.go +++ b/x/authz/client/cli/tx_test.go @@ -82,9 +82,7 @@ func (s *IntegrationTestSuite) TearDownSuite() { } var typeMsgSend = bank.SendAuthorization{}.MethodName() -var typeMsgVote = sdk.MsgName(&govtypes.MsgVote{}) - -var commonFlags = []string{} +var typeMsgVote = sdk.MsgRoute(&govtypes.MsgVote{}) func (s *IntegrationTestSuite) TestCLITxGrantAuthorization() { val := s.network.Validators[0] @@ -96,7 +94,6 @@ func (s *IntegrationTestSuite) TestCLITxGrantAuthorization() { testCases := []struct { name string args []string - respType proto.Message expectedCode uint32 expectErr bool }{ @@ -110,7 +107,6 @@ func (s *IntegrationTestSuite) TestCLITxGrantAuthorization() { fmt.Sprintf("--%s=true", flags.FlagGenerateOnly), fmt.Sprintf("--%s=%d", cli.FlagExpiration, twoHours), }, - nil, 0, true, }, @@ -124,7 +120,7 @@ func (s *IntegrationTestSuite) TestCLITxGrantAuthorization() { fmt.Sprintf("--%s=true", flags.FlagGenerateOnly), fmt.Sprintf("--%s=%d", cli.FlagExpiration, twoHours), }, - nil, 0, + 0, true, }, { @@ -137,7 +133,7 @@ func (s *IntegrationTestSuite) TestCLITxGrantAuthorization() { fmt.Sprintf("--%s=true", flags.FlagGenerateOnly), fmt.Sprintf("--%s=%d", cli.FlagExpiration, pastHour), }, - nil, 0, + 0, true, }, { @@ -152,8 +148,8 @@ func (s *IntegrationTestSuite) TestCLITxGrantAuthorization() { fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), fmt.Sprintf("--%s=%d", cli.FlagExpiration, twoHours), }, - nil, 0, - true, + 0x1d, + false, }, { "failed with error both validators not allowed", @@ -165,11 +161,11 @@ func (s *IntegrationTestSuite) TestCLITxGrantAuthorization() { fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), fmt.Sprintf("--%s=%d", cli.FlagExpiration, twoHours), - fmt.Sprintf("--%s=%s", cli.FlagAllowedValidators, fmt.Sprintf("%s", val.ValAddress.String())), - fmt.Sprintf("--%s=%s", cli.FlagDenyValidators, fmt.Sprintf("%s", val.ValAddress.String())), + fmt.Sprintf("--%s=%s", cli.FlagAllowedValidators, val.ValAddress.String()), + fmt.Sprintf("--%s=%s", cli.FlagDenyValidators, val.ValAddress.String()), fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), }, - nil, 0, + 0, true, }, { @@ -182,10 +178,10 @@ func (s *IntegrationTestSuite) TestCLITxGrantAuthorization() { fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), fmt.Sprintf("--%s=%d", cli.FlagExpiration, twoHours), - fmt.Sprintf("--%s=%s", cli.FlagAllowedValidators, fmt.Sprintf("%s", val.ValAddress.String())), + fmt.Sprintf("--%s=%s", cli.FlagAllowedValidators, val.ValAddress.String()), fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), }, - &sdk.TxResponse{}, 0, + 0, false, }, { @@ -198,10 +194,10 @@ func (s *IntegrationTestSuite) TestCLITxGrantAuthorization() { fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), fmt.Sprintf("--%s=%d", cli.FlagExpiration, twoHours), - fmt.Sprintf("--%s=%s", cli.FlagDenyValidators, fmt.Sprintf("%s", val.ValAddress.String())), + fmt.Sprintf("--%s=%s", cli.FlagDenyValidators, val.ValAddress.String()), fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), }, - &sdk.TxResponse{}, 0, + 0, false, }, { @@ -214,10 +210,10 @@ func (s *IntegrationTestSuite) TestCLITxGrantAuthorization() { fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), fmt.Sprintf("--%s=%d", cli.FlagExpiration, twoHours), - fmt.Sprintf("--%s=%s", cli.FlagAllowedValidators, fmt.Sprintf("%s", val.ValAddress.String())), + fmt.Sprintf("--%s=%s", cli.FlagAllowedValidators, val.ValAddress.String()), fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), }, - &sdk.TxResponse{}, 0, + 0, false, }, { @@ -230,10 +226,10 @@ func (s *IntegrationTestSuite) TestCLITxGrantAuthorization() { fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), fmt.Sprintf("--%s=%d", cli.FlagExpiration, twoHours), - fmt.Sprintf("--%s=%s", cli.FlagAllowedValidators, fmt.Sprintf("%s", val.ValAddress.String())), + fmt.Sprintf("--%s=%s", cli.FlagAllowedValidators, val.ValAddress.String()), fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), }, - &sdk.TxResponse{}, 0, + 0, false, }, { @@ -248,7 +244,7 @@ func (s *IntegrationTestSuite) TestCLITxGrantAuthorization() { fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), }, - &sdk.TxResponse{}, 0, + 0, false, }, { @@ -263,7 +259,7 @@ func (s *IntegrationTestSuite) TestCLITxGrantAuthorization() { fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), }, - &sdk.TxResponse{}, 0, + 0, false, }, } @@ -279,9 +275,9 @@ func (s *IntegrationTestSuite) TestCLITxGrantAuthorization() { if tc.expectErr { s.Require().Error(err) } else { + var txResp sdk.TxResponse s.Require().NoError(err) - s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) - txResp := tc.respType.(*sdk.TxResponse) + s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &txResp), out.String()) s.Require().Equal(tc.expectedCode, txResp.Code, out.String()) } }) @@ -639,7 +635,7 @@ func (s *IntegrationTestSuite) TestExecDelegateAuthorization() { fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), fmt.Sprintf("--%s=%d", cli.FlagExpiration, twoHours), - fmt.Sprintf("--%s=%s", cli.FlagAllowedValidators, fmt.Sprintf("%s", val.ValAddress.String())), + fmt.Sprintf("--%s=%s", cli.FlagAllowedValidators, val.ValAddress.String()), fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), }, ) @@ -720,7 +716,7 @@ func (s *IntegrationTestSuite) TestExecDelegateAuthorization() { }) } - //test delegate no spend-limit + // test delegate no spend-limit _, err = authztestutil.ExecGrantAuthorization( val, []string{ @@ -730,7 +726,7 @@ func (s *IntegrationTestSuite) TestExecDelegateAuthorization() { fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), fmt.Sprintf("--%s=%d", cli.FlagExpiration, twoHours), - fmt.Sprintf("--%s=%s", cli.FlagAllowedValidators, fmt.Sprintf("%s", val.ValAddress.String())), + fmt.Sprintf("--%s=%s", cli.FlagAllowedValidators, val.ValAddress.String()), fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), }, ) @@ -808,7 +804,7 @@ func (s *IntegrationTestSuite) TestExecDelegateAuthorization() { fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), fmt.Sprintf("--%s=%d", cli.FlagExpiration, twoHours), - fmt.Sprintf("--%s=%s", cli.FlagDenyValidators, fmt.Sprintf("%s", val.ValAddress.String())), + fmt.Sprintf("--%s=%s", cli.FlagDenyValidators, val.ValAddress.String()), fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), }, ) @@ -825,7 +821,6 @@ func (s *IntegrationTestSuite) TestExecDelegateAuthorization() { out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args) s.Require().NoError(err) s.Contains(out.String(), fmt.Sprintf("cannot delegate/undelegate to %s validator", val.ValAddress.String())) - } func (s *IntegrationTestSuite) TestExecUndelegateAuthorization() { @@ -844,7 +839,7 @@ func (s *IntegrationTestSuite) TestExecUndelegateAuthorization() { fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), fmt.Sprintf("--%s=%d", cli.FlagExpiration, twoHours), - fmt.Sprintf("--%s=%s", cli.FlagAllowedValidators, fmt.Sprintf("%s", val.ValAddress.String())), + fmt.Sprintf("--%s=%s", cli.FlagAllowedValidators, val.ValAddress.String()), fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), }, ) @@ -949,7 +944,7 @@ func (s *IntegrationTestSuite) TestExecUndelegateAuthorization() { fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), fmt.Sprintf("--%s=%d", cli.FlagExpiration, twoHours), - fmt.Sprintf("--%s=%s", cli.FlagAllowedValidators, fmt.Sprintf("%s", val.ValAddress.String())), + fmt.Sprintf("--%s=%s", cli.FlagAllowedValidators, val.ValAddress.String()), fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), }, ) diff --git a/x/authz/client/rest/grpc_query_test.go b/x/authz/client/rest/grpc_query_test.go index 20a8643cfb14..7fbf9e5f9cf0 100644 --- a/x/authz/client/rest/grpc_query_test.go +++ b/x/authz/client/rest/grpc_query_test.go @@ -31,7 +31,7 @@ type IntegrationTestSuite struct { } var typeMsgSend = banktypes.SendAuthorization{}.MethodName() -var typeMsgVote = sdk.MsgName(&govtypes.MsgVote{}) +var typeMsgVote = sdk.MsgRoute(&govtypes.MsgVote{}) func (s *IntegrationTestSuite) SetupSuite() { s.T().Log("setting up integration test suite") @@ -49,7 +49,7 @@ func (s *IntegrationTestSuite) SetupSuite() { newAddr := sdk.AccAddress(info.GetPubKey().Address()) // Send some funds to the new account. - _, err = banktestutil.MsgSendExec( + out, err := banktestutil.MsgSendExec( val.ClientCtx, val.Address, newAddr, @@ -58,9 +58,10 @@ func (s *IntegrationTestSuite) SetupSuite() { fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), ) s.Require().NoError(err) + s.Require().Contains(out.String(), `"code":0`) // grant authorization - _, err = authztestutil.ExecGrantAuthorization(val, []string{ + out, err = authztestutil.ExecGrantAuthorization(val, []string{ newAddr.String(), "send", fmt.Sprintf("--%s=100steak", cli.FlagSpendLimit), @@ -71,6 +72,7 @@ func (s *IntegrationTestSuite) SetupSuite() { fmt.Sprintf("--%s=%d", cli.FlagExpiration, time.Now().Add(time.Minute*time.Duration(120)).Unix()), }) s.Require().NoError(err) + s.Require().Contains(out.String(), `"code":0`) s.grantee = newAddr _, err = s.network.WaitForHeight(1) @@ -186,10 +188,9 @@ func (s *IntegrationTestSuite) TestQueryAuthorizationsGRPC() { fmt.Sprintf("%s/cosmos/authz/v1beta1/granters/%s/grantees/%s/grants", baseURL, val.Address.String(), s.grantee.String()), false, "", - func() { - }, + func() {}, func(authorizations *types.QueryAuthorizationsResponse) { - s.Require().Equal(len(authorizations.Authorizations), 1) + s.Require().Equal(1, len(authorizations.Authorizations)) }, }, { diff --git a/x/authz/keeper/keeper.go b/x/authz/keeper/keeper.go index 7e20e0a90168..887b2a1f203d 100644 --- a/x/authz/keeper/keeper.go +++ b/x/authz/keeper/keeper.go @@ -83,7 +83,7 @@ func (k Keeper) DispatchActions(ctx sdk.Context, grantee sdk.AccAddress, msgs [] } granter := signers[0] if !granter.Equals(grantee) { - authorization, _ := k.GetOrRevokeAuthorization(ctx, grantee, granter, sdk.MsgName(msg)) + authorization, _ := k.GetOrRevokeAuthorization(ctx, grantee, granter, sdk.MsgRoute(msg)) if authorization == nil { return nil, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "authorization not found") } @@ -92,7 +92,7 @@ func (k Keeper) DispatchActions(ctx sdk.Context, grantee sdk.AccAddress, msgs [] return nil, err } if del { - err = k.Revoke(ctx, grantee, granter, sdk.MsgName(msg)) + err = k.Revoke(ctx, grantee, granter, sdk.MsgRoute(msg)) if err != nil { return nil, err } @@ -106,7 +106,7 @@ func (k Keeper) DispatchActions(ctx sdk.Context, grantee sdk.AccAddress, msgs [] handler := k.router.Handler(msg) if handler == nil { - return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message route: %s", sdk.MsgName(msg)) + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message route: %s", sdk.MsgRoute(msg)) } msgResult, err = handler(ctx, msg) diff --git a/x/authz/keeper/keeper_test.go b/x/authz/keeper/keeper_test.go index 9daac3ad783d..1864daa4ca92 100644 --- a/x/authz/keeper/keeper_test.go +++ b/x/authz/keeper/keeper_test.go @@ -39,7 +39,6 @@ func (s *TestSuite) SetupTest() { s.ctx = ctx s.queryClient = queryClient s.addrs = simapp.AddTestAddrsIncremental(app, ctx, 3, sdk.NewInt(30000000)) - } func (s *TestSuite) TestKeeper() { diff --git a/x/authz/simulation/operations.go b/x/authz/simulation/operations.go index 692e6d656d3a..e19925eafef5 100644 --- a/x/authz/simulation/operations.go +++ b/x/authz/simulation/operations.go @@ -22,9 +22,9 @@ import ( // authz message types var ( // FIXME Remove `Request` suffix - TypeMsgGrantAuthorization = sdk.MsgName(&types.MsgGrantAuthorizationRequest{}) - TypeMsgRevokeAuthorization = sdk.MsgName(&types.MsgRevokeAuthorizationRequest{}) - TypeMsgExecDelegated = sdk.MsgName(&types.MsgExecAuthorizedRequest{}) + TypeMsgGrantAuthorization = sdk.MsgRoute(&types.MsgGrantAuthorizationRequest{}) + TypeMsgRevokeAuthorization = sdk.MsgRoute(&types.MsgRevokeAuthorizationRequest{}) + TypeMsgExecDelegated = sdk.MsgRoute(&types.MsgExecAuthorizedRequest{}) ) // Simulation operation weights constants @@ -131,7 +131,7 @@ func SimulateMsgGrantAuthorization(ak types.AccountKeeper, bk types.BankKeeper, _, _, err = app.Deliver(txGen.TxEncoder(), tx) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgName(svcMsgClientConn.GetMsgs()[0]), "unable to deliver tx"), nil, err + return simtypes.NoOpMsg(types.ModuleName, sdk.MsgRoute(svcMsgClientConn.GetMsgs()[0]), "unable to deliver tx"), nil, err } return simtypes.NewOperationMsg(svcMsgClientConn.GetMsgs()[0], true, "", protoCdc), nil, err } diff --git a/x/authz/types/generic_authorization_test.go b/x/authz/types/generic_authorization_test.go index 2dda401c693f..a5ffe0803a81 100644 --- a/x/authz/types/generic_authorization_test.go +++ b/x/authz/types/generic_authorization_test.go @@ -10,12 +10,8 @@ import ( ) func TestGenericAuthorization(t *testing.T) { - t.Log("verify ValidateBasic returns error for non-service msg") - authorization := types.NewGenericAuthorization(banktypes.TypeMsgSend) - require.Error(t, authorization.ValidateBasic()) - t.Log("verify ValidateBasic returns nil for service msg") - authorization = types.NewGenericAuthorization(banktypes.SendAuthorization{}.MethodName()) + authorization := types.NewGenericAuthorization(banktypes.SendAuthorization{}.MethodName()) require.NoError(t, authorization.ValidateBasic()) require.Equal(t, banktypes.SendAuthorization{}.MethodName(), authorization.MessageName) } diff --git a/x/bank/types/send_authorization.go b/x/bank/types/send_authorization.go index 902259cad6e2..da9d3563000a 100644 --- a/x/bank/types/send_authorization.go +++ b/x/bank/types/send_authorization.go @@ -19,7 +19,7 @@ func NewSendAuthorization(spendLimit sdk.Coins) *SendAuthorization { // MethodName implements Authorization.MethodName. func (authorization SendAuthorization) MethodName() string { - return sdk.MsgName(&MsgSend{}) + return sdk.MsgRoute(&MsgSend{}) } // Accept implements Authorization.Accept. diff --git a/x/staking/types/authz.go b/x/staking/types/authz.go index bdec4e8bf3e7..d751e6a8e6b5 100644 --- a/x/staking/types/authz.go +++ b/x/staking/types/authz.go @@ -138,11 +138,11 @@ func validateAndBech32fy(allowed []sdk.ValAddress, denied []sdk.ValAddress) ([]s func normalizeAuthzType(authzType AuthorizationType) (string, error) { switch authzType { case AuthorizationType_AUTHORIZATION_TYPE_DELEGATE: - return sdk.MsgName(&MsgDelegate{}), nil + return sdk.MsgRoute(&MsgDelegate{}), nil case AuthorizationType_AUTHORIZATION_TYPE_UNDELEGATE: - return sdk.MsgName(&MsgUndelegate{}), nil + return sdk.MsgRoute(&MsgUndelegate{}), nil case AuthorizationType_AUTHORIZATION_TYPE_REDELEGATE: - return sdk.MsgName(&MsgBeginRedelegate{}), nil + return sdk.MsgRoute(&MsgBeginRedelegate{}), nil default: return "", sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "unknown authorization type %T", authzType) } From d4130c2f3dcb92de64e64b0653befdd7ea0d1658 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Tue, 20 Apr 2021 12:50:50 +0200 Subject: [PATCH 17/47] Use MsgRoute in feegrant and staking/authz --- x/authz/keeper/keeper_test.go | 2 +- x/feegrant/client/cli/cli_test.go | 2 +- x/feegrant/simulation/operations.go | 6 +++--- x/feegrant/types/filtered_fee.go | 2 +- x/staking/types/authz_test.go | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/x/authz/keeper/keeper_test.go b/x/authz/keeper/keeper_test.go index 1864daa4ca92..7b2fbef0c97a 100644 --- a/x/authz/keeper/keeper_test.go +++ b/x/authz/keeper/keeper_test.go @@ -72,7 +72,7 @@ func (s *TestSuite) TestKeeper() { s.Require().Equal(authorization.MethodName(), banktypes.SendAuthorization{}.MethodName()) s.T().Log("verify fetching authorization with wrong msg type fails") - authorization, _ = app.AuthzKeeper.GetOrRevokeAuthorization(ctx, granteeAddr, granterAddr, sdk.MsgName(&banktypes.MsgMultiSend{})) + authorization, _ = app.AuthzKeeper.GetOrRevokeAuthorization(ctx, granteeAddr, granterAddr, sdk.MsgRoute(&banktypes.MsgMultiSend{})) s.Require().Nil(authorization) s.T().Log("verify fetching authorization with wrong grantee fails") diff --git a/x/feegrant/client/cli/cli_test.go b/x/feegrant/client/cli/cli_test.go index 157a1e0122a8..904c4f9a84ee 100644 --- a/x/feegrant/client/cli/cli_test.go +++ b/x/feegrant/client/cli/cli_test.go @@ -639,7 +639,7 @@ func (s *IntegrationTestSuite) TestFilteredFeeAllowance() { } spendLimit := sdk.NewCoin("stake", sdk.NewInt(1000)) - allowMsgs := sdk.MsgName(&govtypes.MsgSubmitProposal{}) + allowMsgs := sdk.MsgRoute(&govtypes.MsgSubmitProposal{}) testCases := []struct { name string diff --git a/x/feegrant/simulation/operations.go b/x/feegrant/simulation/operations.go index 36fd91e5318a..51617bb614ba 100644 --- a/x/feegrant/simulation/operations.go +++ b/x/feegrant/simulation/operations.go @@ -24,8 +24,8 @@ const ( ) var ( - TypeMsgGrantFeeAllowance = sdk.MsgName(&types.MsgGrantFeeAllowance{}) - TypeMsgRevokeFeeAllowance = sdk.MsgName(&types.MsgRevokeFeeAllowance{}) + TypeMsgGrantFeeAllowance = sdk.MsgRoute(&types.MsgGrantFeeAllowance{}) + TypeMsgRevokeFeeAllowance = sdk.MsgRoute(&types.MsgRevokeFeeAllowance{}) ) func WeightedOperations( @@ -124,7 +124,7 @@ func SimulateMsgGrantFeeAllowance(ak types.AccountKeeper, bk types.BankKeeper, k _, _, err = app.Deliver(txGen.TxEncoder(), tx) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgName(svcMsgClientConn.GetMsgs()[0]), "unable to deliver tx"), nil, err + return simtypes.NoOpMsg(types.ModuleName, sdk.MsgRoute(svcMsgClientConn.GetMsgs()[0]), "unable to deliver tx"), nil, err } return simtypes.NewOperationMsg(svcMsgClientConn.GetMsgs()[0], true, "", protoCdc), nil, err } diff --git a/x/feegrant/types/filtered_fee.go b/x/feegrant/types/filtered_fee.go index cb55c1ed24d9..d6acc55dcf87 100644 --- a/x/feegrant/types/filtered_fee.go +++ b/x/feegrant/types/filtered_fee.go @@ -81,7 +81,7 @@ func (a *AllowedMsgFeeAllowance) allMsgTypesAllowed(ctx sdk.Context, msgs []sdk. for _, msg := range msgs { ctx.GasMeter().ConsumeGas(gasCostPerIteration, "check msg") - if !msgsMap[sdk.MsgName(msg)] { + if !msgsMap[sdk.MsgRoute(msg)] { return false } } diff --git a/x/staking/types/authz_test.go b/x/staking/types/authz_test.go index 6cada8b8091c..98596d6c1ffa 100644 --- a/x/staking/types/authz_test.go +++ b/x/staking/types/authz_test.go @@ -32,7 +32,7 @@ func TestAuthzAuthorizations(t *testing.T) { // verify MethodName delAuth, err = stakingtypes.NewStakeAuthorization([]sdk.ValAddress{val1, val2}, []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE, &coin100) require.NoError(t, err) - require.Equal(t, delAuth.MethodName(), sdk.MsgName(&stakingtypes.MsgDelegate{})) + require.Equal(t, delAuth.MethodName(), sdk.MsgRoute(&stakingtypes.MsgDelegate{})) // error both allow & deny list _, err = stakingtypes.NewStakeAuthorization([]sdk.ValAddress{val1, val2}, []sdk.ValAddress{val1}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE, &coin100) @@ -40,11 +40,11 @@ func TestAuthzAuthorizations(t *testing.T) { // verify MethodName undelAuth, _ := stakingtypes.NewStakeAuthorization([]sdk.ValAddress{val1, val2}, []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_UNDELEGATE, &coin100) - require.Equal(t, undelAuth.MethodName(), sdk.MsgName(&stakingtypes.MsgUndelegate{})) + require.Equal(t, undelAuth.MethodName(), sdk.MsgRoute(&stakingtypes.MsgUndelegate{})) // verify MethodName beginRedelAuth, _ := stakingtypes.NewStakeAuthorization([]sdk.ValAddress{val1, val2}, []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_REDELEGATE, &coin100) - require.Equal(t, beginRedelAuth.MethodName(), sdk.MsgName(&stakingtypes.MsgBeginRedelegate{})) + require.Equal(t, beginRedelAuth.MethodName(), sdk.MsgRoute(&stakingtypes.MsgBeginRedelegate{})) validators1_2 := []string{val1.String(), val2.String()} From 5f4a19cd56e6213e582e0370218232b33d5d95f4 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Tue, 20 Apr 2021 15:50:44 +0200 Subject: [PATCH 18/47] Fix more tests --- server/grpc/server_test.go | 47 +++++++++++++++++-- x/auth/client/cli/cli_test.go | 24 ++-------- x/auth/tx/service_test.go | 1 + x/bank/client/testutil/cli_helpers.go | 61 ------------------------- x/bank/types/send_authorization_test.go | 4 +- 5 files changed, 49 insertions(+), 88 deletions(-) diff --git a/server/grpc/server_test.go b/server/grpc/server_test.go index c7bd93982b18..ee45c0156a0b 100644 --- a/server/grpc/server_test.go +++ b/server/grpc/server_test.go @@ -17,6 +17,7 @@ import ( rpb "google.golang.org/grpc/reflection/grpc_reflection_v1alpha" reflectionv1 "github.com/cosmos/cosmos-sdk/client/grpc/reflection" + clienttx "github.com/cosmos/cosmos-sdk/client/tx" reflectionv2 "github.com/cosmos/cosmos-sdk/server/grpc/reflection/v2alpha1" "github.com/cosmos/cosmos-sdk/testutil/network" "github.com/cosmos/cosmos-sdk/testutil/testdata" @@ -24,7 +25,9 @@ import ( grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" "github.com/cosmos/cosmos-sdk/types/tx" txtypes "github.com/cosmos/cosmos-sdk/types/tx" - banktestutil "github.com/cosmos/cosmos-sdk/x/bank/client/testutil" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + authclient "github.com/cosmos/cosmos-sdk/x/auth/client" + "github.com/cosmos/cosmos-sdk/x/bank/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" ) @@ -161,9 +164,45 @@ func (s *IntegrationTestSuite) TestGRPCServer_GetTxsEvent() { func (s *IntegrationTestSuite) TestGRPCServer_BroadcastTx() { val0 := s.network.Validators[0] - grpcRes, err := banktestutil.LegacyGRPCProtoMsgSend(val0.ClientCtx, - val0.Moniker, val0.Address, val0.Address, - sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 10)}, sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 10)}, + // prepare txBuilder with msg + txBuilder := val0.ClientCtx.TxConfig.NewTxBuilder() + feeAmount := sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 10)} + gasLimit := testdata.NewTestGasLimit() + + // This sets a legacy Proto MsgSend. + err := txBuilder.SetMsgs(&types.MsgSend{ + FromAddress: val0.Address.String(), + ToAddress: val0.Address.String(), + Amount: sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 10)}, + }) + s.Require().NoError(err) + + txBuilder.SetFeeAmount(feeAmount) + txBuilder.SetGasLimit(gasLimit) + + // setup txFactory + txFactory := clienttx.Factory{}. + WithChainID(val0.ClientCtx.ChainID). + WithKeybase(val0.ClientCtx.Keyring). + WithTxConfig(val0.ClientCtx.TxConfig). + WithSignMode(signing.SignMode_SIGN_MODE_DIRECT) + + // Sign Tx. + err = authclient.SignTx(txFactory, val0.ClientCtx, val0.Moniker, txBuilder, false, true) + s.Require().NoError(err) + + txBytes, err := val0.ClientCtx.TxConfig.TxEncoder()(txBuilder.GetTx()) + s.Require().NoError(err) + + // Broadcast the tx via gRPC. + queryClient := txtypes.NewServiceClient(val0.ClientCtx) + + grpcRes, err := queryClient.BroadcastTx( + context.Background(), + &txtypes.BroadcastTxRequest{ + Mode: txtypes.BroadcastMode_BROADCAST_MODE_SYNC, + TxBytes: txBytes, + }, ) s.Require().NoError(err) s.Require().Equal(uint32(0), grpcRes.TxResponse.Code) diff --git a/x/auth/client/cli/cli_test.go b/x/auth/client/cli/cli_test.go index a0ed1572ab1c..ab59432dc833 100644 --- a/x/auth/client/cli/cli_test.go +++ b/x/auth/client/cli/cli_test.go @@ -250,19 +250,7 @@ func (s *IntegrationTestSuite) TestCLIQueryTxCmd() { sendTokens := sdk.NewInt64Coin(s.cfg.BondDenom, 10) - // Send coins, try both with legacy Msg and with Msg service. - // Legacy proto Msg. - legacyTxRes, err := bankcli.LegacyGRPCProtoMsgSend( - val.ClientCtx, val.Moniker, - val.Address, - account2.GetAddress(), - sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))), - sdk.NewCoins(sendTokens), - ) - s.Require().NoError(err) - s.Require().NoError(s.network.WaitForNextBlock()) - - // Service Msg. + // Send coins. out, err := s.createBankMsg( val, account2.GetAddress(), sdk.NewCoins(sendTokens), @@ -291,16 +279,10 @@ func (s *IntegrationTestSuite) TestCLIQueryTxCmd() { "", }, { - "happy case (sdk.LegacyMsg)", - []string{legacyTxRes.TxResponse.TxHash, fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, - false, - "", - }, - { - "happy case (sdk.Msg)", + "happy case", []string{txRes.TxHash, fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, false, - "/cosmos.bank.v1beta1.MsgSend", + "cosmos.bank.v1beta1.MsgSend", }, } diff --git a/x/auth/tx/service_test.go b/x/auth/tx/service_test.go index 71eee62a759e..9d89e706293e 100644 --- a/x/auth/tx/service_test.go +++ b/x/auth/tx/service_test.go @@ -584,6 +584,7 @@ func (s IntegrationTestSuite) mkTxBuilder() client.TxBuilder { ) txBuilder.SetFeeAmount(feeAmount) txBuilder.SetGasLimit(gasLimit) + txBuilder.SetMemo("foobar") // setup txFactory txFactory := clienttx.Factory{}. diff --git a/x/bank/client/testutil/cli_helpers.go b/x/bank/client/testutil/cli_helpers.go index 57a5a5798edc..a1a33c4f67bc 100644 --- a/x/bank/client/testutil/cli_helpers.go +++ b/x/bank/client/testutil/cli_helpers.go @@ -1,22 +1,14 @@ package testutil import ( - "context" "fmt" "github.com/tendermint/tendermint/libs/cli" "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/testutil" clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" - "github.com/cosmos/cosmos-sdk/testutil/testdata" - sdk "github.com/cosmos/cosmos-sdk/types" - txtypes "github.com/cosmos/cosmos-sdk/types/tx" - "github.com/cosmos/cosmos-sdk/types/tx/signing" - authclient "github.com/cosmos/cosmos-sdk/x/auth/client" bankcli "github.com/cosmos/cosmos-sdk/x/bank/client/cli" - "github.com/cosmos/cosmos-sdk/x/bank/types" ) func MsgSendExec(clientCtx client.Context, from, to, amount fmt.Stringer, extraArgs ...string) (testutil.BufferWriter, error) { @@ -32,56 +24,3 @@ func QueryBalancesExec(clientCtx client.Context, address fmt.Stringer, extraArgs return clitestutil.ExecTestCLICmd(clientCtx, bankcli.GetBalancesCmd(), args) } - -// LegacyGRPCProtoMsgSend is a legacy method to broadcast a legacy proto MsgSend. -// -// Deprecated. -//nolint:interfacer -func LegacyGRPCProtoMsgSend(clientCtx client.Context, keyName string, from, to sdk.Address, fee, amount []sdk.Coin, extraArgs ...string) (*txtypes.BroadcastTxResponse, error) { - // prepare txBuilder with msg - txBuilder := clientCtx.TxConfig.NewTxBuilder() - feeAmount := fee - gasLimit := testdata.NewTestGasLimit() - - // This sets a legacy Proto MsgSend. - err := txBuilder.SetMsgs(&types.MsgSend{ - FromAddress: from.String(), - ToAddress: to.String(), - Amount: amount, - }) - if err != nil { - return nil, err - } - - txBuilder.SetFeeAmount(feeAmount) - txBuilder.SetGasLimit(gasLimit) - - // setup txFactory - txFactory := tx.Factory{}. - WithChainID(clientCtx.ChainID). - WithKeybase(clientCtx.Keyring). - WithTxConfig(clientCtx.TxConfig). - WithSignMode(signing.SignMode_SIGN_MODE_DIRECT) - - // Sign Tx. - err = authclient.SignTx(txFactory, clientCtx, keyName, txBuilder, false, true) - if err != nil { - return nil, err - } - - txBytes, err := clientCtx.TxConfig.TxEncoder()(txBuilder.GetTx()) - if err != nil { - return nil, err - } - - // Broadcast the tx via gRPC. - queryClient := txtypes.NewServiceClient(clientCtx) - - return queryClient.BroadcastTx( - context.Background(), - &txtypes.BroadcastTxRequest{ - Mode: txtypes.BroadcastMode_BROADCAST_MODE_SYNC, - TxBytes: txBytes, - }, - ) -} diff --git a/x/bank/types/send_authorization_test.go b/x/bank/types/send_authorization_test.go index a58e9b6adf94..8ff979763612 100644 --- a/x/bank/types/send_authorization_test.go +++ b/x/bank/types/send_authorization_test.go @@ -24,7 +24,7 @@ func TestSendAuthorization(t *testing.T) { authorization := types.NewSendAuthorization(coins1000) t.Log("verify authorization returns valid method name") - require.Equal(t, authorization.MethodName(), "cosmos.bank.v1beta1.MsgSend") + require.Equal(t, authorization.MethodName(), "/cosmos.bank.v1beta1.MsgSend") require.NoError(t, authorization.ValidateBasic()) send := types.NewMsgSend(fromAddr, toAddr, coins1000) @@ -37,7 +37,7 @@ func TestSendAuthorization(t *testing.T) { require.Nil(t, updated) authorization = types.NewSendAuthorization(coins1000) - require.Equal(t, authorization.MethodName(), "cosmos.bank.v1beta1.MsgSend") + require.Equal(t, authorization.MethodName(), "/cosmos.bank.v1beta1.MsgSend") require.NoError(t, authorization.ValidateBasic()) send = types.NewMsgSend(fromAddr, toAddr, coins500) require.NoError(t, authorization.ValidateBasic()) From d5a2982e5ed1f131b48aeb80a0095ed613a6fca6 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Tue, 20 Apr 2021 16:13:26 +0200 Subject: [PATCH 19/47] Fix sims? --- types/simulation/types.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/types/simulation/types.go b/types/simulation/types.go index d4838e09de01..e38aebd1e5b1 100644 --- a/types/simulation/types.go +++ b/types/simulation/types.go @@ -76,8 +76,14 @@ func NewOperationMsgBasic(route, name, comment string, ok bool, msg []byte) Oper // NewOperationMsg - create a new operation message from sdk.Msg func NewOperationMsg(msg sdk.Msg, ok bool, comment string, cdc *codec.ProtoCodec) OperationMsg { - msgName := sdk.MsgName(msg) - return NewOperationMsgBasic(msgName, msgName, comment, ok, sdk.GetLegacySignBytes(msg)) + if legacyMsg, okType := msg.(sdk.LegacyMsg); okType { + return NewOperationMsgBasic(legacyMsg.Route(), legacyMsg.Type(), comment, ok, legacyMsg.GetSignBytes()) + } + + bz := cdc.MustMarshalJSON(msg) + + return NewOperationMsgBasic(sdk.MsgRoute(msg), sdk.MsgRoute(msg), comment, ok, bz) + } // NoOpMsg - create a no-operation message From 9743b9ef00301170db82255bbf006b4858539ce7 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Tue, 20 Apr 2021 16:16:48 +0200 Subject: [PATCH 20/47] Add norace tag --- x/auth/client/cli/cli_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x/auth/client/cli/cli_test.go b/x/auth/client/cli/cli_test.go index ab59432dc833..7bc01c2a6e71 100644 --- a/x/auth/client/cli/cli_test.go +++ b/x/auth/client/cli/cli_test.go @@ -1,3 +1,5 @@ +// +build norace + package cli_test import ( From a323bff2a1dde32b195204ec0c0a48403ab97e04 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Tue, 20 Apr 2021 16:37:18 +0200 Subject: [PATCH 21/47] Add CL --- CHANGELOG.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f53879536ffe..048c95be1560 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,8 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## [Unreleased] -## Features +### Features + * [\#8965](https://github.com/cosmos/cosmos-sdk/pull/8965) cosmos reflection now provides more information on the application such as: deliverable msgs, sdk.Config info etc (still in alpha stage). * [\#8559](https://github.com/cosmos/cosmos-sdk/pull/8559) Added Protobuf compatible secp256r1 ECDSA signatures. * [\#8786](https://github.com/cosmos/cosmos-sdk/pull/8786) Enabled secp256r1 in x/auth. @@ -45,6 +46,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [#9088](https://github.com/cosmos/cosmos-sdk/pull/9088) Added implementation to ADR-28 Derived Addresses. ### Client Breaking Changes + * [\#8363](https://github.com/cosmos/cosmos-sdk/pull/8363) Addresses no longer have a fixed 20-byte length. From the SDK modules' point of view, any 1-255 bytes-long byte array is a valid address. * [\#8346](https://github.com/cosmos/cosmos-sdk/pull/8346) All CLI `tx` commands generate ServiceMsgs by default. Graceful Amino support has been added to ServiceMsgs to support signing legacy Msgs. * (crypto/ed25519) [\#8690] Adopt zip1215 ed2559 verification rules. @@ -83,7 +85,10 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (auth/tx) [\#8926](https://github.com/cosmos/cosmos-sdk/pull/8926) The `ProtoTxProvider` interface used as a workaround for transaction simulation has been removed. * (x/bank) [\#8798](https://github.com/cosmos/cosmos-sdk/pull/8798) `GetTotalSupply` is removed in favour of `GetPaginatedTotalSupply` * (x/bank/types) [\#9061](https://github.com/cosmos/cosmos-sdk/pull/9061) `AddressFromBalancesStore` now returns an error for invalid key instead of panic. - +* [\#9139](https://github.com/cosmos/cosmos-sdk/pull/9139) ServiceMsgs as defined in ADR-031 have been removed, so that the SDK adheres to the Protobuf spec of `Any` packing. This has multiple consequences: + * The `sdk.ServiceMsg` interface has been removed. + * `sdk.Msg` now only contains `ValidateBasic` and `GetSigners` methods. The remaining methods `GetSignBytes`, `Route` and `Type` are moved to `sdk.LegacyMsg`. + * The `RegisterCustomTypeURL` function and the `cosmos.base.v1beta1.ServiceMsg` interface have been removed from the interface registry. ### State Machine Breaking From 14acc8479ad695c9fae0e539aae404d239fa970c Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Wed, 21 Apr 2021 01:27:08 +0200 Subject: [PATCH 22/47] rebuild rosetta api test data --- contrib/rosetta/node/data.tar.gz | Bin 36436 -> 38036 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/contrib/rosetta/node/data.tar.gz b/contrib/rosetta/node/data.tar.gz index d96715778251d8388d761d2bf3623bc96c99a8f3..43575efb8c8a8c1c2aaf94f053a62bb203ebc713 100644 GIT binary patch literal 38036 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

-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 literal 36436 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�+4S2|T{ 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 E0Ag?6Qvd(} From 0e57b6fde0440dbbee91faf64533ff1da0b5f42e Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Wed, 21 Apr 2021 12:48:22 +0200 Subject: [PATCH 23/47] Update ADR --- docs/architecture/adr-031-msg-service.md | 73 +++++------------------- 1 file changed, 14 insertions(+), 59 deletions(-) diff --git a/docs/architecture/adr-031-msg-service.md b/docs/architecture/adr-031-msg-service.md index a36d86c4312f..a4ff9cb54ac9 100644 --- a/docs/architecture/adr-031-msg-service.md +++ b/docs/architecture/adr-031-msg-service.md @@ -3,6 +3,7 @@ ## Changelog - 2020-10-05: Initial Draft +- 2021-04-21: Remove `ServiceMsg`s to follow Protobuf `Any`'s spec, see [#9063](https://github.com/cosmos/cosmos-sdk/issues/9063). ## Status @@ -96,71 +97,28 @@ On the client side, developers could take advantage of this by creating RPC impl logic. Protobuf libraries that use asynchronous callbacks, like [protobuf.js](https://github.com/protobufjs/protobuf.js#using-services) could use this to register callbacks for specific messages even for transactions that include multiple `Msg`s. -For backwards compatibility, existing `Msg` types should be used as the request parameter -for `service` definitions. Newer `Msg` types which only support `service` definitions -should use the more canonical `Msg...Request` names. +Each `Msg` service method should have exactly one request parameter: its corresponding `Msg` type. For example, the `Msg` service method `/cosmos.gov.v1beta1.Msg/SubmitProposal` above has exactly one request parameter, namely the `Msg` type `/cosmos.gov.v1beta1.MsgSubmitProposal`. It is important the reader understands clearly the nomenclature difference between a `Msg` service (a Protobuf service) and a `Msg` type (a Protobuf message), and the differences in their fully-qualified name. -### Encoding - -Currently, we are encoding `Msg`s as `Any` in `Tx`s which involves packing the -binary-encoded `Msg` with its type URL. +This convention has been decided over the more canonical `Msg...Request` names mainly for backwards compatibility, but also for better readability in `TxBody.messages` (see [Encoding section](#encoding) below): transactions containing `/cosmos.gov.MsgSubmitProposal` read better than those containing `/cosmos.gov.v1beta1.MsgSubmitProposalRequest`. -The type URL for `MsgSubmitProposal` based on the proto3 spec is `/cosmos.gov.MsgSubmitProposal`. +One consequence of this convention is that each `Msg` type can be the request parameter of only one `Msg` service method. However, we consider this limitation a good practice in explicitness. -The fully-qualified name for the `SubmitProposal` service method above (also -based on the proto3 and gRPC specs) is `/cosmos.gov.Msg/SubmitProposal` which varies -by a single `/` character. The generated `.pb.go` files for protobuf `service`s -include names of this form and any compliant protobuf/gRPC code generator will -generate the same name. +### Encoding -In order to encode service methods in transactions, we encode them as `Any`s in -the same `TxBody.messages` field as other `Msg`s. We simply set `Any.type_url` -to the full-qualified method name (ex. `/cosmos.gov.Msg/SubmitProposal`) and -set `Any.value` to the protobuf encoding of the request message -(`MsgSubmitProposal` in this case). +Encoding of transactions generated with `Msg` services do not differ from current Protobuf transaction encoding as defined in [ADR-020](./adr-020-protobuf-transaction-encoding.md). We are encoding `Msg` types (which are exactly `Msg` service methods' request parameters) as `Any` in `Tx`s which involves packing the +binary-encoded `Msg` with its type URL. ### Decoding -When decoding, `TxBody.UnpackInterfaces` will need a special case -to detect if `Any` type URLs match the service method format (ex. `/cosmos.gov.Msg/SubmitProposal`) -by checking for two `/` characters. Messages that are method names plus request parameters -instead of a normal `Any` messages will get unpacked into the `ServiceMsg` struct: - -```go -type ServiceMsg struct { - // MethodName is the fully-qualified service name - MethodName string - // Request is the request payload - Request MsgRequest -} -``` +Since `Msg` types are packed into `Any`, decoding transactions messages are done by unpacking `Any`s into `Msg` types. For more information, please refer to [ADR-020](./adr-020-protobuf-transaction-encoding.md#transactions). ### Routing -In the future, `service` definitions may become the primary method for defining -`Msg`s. As a starting point, we need to integrate with the SDK's existing routing -and `Msg` interface. - -To do this, `ServiceMsg` implements the `sdk.Msg` interface and its handler does the -actual method routing, allowing this feature to be added incrementally on top of -existing functionality. - -### `MsgRequest` interface - -All request messages will need to implement the `MsgRequest` interface which is a -simplified version of `Msg`, without `Route()`, `Type()` and `GetSignBytes()` which -are no longer needed: +We propose to add a `msg_service_router` in BaseApp. This router is a key/value map which maps `Msg` types' `type_url`s to their corresponding `Msg` service method handler. Since there is a 1-to-1 mapping between `Msg` types and `Msg` service method, the `msg_service_router` has exactly one entry per `Msg` service method. -```go -type MsgRequest interface { - proto.Message - ValidateBasic() error - GetSigners() []AccAddress -} -``` +When a transaction is processed by BaseApp (in CheckTx or in DeliverTx), its `TxBody.messages` are decoded as `Msg`s. Each `Msg`'s `type_url` is matched against an entry in the `msg_service_router`, and the respective `Msg` service method handler is called. -`ServiceMsg` will forward its `ValidateBasic` and `GetSigners` methods to the `MsgRequest` -methods. +For backward compatability, the old handlers are not removed yet. If BaseApp receives a legacy `Msg` with no correspoding entry in the `msg_service_router`, it will be routed via its legacy `Route()` method into the legacy handler. ### Module Configuration @@ -192,8 +150,8 @@ The `RegisterServices` method and the `Configurator` interface are intended to evolve to satisfy the use cases discussed in [\#7093](https://github.com/cosmos/cosmos-sdk/issues/7093) and [\#7122](https://github.com/cosmos/cosmos-sdk/issues/7421). -When `Msg` services are registered, the framework _should_ verify that all `Msg...Request` types -implement the `MsgRequest` interface described above and throw an error during initialization rather +When `Msg` services are registered, the framework _should_ verify that all `Msg` types +implement the `sdk.Msg` interface and throw an error during initialization rather than later when transactions are processed. ### `Msg` Service Implementation @@ -211,8 +169,7 @@ func (k Keeper) SubmitProposal(goCtx context.Context, params *types.MsgSubmitPro } ``` -The `sdk.Context` should have an `EventManager` already attached by the `ServiceMsg` -router. +The `sdk.Context` should have an `EventManager` already attached by BaseApp's `msg_service_router`. Separate handler definition is no longer needed with this approach. @@ -232,8 +189,6 @@ Finally, closing a module to client API opens desirable OCAP patterns discussed - dramatically reduces and simplifies the code ### Cons -- supporting both this and the current concrete `Msg` type approach simultaneously could be confusing -(we could choose to deprecate the current approach) - using `service` definitions outside the context of gRPC could be confusing (but doesn’t violate the proto3 spec) From 0840c18c40cbe5e5b21509a84f43ee744ab98cb7 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Wed, 21 Apr 2021 12:54:43 +0200 Subject: [PATCH 24/47] Rename MsgRoute -> MsgTypeURL --- baseapp/msg_service_router.go | 4 ++-- types/simulation/types.go | 2 +- types/tx_msg.go | 4 ++-- x/authz/client/cli/tx_test.go | 2 +- x/authz/client/rest/grpc_query_test.go | 2 +- x/authz/keeper/keeper.go | 6 +++--- x/authz/keeper/keeper_test.go | 2 +- x/authz/simulation/operations.go | 8 ++++---- x/bank/types/send_authorization.go | 2 +- x/feegrant/client/cli/cli_test.go | 2 +- x/feegrant/simulation/operations.go | 6 +++--- x/feegrant/types/filtered_fee.go | 2 +- x/staking/types/authz.go | 6 +++--- x/staking/types/authz_test.go | 6 +++--- 14 files changed, 27 insertions(+), 27 deletions(-) diff --git a/baseapp/msg_service_router.go b/baseapp/msg_service_router.go index f681e8f0b466..cea024f2a654 100644 --- a/baseapp/msg_service_router.go +++ b/baseapp/msg_service_router.go @@ -33,7 +33,7 @@ type MsgServiceHandler = func(ctx sdk.Context, req sdk.Msg) (*sdk.Result, error) // Handler returns the MsgServiceHandler for a given msg or nil if not found. func (msr *MsgServiceRouter) Handler(msg sdk.Msg) MsgServiceHandler { - return msr.routes[sdk.MsgRoute(msg)] + return msr.routes[sdk.MsgTypeURL(msg)] } // HandlerByName returns the MsgServiceHandler for a given query route path or nil @@ -69,7 +69,7 @@ func (msr *MsgServiceRouter) RegisterService(sd *grpc.ServiceDesc, handler inter panic(fmt.Errorf("can't register request type %T for service method %s", i, fqMethod)) } - requestTypeName = sdk.MsgRoute(msg) + requestTypeName = sdk.MsgTypeURL(msg) return nil }, func(_ context.Context, _ interface{}, _ *grpc.UnaryServerInfo, _ grpc.UnaryHandler) (interface{}, error) { return nil, nil diff --git a/types/simulation/types.go b/types/simulation/types.go index e38aebd1e5b1..84e97cfa1bb5 100644 --- a/types/simulation/types.go +++ b/types/simulation/types.go @@ -82,7 +82,7 @@ func NewOperationMsg(msg sdk.Msg, ok bool, comment string, cdc *codec.ProtoCodec bz := cdc.MustMarshalJSON(msg) - return NewOperationMsgBasic(sdk.MsgRoute(msg), sdk.MsgRoute(msg), comment, ok, bz) + return NewOperationMsgBasic(sdk.MsgTypeURL(msg), sdk.MsgTypeURL(msg), comment, ok, bz) } diff --git a/types/tx_msg.go b/types/tx_msg.go index 60ec576e8a16..e6b59e346fed 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -105,7 +105,7 @@ func MsgName(msg Msg) string { return proto.MessageName(msg) } -// MsgRoute returns the the key of a sdk.Msg in the BaseApp msg_service router. -func MsgRoute(msg Msg) string { +// MsgTypeURL returns the the key of a sdk.Msg in the BaseApp msg_service router. +func MsgTypeURL(msg Msg) string { return fmt.Sprintf("/%s", MsgName(msg)) } diff --git a/x/authz/client/cli/tx_test.go b/x/authz/client/cli/tx_test.go index b59cb4d0836a..a55959c56eca 100644 --- a/x/authz/client/cli/tx_test.go +++ b/x/authz/client/cli/tx_test.go @@ -82,7 +82,7 @@ func (s *IntegrationTestSuite) TearDownSuite() { } var typeMsgSend = bank.SendAuthorization{}.MethodName() -var typeMsgVote = sdk.MsgRoute(&govtypes.MsgVote{}) +var typeMsgVote = sdk.MsgTypeURL(&govtypes.MsgVote{}) func (s *IntegrationTestSuite) TestCLITxGrantAuthorization() { val := s.network.Validators[0] diff --git a/x/authz/client/rest/grpc_query_test.go b/x/authz/client/rest/grpc_query_test.go index 7fbf9e5f9cf0..88e1b54eeeee 100644 --- a/x/authz/client/rest/grpc_query_test.go +++ b/x/authz/client/rest/grpc_query_test.go @@ -31,7 +31,7 @@ type IntegrationTestSuite struct { } var typeMsgSend = banktypes.SendAuthorization{}.MethodName() -var typeMsgVote = sdk.MsgRoute(&govtypes.MsgVote{}) +var typeMsgVote = sdk.MsgTypeURL(&govtypes.MsgVote{}) func (s *IntegrationTestSuite) SetupSuite() { s.T().Log("setting up integration test suite") diff --git a/x/authz/keeper/keeper.go b/x/authz/keeper/keeper.go index 887b2a1f203d..ed178ca7b15d 100644 --- a/x/authz/keeper/keeper.go +++ b/x/authz/keeper/keeper.go @@ -83,7 +83,7 @@ func (k Keeper) DispatchActions(ctx sdk.Context, grantee sdk.AccAddress, msgs [] } granter := signers[0] if !granter.Equals(grantee) { - authorization, _ := k.GetOrRevokeAuthorization(ctx, grantee, granter, sdk.MsgRoute(msg)) + authorization, _ := k.GetOrRevokeAuthorization(ctx, grantee, granter, sdk.MsgTypeURL(msg)) if authorization == nil { return nil, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "authorization not found") } @@ -92,7 +92,7 @@ func (k Keeper) DispatchActions(ctx sdk.Context, grantee sdk.AccAddress, msgs [] return nil, err } if del { - err = k.Revoke(ctx, grantee, granter, sdk.MsgRoute(msg)) + err = k.Revoke(ctx, grantee, granter, sdk.MsgTypeURL(msg)) if err != nil { return nil, err } @@ -106,7 +106,7 @@ func (k Keeper) DispatchActions(ctx sdk.Context, grantee sdk.AccAddress, msgs [] handler := k.router.Handler(msg) if handler == nil { - return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message route: %s", sdk.MsgRoute(msg)) + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message route: %s", sdk.MsgTypeURL(msg)) } msgResult, err = handler(ctx, msg) diff --git a/x/authz/keeper/keeper_test.go b/x/authz/keeper/keeper_test.go index 7b2fbef0c97a..8c6308dc9ef1 100644 --- a/x/authz/keeper/keeper_test.go +++ b/x/authz/keeper/keeper_test.go @@ -72,7 +72,7 @@ func (s *TestSuite) TestKeeper() { s.Require().Equal(authorization.MethodName(), banktypes.SendAuthorization{}.MethodName()) s.T().Log("verify fetching authorization with wrong msg type fails") - authorization, _ = app.AuthzKeeper.GetOrRevokeAuthorization(ctx, granteeAddr, granterAddr, sdk.MsgRoute(&banktypes.MsgMultiSend{})) + authorization, _ = app.AuthzKeeper.GetOrRevokeAuthorization(ctx, granteeAddr, granterAddr, sdk.MsgTypeURL(&banktypes.MsgMultiSend{})) s.Require().Nil(authorization) s.T().Log("verify fetching authorization with wrong grantee fails") diff --git a/x/authz/simulation/operations.go b/x/authz/simulation/operations.go index e19925eafef5..d50a1295859d 100644 --- a/x/authz/simulation/operations.go +++ b/x/authz/simulation/operations.go @@ -22,9 +22,9 @@ import ( // authz message types var ( // FIXME Remove `Request` suffix - TypeMsgGrantAuthorization = sdk.MsgRoute(&types.MsgGrantAuthorizationRequest{}) - TypeMsgRevokeAuthorization = sdk.MsgRoute(&types.MsgRevokeAuthorizationRequest{}) - TypeMsgExecDelegated = sdk.MsgRoute(&types.MsgExecAuthorizedRequest{}) + TypeMsgGrantAuthorization = sdk.MsgTypeURL(&types.MsgGrantAuthorizationRequest{}) + TypeMsgRevokeAuthorization = sdk.MsgTypeURL(&types.MsgRevokeAuthorizationRequest{}) + TypeMsgExecDelegated = sdk.MsgTypeURL(&types.MsgExecAuthorizedRequest{}) ) // Simulation operation weights constants @@ -131,7 +131,7 @@ func SimulateMsgGrantAuthorization(ak types.AccountKeeper, bk types.BankKeeper, _, _, err = app.Deliver(txGen.TxEncoder(), tx) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgRoute(svcMsgClientConn.GetMsgs()[0]), "unable to deliver tx"), nil, err + return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(svcMsgClientConn.GetMsgs()[0]), "unable to deliver tx"), nil, err } return simtypes.NewOperationMsg(svcMsgClientConn.GetMsgs()[0], true, "", protoCdc), nil, err } diff --git a/x/bank/types/send_authorization.go b/x/bank/types/send_authorization.go index da9d3563000a..758526f64fa1 100644 --- a/x/bank/types/send_authorization.go +++ b/x/bank/types/send_authorization.go @@ -19,7 +19,7 @@ func NewSendAuthorization(spendLimit sdk.Coins) *SendAuthorization { // MethodName implements Authorization.MethodName. func (authorization SendAuthorization) MethodName() string { - return sdk.MsgRoute(&MsgSend{}) + return sdk.MsgTypeURL(&MsgSend{}) } // Accept implements Authorization.Accept. diff --git a/x/feegrant/client/cli/cli_test.go b/x/feegrant/client/cli/cli_test.go index 904c4f9a84ee..62240b358f7e 100644 --- a/x/feegrant/client/cli/cli_test.go +++ b/x/feegrant/client/cli/cli_test.go @@ -639,7 +639,7 @@ func (s *IntegrationTestSuite) TestFilteredFeeAllowance() { } spendLimit := sdk.NewCoin("stake", sdk.NewInt(1000)) - allowMsgs := sdk.MsgRoute(&govtypes.MsgSubmitProposal{}) + allowMsgs := sdk.MsgTypeURL(&govtypes.MsgSubmitProposal{}) testCases := []struct { name string diff --git a/x/feegrant/simulation/operations.go b/x/feegrant/simulation/operations.go index 51617bb614ba..ed4537c5905e 100644 --- a/x/feegrant/simulation/operations.go +++ b/x/feegrant/simulation/operations.go @@ -24,8 +24,8 @@ const ( ) var ( - TypeMsgGrantFeeAllowance = sdk.MsgRoute(&types.MsgGrantFeeAllowance{}) - TypeMsgRevokeFeeAllowance = sdk.MsgRoute(&types.MsgRevokeFeeAllowance{}) + TypeMsgGrantFeeAllowance = sdk.MsgTypeURL(&types.MsgGrantFeeAllowance{}) + TypeMsgRevokeFeeAllowance = sdk.MsgTypeURL(&types.MsgRevokeFeeAllowance{}) ) func WeightedOperations( @@ -124,7 +124,7 @@ func SimulateMsgGrantFeeAllowance(ak types.AccountKeeper, bk types.BankKeeper, k _, _, err = app.Deliver(txGen.TxEncoder(), tx) if err != nil { - return simtypes.NoOpMsg(types.ModuleName, sdk.MsgRoute(svcMsgClientConn.GetMsgs()[0]), "unable to deliver tx"), nil, err + return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(svcMsgClientConn.GetMsgs()[0]), "unable to deliver tx"), nil, err } return simtypes.NewOperationMsg(svcMsgClientConn.GetMsgs()[0], true, "", protoCdc), nil, err } diff --git a/x/feegrant/types/filtered_fee.go b/x/feegrant/types/filtered_fee.go index d6acc55dcf87..6cc0c34c318d 100644 --- a/x/feegrant/types/filtered_fee.go +++ b/x/feegrant/types/filtered_fee.go @@ -81,7 +81,7 @@ func (a *AllowedMsgFeeAllowance) allMsgTypesAllowed(ctx sdk.Context, msgs []sdk. for _, msg := range msgs { ctx.GasMeter().ConsumeGas(gasCostPerIteration, "check msg") - if !msgsMap[sdk.MsgRoute(msg)] { + if !msgsMap[sdk.MsgTypeURL(msg)] { return false } } diff --git a/x/staking/types/authz.go b/x/staking/types/authz.go index d751e6a8e6b5..45e0e4088d11 100644 --- a/x/staking/types/authz.go +++ b/x/staking/types/authz.go @@ -138,11 +138,11 @@ func validateAndBech32fy(allowed []sdk.ValAddress, denied []sdk.ValAddress) ([]s func normalizeAuthzType(authzType AuthorizationType) (string, error) { switch authzType { case AuthorizationType_AUTHORIZATION_TYPE_DELEGATE: - return sdk.MsgRoute(&MsgDelegate{}), nil + return sdk.MsgTypeURL(&MsgDelegate{}), nil case AuthorizationType_AUTHORIZATION_TYPE_UNDELEGATE: - return sdk.MsgRoute(&MsgUndelegate{}), nil + return sdk.MsgTypeURL(&MsgUndelegate{}), nil case AuthorizationType_AUTHORIZATION_TYPE_REDELEGATE: - return sdk.MsgRoute(&MsgBeginRedelegate{}), nil + return sdk.MsgTypeURL(&MsgBeginRedelegate{}), nil default: return "", sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "unknown authorization type %T", authzType) } diff --git a/x/staking/types/authz_test.go b/x/staking/types/authz_test.go index 98596d6c1ffa..31b95b702283 100644 --- a/x/staking/types/authz_test.go +++ b/x/staking/types/authz_test.go @@ -32,7 +32,7 @@ func TestAuthzAuthorizations(t *testing.T) { // verify MethodName delAuth, err = stakingtypes.NewStakeAuthorization([]sdk.ValAddress{val1, val2}, []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE, &coin100) require.NoError(t, err) - require.Equal(t, delAuth.MethodName(), sdk.MsgRoute(&stakingtypes.MsgDelegate{})) + require.Equal(t, delAuth.MethodName(), sdk.MsgTypeURL(&stakingtypes.MsgDelegate{})) // error both allow & deny list _, err = stakingtypes.NewStakeAuthorization([]sdk.ValAddress{val1, val2}, []sdk.ValAddress{val1}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_DELEGATE, &coin100) @@ -40,11 +40,11 @@ func TestAuthzAuthorizations(t *testing.T) { // verify MethodName undelAuth, _ := stakingtypes.NewStakeAuthorization([]sdk.ValAddress{val1, val2}, []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_UNDELEGATE, &coin100) - require.Equal(t, undelAuth.MethodName(), sdk.MsgRoute(&stakingtypes.MsgUndelegate{})) + require.Equal(t, undelAuth.MethodName(), sdk.MsgTypeURL(&stakingtypes.MsgUndelegate{})) // verify MethodName beginRedelAuth, _ := stakingtypes.NewStakeAuthorization([]sdk.ValAddress{val1, val2}, []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_REDELEGATE, &coin100) - require.Equal(t, beginRedelAuth.MethodName(), sdk.MsgRoute(&stakingtypes.MsgBeginRedelegate{})) + require.Equal(t, beginRedelAuth.MethodName(), sdk.MsgTypeURL(&stakingtypes.MsgBeginRedelegate{})) validators1_2 := []string{val1.String(), val2.String()} From 5cb430147f2477f1fd65e8de95cc3bd0b643d1b4 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Wed, 21 Apr 2021 13:01:53 +0200 Subject: [PATCH 25/47] Fix codec registration --- x/authz/types/codec.go | 7 +++++++ x/feegrant/types/codec.go | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/x/authz/types/codec.go b/x/authz/types/codec.go index bf2d4ef91d17..dd68cf0bc126 100644 --- a/x/authz/types/codec.go +++ b/x/authz/types/codec.go @@ -2,6 +2,7 @@ package types import ( types "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/msgservice" "github.com/cosmos/cosmos-sdk/x/authz/exported" bank "github.com/cosmos/cosmos-sdk/x/bank/types" @@ -10,6 +11,12 @@ import ( // RegisterInterfaces registers the interfaces types with the interface registry func RegisterInterfaces(registry types.InterfaceRegistry) { + registry.RegisterImplementations((*sdk.Msg)(nil), + &MsgGrantAuthorizationRequest{}, + &MsgRevokeAuthorizationRequest{}, + &MsgExecAuthorizedRequest{}, + ) + registry.RegisterInterface( "cosmos.authz.v1beta1.Authorization", (*exported.Authorization)(nil), diff --git a/x/feegrant/types/codec.go b/x/feegrant/types/codec.go index 80ff7525b2f1..a8a0adb69508 100644 --- a/x/feegrant/types/codec.go +++ b/x/feegrant/types/codec.go @@ -2,11 +2,17 @@ package types import ( "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/msgservice" ) // RegisterInterfaces registers the interfaces types with the interface registry func RegisterInterfaces(registry types.InterfaceRegistry) { + registry.RegisterImplementations((*sdk.Msg)(nil), + &MsgGrantFeeAllowance{}, + &MsgRevokeFeeAllowance{}, + ) + registry.RegisterInterface( "cosmos.feegrant.v1beta1.FeeAllowanceI", (*FeeAllowanceI)(nil), From fc37733aa24af053c7a2323a2baf7860438a4a58 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Wed, 21 Apr 2021 13:07:53 +0200 Subject: [PATCH 26/47] Remove sdk.GetLegacySignBytes --- types/tx_msg.go | 5 ----- x/auth/legacy/legacytx/stdsign.go | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/types/tx_msg.go b/types/tx_msg.go index e6b59e346fed..a441161c7f22 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -95,11 +95,6 @@ type TxDecoder func(txBytes []byte) (Tx, error) // TxEncoder marshals transaction to bytes type TxEncoder func(tx Tx) ([]byte, error) -func GetLegacySignBytes(msg Msg) []byte { - legacyMsg := msg.(LegacyMsg) - return legacyMsg.GetSignBytes() -} - // MsgName returns the protobuf MessageName of a sdk.Msg. func MsgName(msg Msg) string { return proto.MessageName(msg) diff --git a/x/auth/legacy/legacytx/stdsign.go b/x/auth/legacy/legacytx/stdsign.go index bb2b5cc10206..3a67601b34cc 100644 --- a/x/auth/legacy/legacytx/stdsign.go +++ b/x/auth/legacy/legacytx/stdsign.go @@ -38,7 +38,7 @@ func StdSignBytes(chainID string, accnum, sequence, timeout uint64, fee StdFee, // If msg is a legacy Msg, then GetSignBytes is implemented. // If msg is a ServiceMsg, then GetSignBytes has graceful support of // calling GetSignBytes from its underlying Msg. - msgsBytes = append(msgsBytes, json.RawMessage(sdk.GetLegacySignBytes(msg))) + msgsBytes = append(msgsBytes, json.RawMessage(msg.(sdk.LegacyMsg).GetSignBytes())) } bz, err := legacy.Cdc.MarshalJSON(StdSignDoc{ From 5d1836bdceba4228d635c7f167764ef2daaad171 Mon Sep 17 00:00:00 2001 From: Amaury <1293565+amaurym@users.noreply.github.com> Date: Wed, 21 Apr 2021 13:12:51 +0200 Subject: [PATCH 27/47] Update types/tx_msg.go --- types/tx_msg.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/tx_msg.go b/types/tx_msg.go index a441161c7f22..670fed564fab 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -100,7 +100,7 @@ func MsgName(msg Msg) string { return proto.MessageName(msg) } -// MsgTypeURL returns the the key of a sdk.Msg in the BaseApp msg_service router. +// MsgTypeURL returns the TypeURL of a `sdk.Msg`. func MsgTypeURL(msg Msg) string { return fmt.Sprintf("/%s", MsgName(msg)) } From fcaedf9c9cc181a48ba67757a6b97aacf677f6e5 Mon Sep 17 00:00:00 2001 From: Amaury <1293565+amaurym@users.noreply.github.com> Date: Wed, 21 Apr 2021 13:13:47 +0200 Subject: [PATCH 28/47] Update x/authz/simulation/operations.go --- x/authz/simulation/operations.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x/authz/simulation/operations.go b/x/authz/simulation/operations.go index d50a1295859d..c663bb002307 100644 --- a/x/authz/simulation/operations.go +++ b/x/authz/simulation/operations.go @@ -21,7 +21,8 @@ import ( // authz message types var ( - // FIXME Remove `Request` suffix + // TODO Remove `Request` suffix + // https://github.com/cosmos/cosmos-sdk/issues/9114 TypeMsgGrantAuthorization = sdk.MsgTypeURL(&types.MsgGrantAuthorizationRequest{}) TypeMsgRevokeAuthorization = sdk.MsgTypeURL(&types.MsgRevokeAuthorizationRequest{}) TypeMsgExecDelegated = sdk.MsgTypeURL(&types.MsgExecAuthorizedRequest{}) From 7904aef6b04b37364c9a55c74f31dbd0d4b8afbd Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Wed, 21 Apr 2021 15:03:01 +0200 Subject: [PATCH 29/47] Move LegacyMsg to legacytx --- CHANGELOG.md | 2 +- baseapp/baseapp.go | 3 ++- baseapp/msg_service_router.go | 4 ++-- types/simulation/types.go | 3 ++- types/tx_msg.go | 18 ------------------ x/auth/legacy/legacytx/stdsign.go | 20 +++++++++++++++++++- x/authz/keeper/msg_server.go | 2 +- x/evidence/types/msgs_test.go | 5 +++-- x/genutil/client/cli/gentx_test.go | 3 ++- x/gov/client/utils/query_test.go | 3 ++- 10 files changed, 34 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 048c95be1560..1b3f982463c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,7 +87,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (x/bank/types) [\#9061](https://github.com/cosmos/cosmos-sdk/pull/9061) `AddressFromBalancesStore` now returns an error for invalid key instead of panic. * [\#9139](https://github.com/cosmos/cosmos-sdk/pull/9139) ServiceMsgs as defined in ADR-031 have been removed, so that the SDK adheres to the Protobuf spec of `Any` packing. This has multiple consequences: * The `sdk.ServiceMsg` interface has been removed. - * `sdk.Msg` now only contains `ValidateBasic` and `GetSigners` methods. The remaining methods `GetSignBytes`, `Route` and `Type` are moved to `sdk.LegacyMsg`. + * `sdk.Msg` now only contains `ValidateBasic` and `GetSigners` methods. The remaining methods `GetSignBytes`, `Route` and `Type` are moved to `legacytx.LegacyMsg`. * The `RegisterCustomTypeURL` function and the `cosmos.base.v1beta1.ServiceMsg` interface have been removed from the interface registry. diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 7804aaa9b77b..9b1e0242415f 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -19,6 +19,7 @@ import ( "github.com/cosmos/cosmos-sdk/store/rootmulti" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/auth/legacy/legacytx" ) const ( @@ -712,7 +713,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s // ADR 031 request type routing msgResult, err = handler(ctx, msg) msgEventAction = sdk.MsgName(msg) - } else if legacyMsg, ok := msg.(sdk.LegacyMsg); ok { + } else if legacyMsg, ok := msg.(legacytx.LegacyMsg); ok { // legacy sdk.Msg routing msgRoute := legacyMsg.Route() msgEventAction = legacyMsg.Type() diff --git a/baseapp/msg_service_router.go b/baseapp/msg_service_router.go index cea024f2a654..5a91944c7bea 100644 --- a/baseapp/msg_service_router.go +++ b/baseapp/msg_service_router.go @@ -36,9 +36,9 @@ func (msr *MsgServiceRouter) Handler(msg sdk.Msg) MsgServiceHandler { return msr.routes[sdk.MsgTypeURL(msg)] } -// HandlerByName returns the MsgServiceHandler for a given query route path or nil +// HandlerbyTypeURL returns the MsgServiceHandler for a given query route path or nil // if not found. -func (msr *MsgServiceRouter) HandlerByName(msgName string) MsgServiceHandler { +func (msr *MsgServiceRouter) HandlerbyTypeURL(msgName string) MsgServiceHandler { return msr.routes[msgName] } diff --git a/types/simulation/types.go b/types/simulation/types.go index 84e97cfa1bb5..d61e3acb931d 100644 --- a/types/simulation/types.go +++ b/types/simulation/types.go @@ -8,6 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/legacy/legacytx" ) type WeightedProposalContent interface { @@ -76,7 +77,7 @@ func NewOperationMsgBasic(route, name, comment string, ok bool, msg []byte) Oper // NewOperationMsg - create a new operation message from sdk.Msg func NewOperationMsg(msg sdk.Msg, ok bool, comment string, cdc *codec.ProtoCodec) OperationMsg { - if legacyMsg, okType := msg.(sdk.LegacyMsg); okType { + if legacyMsg, okType := msg.(legacytx.LegacyMsg); okType { return NewOperationMsgBasic(legacyMsg.Route(), legacyMsg.Type(), comment, ok, legacyMsg.GetSignBytes()) } diff --git a/types/tx_msg.go b/types/tx_msg.go index 670fed564fab..38eaa7e30cb8 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -23,24 +23,6 @@ type ( GetSigners() []AccAddress } - // LegacyMsg defines the old interface a message must fulfill, containing - // Amino signing method and legacy router info. - // Deprecated: Please use `Msg` instead. - LegacyMsg interface { - Msg - - // Get the canonical byte representation of the Msg. - GetSignBytes() []byte - - // Return the message type. - // Must be alphanumeric or empty. - Route() string - - // Returns a human-readable string for the message, intended for utilization - // within tags - Type() string - } - // Fee defines an interface for an application application-defined concrete // transaction type to be able to set and return the transaction fee. Fee interface { diff --git a/x/auth/legacy/legacytx/stdsign.go b/x/auth/legacy/legacytx/stdsign.go index 3a67601b34cc..8ab268bad815 100644 --- a/x/auth/legacy/legacytx/stdsign.go +++ b/x/auth/legacy/legacytx/stdsign.go @@ -16,6 +16,24 @@ import ( "github.com/cosmos/cosmos-sdk/types/tx/signing" ) +// LegacyMsg defines the old interface a message must fulfill, containing +// Amino signing method and legacy router info. +// Deprecated: Please use `Msg` instead. +type LegacyMsg interface { + sdk.Msg + + // Get the canonical byte representation of the Msg. + GetSignBytes() []byte + + // Return the message type. + // Must be alphanumeric or empty. + Route() string + + // Returns a human-readable string for the message, intended for utilization + // within tags + Type() string +} + // StdSignDoc is replay-prevention structure. // It includes the result of msg.GetSignBytes(), // as well as the ChainID (prevent cross chain replay) @@ -38,7 +56,7 @@ func StdSignBytes(chainID string, accnum, sequence, timeout uint64, fee StdFee, // If msg is a legacy Msg, then GetSignBytes is implemented. // If msg is a ServiceMsg, then GetSignBytes has graceful support of // calling GetSignBytes from its underlying Msg. - msgsBytes = append(msgsBytes, json.RawMessage(msg.(sdk.LegacyMsg).GetSignBytes())) + msgsBytes = append(msgsBytes, json.RawMessage(msg.(LegacyMsg).GetSignBytes())) } bz, err := legacy.Cdc.MarshalJSON(StdSignDoc{ diff --git a/x/authz/keeper/msg_server.go b/x/authz/keeper/msg_server.go index 9f3fe3b00ae6..85c5ff262b58 100644 --- a/x/authz/keeper/msg_server.go +++ b/x/authz/keeper/msg_server.go @@ -24,7 +24,7 @@ func (k Keeper) GrantAuthorization(goCtx context.Context, msg *types.MsgGrantAut authorization := msg.GetGrantAuthorization() // If the granted service Msg doesn't exist, we throw an error. - if k.router.HandlerByName(authorization.MethodName()) == nil { + if k.router.HandlerbyTypeURL(authorization.MethodName()) == nil { return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "%s doesn't exist.", authorization.MethodName()) } diff --git a/x/evidence/types/msgs_test.go b/x/evidence/types/msgs_test.go index 6f5d6ab522c4..ee1ed509bca5 100644 --- a/x/evidence/types/msgs_test.go +++ b/x/evidence/types/msgs_test.go @@ -8,6 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/legacy/legacytx" "github.com/cosmos/cosmos-sdk/x/evidence/exported" "github.com/cosmos/cosmos-sdk/x/evidence/types" ) @@ -50,8 +51,8 @@ func TestMsgSubmitEvidence(t *testing.T) { } for i, tc := range testCases { - require.Equal(t, tc.msg.(sdk.LegacyMsg).Route(), types.RouterKey, "unexpected result for tc #%d", i) - require.Equal(t, tc.msg.(sdk.LegacyMsg).Type(), types.TypeMsgSubmitEvidence, "unexpected result for tc #%d", i) + require.Equal(t, tc.msg.(legacytx.LegacyMsg).Route(), types.RouterKey, "unexpected result for tc #%d", i) + require.Equal(t, tc.msg.(legacytx.LegacyMsg).Type(), types.TypeMsgSubmitEvidence, "unexpected result for tc #%d", i) require.Equal(t, tc.expectErr, tc.msg.ValidateBasic() != nil, "unexpected result for tc #%d", i) if !tc.expectErr { diff --git a/x/genutil/client/cli/gentx_test.go b/x/genutil/client/cli/gentx_test.go index 6f1be047472b..e8bdb0c2e3b1 100644 --- a/x/genutil/client/cli/gentx_test.go +++ b/x/genutil/client/cli/gentx_test.go @@ -16,6 +16,7 @@ import ( "github.com/cosmos/cosmos-sdk/testutil" "github.com/cosmos/cosmos-sdk/testutil/network" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/legacy/legacytx" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -85,7 +86,7 @@ func (s *IntegrationTestSuite) TestGenTxCmd() { msgs := tx.GetMsgs() s.Require().Len(msgs, 1) - s.Require().Equal(types.TypeMsgCreateValidator, msgs[0].(sdk.LegacyMsg).Type()) + s.Require().Equal(types.TypeMsgCreateValidator, msgs[0].(legacytx.LegacyMsg).Type()) s.Require().Equal([]sdk.AccAddress{val.Address}, msgs[0].GetSigners()) s.Require().Equal(amount, msgs[0].(*types.MsgCreateValidator).Value) err = tx.ValidateBasic() diff --git a/x/gov/client/utils/query_test.go b/x/gov/client/utils/query_test.go index 8649f9878307..30822c0d671c 100644 --- a/x/gov/client/utils/query_test.go +++ b/x/gov/client/utils/query_test.go @@ -13,6 +13,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/legacy/legacytx" "github.com/cosmos/cosmos-sdk/x/gov/client/utils" "github.com/cosmos/cosmos-sdk/x/gov/types" ) @@ -44,7 +45,7 @@ func (mock TxSearchMock) TxSearch(ctx context.Context, query string, prove bool, return nil, err } for _, msg := range sdkTx.GetMsgs() { - if msg.(sdk.LegacyMsg).Type() == msgType { + if msg.(legacytx.LegacyMsg).Type() == msgType { matchingTxs = append(matchingTxs, tx) break } From 77e20c229f7427ad951c15c8f408d8c0636436ee Mon Sep 17 00:00:00 2001 From: Amaury <1293565+amaurym@users.noreply.github.com> Date: Thu, 22 Apr 2021 11:24:38 +0200 Subject: [PATCH 30/47] Update CHANGELOG.md Co-authored-by: Aaron Craelius --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b3f982463c2..b9171d058ee4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,7 +86,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (x/bank) [\#8798](https://github.com/cosmos/cosmos-sdk/pull/8798) `GetTotalSupply` is removed in favour of `GetPaginatedTotalSupply` * (x/bank/types) [\#9061](https://github.com/cosmos/cosmos-sdk/pull/9061) `AddressFromBalancesStore` now returns an error for invalid key instead of panic. * [\#9139](https://github.com/cosmos/cosmos-sdk/pull/9139) ServiceMsgs as defined in ADR-031 have been removed, so that the SDK adheres to the Protobuf spec of `Any` packing. This has multiple consequences: - * The `sdk.ServiceMsg` interface has been removed. + * The `sdk.ServiceMsg` struct has been removed. * `sdk.Msg` now only contains `ValidateBasic` and `GetSigners` methods. The remaining methods `GetSignBytes`, `Route` and `Type` are moved to `legacytx.LegacyMsg`. * The `RegisterCustomTypeURL` function and the `cosmos.base.v1beta1.ServiceMsg` interface have been removed from the interface registry. From 3cac0e67be1b6382e7cc769e9974b044ef6cca96 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Thu, 22 Apr 2021 15:11:49 +0200 Subject: [PATCH 31/47] Remove NewAnyWithCustomTypeURL --- codec/types/any.go | 10 ++-------- codec/types/any_test.go | 6 ++---- server/grpc/server_test.go | 10 ++-------- 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/codec/types/any.go b/codec/types/any.go index 14087efa8fd6..07c8de98c81e 100644 --- a/codec/types/any.go +++ b/codec/types/any.go @@ -64,20 +64,14 @@ func NewAnyWithValue(v proto.Message) (*Any, error) { if v == nil { return nil, sdkerrors.Wrap(sdkerrors.ErrPackAny, "Expecting non nil value to create a new Any") } - return NewAnyWithCustomTypeURL(v, "/"+proto.MessageName(v)) -} -// NewAnyWithCustomTypeURL same as NewAnyWithValue, but sets a custom type url, instead -// using the one from proto.Message. -// NOTE: This functions should be only used for types with additional logic bundled -// into the protobuf Any serialization. For simple marshaling you should use NewAnyWithValue. -func NewAnyWithCustomTypeURL(v proto.Message, typeURL string) (*Any, error) { bz, err := proto.Marshal(v) if err != nil { return nil, err } + return &Any{ - TypeUrl: typeURL, + TypeUrl: "/" + proto.MessageName(v), Value: bz, cachedValue: v, }, nil diff --git a/codec/types/any_test.go b/codec/types/any_test.go index cea3e0444efb..b9ddbe72ec39 100644 --- a/codec/types/any_test.go +++ b/codec/types/any_test.go @@ -23,8 +23,6 @@ func (eom *errOnMarshal) XXX_Marshal(b []byte, deterministic bool) ([]byte, erro return nil, errAlways } -const fauxURL = "/anyhere" - var eom = &errOnMarshal{} // Ensure that returning an error doesn't suddenly allocate and waste bytes. @@ -32,7 +30,7 @@ var eom = &errOnMarshal{} func TestNewAnyWithCustomTypeURLWithErrorNoAllocation(t *testing.T) { var ms1, ms2 runtime.MemStats runtime.ReadMemStats(&ms1) - any, err := types.NewAnyWithCustomTypeURL(eom, fauxURL) + any, err := types.NewAnyWithValue(eom) runtime.ReadMemStats(&ms2) // Ensure that no fresh allocation was made. if diff := ms2.HeapAlloc - ms1.HeapAlloc; diff > 0 { @@ -52,7 +50,7 @@ func BenchmarkNewAnyWithCustomTypeURLWithErrorReturned(b *testing.B) { b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - any, err := types.NewAnyWithCustomTypeURL(eom, fauxURL) + any, err := types.NewAnyWithValue(eom) if err == nil { b.Fatal("err wasn't returned") } diff --git a/server/grpc/server_test.go b/server/grpc/server_test.go index ee45c0156a0b..21a9a82372f8 100644 --- a/server/grpc/server_test.go +++ b/server/grpc/server_test.go @@ -195,7 +195,7 @@ func (s *IntegrationTestSuite) TestGRPCServer_BroadcastTx() { s.Require().NoError(err) // Broadcast the tx via gRPC. - queryClient := txtypes.NewServiceClient(val0.ClientCtx) + queryClient := txtypes.NewServiceClient(s.conn) grpcRes, err := queryClient.BroadcastTx( context.Background(), @@ -228,13 +228,7 @@ func (s *IntegrationTestSuite) TestGRPCServerInvalidHeaderHeights() { } for _, tt := range invalidHeightStrs { t.Run(tt.value, func(t *testing.T) { - conn, err := grpc.Dial( - val0.AppConfig.GRPC.Address, - grpc.WithInsecure(), // Or else we get "no transport security set" - ) - defer conn.Close() - - testClient := testdata.NewQueryClient(conn) + testClient := testdata.NewQueryClient(s.conn) ctx := metadata.AppendToOutgoingContext(context.Background(), grpctypes.GRPCBlockHeightHeader, tt.value) testRes, err := testClient.Echo(ctx, &testdata.EchoRequest{Message: "hello"}) require.Error(t, err) From 113ef64e2846e483a251faadbcafcad8a9af9d76 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Thu, 22 Apr 2021 15:15:00 +0200 Subject: [PATCH 32/47] Keep support for ServiceMsgs --- codec/types/interface_registry.go | 22 +++ types/msgservice/msg_service.go | 3 + x/auth/legacy/legacytx/service_msg_test.go | 160 +++++++++++++++++++++ 3 files changed, 185 insertions(+) create mode 100644 x/auth/legacy/legacytx/service_msg_test.go diff --git a/codec/types/interface_registry.go b/codec/types/interface_registry.go index 5c345d347cdc..4e1e95e71c7b 100644 --- a/codec/types/interface_registry.go +++ b/codec/types/interface_registry.go @@ -46,6 +46,19 @@ type InterfaceRegistry interface { // registry.RegisterImplementations((*sdk.Msg)(nil), &MsgSend{}, &MsgMultiSend{}) RegisterImplementations(iface interface{}, impls ...proto.Message) + // RegisterCustomTypeURL allows a protobuf message to be registered as a + // google.protobuf.Any with a custom typeURL (besides its own canonical + // typeURL). iface should be an interface as type, as in RegisterInterface + // and RegisterImplementations. + // + // Ex: + // This will allow us to pack service methods in Any's using the full method name + // as the type URL and the request body as the value, and allow us to unpack + // such packed methods using the normal UnpackAny method for the interface iface. + // + // Deprecated. TODO Remove as part of https://github.com/cosmos/cosmos-sdk/issues/9172. + RegisterCustomTypeURL(iface interface{}, typeURL string, impl proto.Message) + // ListAllInterfaces list the type URLs of all registered interfaces. ListAllInterfaces() []string @@ -116,6 +129,15 @@ func (registry *interfaceRegistry) RegisterImplementations(iface interface{}, im } } +// RegisterCustomTypeURL registers a concrete type which implements the given +// interface under `typeURL`. +// +// This function PANICs if different concrete types are registered under the +// same typeURL. +func (registry *interfaceRegistry) RegisterCustomTypeURL(iface interface{}, typeURL string, impl proto.Message) { + registry.registerImpl(iface, typeURL, impl) +} + // registerImpl registers a concrete type which implements the given // interface under `typeURL`. // diff --git a/types/msgservice/msg_service.go b/types/msgservice/msg_service.go index 694a27b03f40..d8df1efc8fea 100644 --- a/types/msgservice/msg_service.go +++ b/types/msgservice/msg_service.go @@ -31,6 +31,9 @@ func RegisterMsgServiceDesc(registry codectypes.InterfaceRegistry, sd *grpc.Serv } registry.RegisterImplementations((*sdk.Msg)(nil), msg) + // Support ServiceMsg's custom TypeURL for now. + // TODO Remove as part of https://github.com/cosmos/cosmos-sdk/issues/9172. + registry.RegisterCustomTypeURL((*sdk.Msg)(nil), fqMethod, msg) return nil }, noopInterceptor) diff --git a/x/auth/legacy/legacytx/service_msg_test.go b/x/auth/legacy/legacytx/service_msg_test.go new file mode 100644 index 000000000000..82fbd55d0fe7 --- /dev/null +++ b/x/auth/legacy/legacytx/service_msg_test.go @@ -0,0 +1,160 @@ +// This file contains legacy code for testing `ServiceMsg`s. Following the issue +// https://github.com/cosmos/cosmos-sdk/issues/9063, `ServiceMsg`s have been +// deprecated, but we still support them in v043. These tests ensures that txs +// containing `ServiceMsg`s still work. +// +// This file should be removed as part of https://github.com/cosmos/cosmos-sdk/issues/9172. +package legacytx_test + +import ( + "context" + "strings" + "testing" + + "github.com/gogo/protobuf/proto" + "github.com/stretchr/testify/suite" + + "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/testutil/network" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + txtypes "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + authclient "github.com/cosmos/cosmos-sdk/x/auth/client" + "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +// MsgRequest is the interface a transaction message, defined as a proto +// service method, must fulfill. +type msgRequest interface { + proto.Message + // ValidateBasic does a simple validation check that + // doesn't require access to any other information. + ValidateBasic() error + // Signers returns the addrs of signers that must sign. + // CONTRACT: All signatures must be present to be valid. + // CONTRACT: Returns addrs in some deterministic order. + GetSigners() []sdk.AccAddress +} + +// ServiceMsg is the struct into which an Any whose typeUrl matches a service +// method format (ex. `/cosmos.gov.v1beta1.Msg/SubmitProposal`) unpacks. +type serviceMsg struct { + // MethodName is the fully-qualified service method name. + MethodName string + // Request is the request payload. + Request msgRequest +} + +var _ sdk.Msg = &serviceMsg{} + +func (msg *serviceMsg) ProtoMessage() {} +func (msg *serviceMsg) Reset() {} +func (msg *serviceMsg) String() string { return "ServiceMsg" } + +// ValidateBasic implements Msg.ValidateBasic method. +func (msg *serviceMsg) ValidateBasic() error { + return msg.Request.ValidateBasic() +} + +// GetSigners implements Msg.GetSigners method. +func (msg *serviceMsg) GetSigners() []sdk.AccAddress { + return msg.Request.GetSigners() +} + +type IntegrationTestSuite struct { + suite.Suite + + cfg network.Config + network *network.Network +} + +func (s *IntegrationTestSuite) SetupSuite() { + s.T().Log("setting up integration test suite") + + cfg := network.DefaultConfig() + cfg.NumValidators = 1 + + s.cfg = cfg + s.network = network.New(s.T(), cfg) + s.Require().NotNil(s.network) + + _, err := s.network.WaitForHeight(1) + s.Require().NoError(err) +} + +func (s *IntegrationTestSuite) TearDownSuite() { + s.T().Log("tearing down integration test suite") + s.network.Cleanup() +} + +// TestServiceMsg tests sending txs with a ServiceMsg. +func (s IntegrationTestSuite) TestServiceMsg() { + val := s.network.Validators[0] + + // prepare txBuilder with msg + txBuilder := val.ClientCtx.TxConfig.NewTxBuilder() + + // This sets a ServiceMsg Msg/Send. + // Thanks for the `proto.Marshal` override at the end of this file, tx + // encoding works nicely. + err := txBuilder.SetMsgs(&serviceMsg{ + MethodName: "/cosmos.bank.v1beta1.Msg/Send", + Request: &types.MsgSend{ + FromAddress: val.Address.String(), + ToAddress: val.Address.String(), + Amount: sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 23)}, + }, + }) + s.Require().NoError(err) + + txBuilder.SetFeeAmount(sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 23)}) + txBuilder.SetGasLimit(testdata.NewTestGasLimit()) + + // setup txFactory + txFactory := tx.Factory{}. + WithChainID(val.ClientCtx.ChainID). + WithKeybase(val.ClientCtx.Keyring). + WithTxConfig(val.ClientCtx.TxConfig). + WithSignMode(signing.SignMode_SIGN_MODE_DIRECT) + + // Sign Tx. + err = authclient.SignTx(txFactory, val.ClientCtx, val.Moniker, txBuilder, false, true) + s.Require().NoError(err) + + txBytes, err := val.ClientCtx.TxConfig.TxEncoder()(txBuilder.GetTx()) + s.Require().NoError(err) + + // Broadcast the tx via gRPC. + queryClient := txtypes.NewServiceClient(val.ClientCtx) + + grpcRes, err := queryClient.BroadcastTx( + context.Background(), + &txtypes.BroadcastTxRequest{ + Mode: txtypes.BroadcastMode_BROADCAST_MODE_SYNC, + TxBytes: txBytes, + }, + ) + s.Require().NoError(err) + s.Require().Equal(uint32(0), grpcRes.TxResponse.Code) +} + +func TestIntegrationTestSuite(t *testing.T) { + suite.Run(t, new(IntegrationTestSuite)) +} + +// newMarshaler is the interface representing objects that can marshal themselves. +// This exists to overwrite `proto.Marshal` behavior for ServiceMsg. +// +// DO NOT DEPEND ON THIS. +type svcMsgMarshaler interface { + XXX_Size() int + XXX_Marshal(b []byte, deterministic bool) ([]byte, error) +} + +// Overwrite the `proto.MessageName` and `proto.Marshal` return values for ServiceMsg. +func (msg *serviceMsg) XXX_MessageName() string { return strings.TrimPrefix(msg.MethodName, "/") } +func (msg *serviceMsg) XXX_Size() int { return msg.Request.(svcMsgMarshaler).XXX_Size() } +func (msg *serviceMsg) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return msg.Request.(svcMsgMarshaler).XXX_Marshal(b, deterministic) +} From 09e81a2a7e8f4faa006139e2077d8caceeeb5430 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Thu, 22 Apr 2021 16:28:39 +0200 Subject: [PATCH 33/47] Fix TxBody UnpackInterfaces --- types/tx/types.go | 50 ++++++++++++++++++++-- x/auth/legacy/legacytx/service_msg_test.go | 28 ++++++------ 2 files changed, 62 insertions(+), 16 deletions(-) diff --git a/types/tx/types.go b/types/tx/types.go index 9fe923aa20cb..5ba366f45296 100644 --- a/types/tx/types.go +++ b/types/tx/types.go @@ -2,6 +2,9 @@ package tx import ( "fmt" + "strings" + + "github.com/gogo/protobuf/proto" codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" @@ -164,14 +167,53 @@ func (t *Tx) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { return nil } +// isServiceMsg checks if a type URL corresponds to a service method name, +// i.e. /cosmos.bank.Msg/Send vs /cosmos.bank.MsgSend +func isServiceMsg(typeURL string) bool { + return strings.Count(typeURL, "/") >= 2 +} + // UnpackInterfaces implements the UnpackInterfaceMessages.UnpackInterfaces method func (m *TxBody) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { for _, any := range m.Messages { - var msg sdk.Msg - err := unpacker.UnpackAny(any, &msg) - if err != nil { - return err + // We gracefully keep support for ServiceMsgs, even though they are + // deprecated. + // TODO Remove ServiceMsgs in https://github.com/cosmos/cosmos-sdk/issues/9172. + if isServiceMsg(any.TypeUrl) { + // Recall that a ServiceMsg is packed inside an `Any`, its Any.Value + // being the encoded bytes of another `Any` packing the request + // parameter (a skd.Msg). So there's an `Any` packed inside another + // `Any`. + // Our strategy here is to: + // - create an `Any` called `requestAny`, will represents the request + // parameter, + // - unmarshal the ServiceMsg `Any`'s `Any.Value` into `requestAny`, + // - then use the interface registry to unpack `requestAny` into a + // sdk.Msg. + // + // The initial ServiceMsg `Any` is replaced by the request parameter + // `sdk.Msg` `Any`. + var requestAny codectypes.Any + err := proto.Unmarshal(any.Value, &requestAny) + if err != nil { + return err + } + + var msg sdk.Msg + err = unpacker.UnpackAny(&requestAny, &msg) + if err != nil { + return err + } + + *any = requestAny + } else { + var msg sdk.Msg + err := unpacker.UnpackAny(any, &msg) + if err != nil { + return err + } } + } return nil diff --git a/x/auth/legacy/legacytx/service_msg_test.go b/x/auth/legacy/legacytx/service_msg_test.go index 82fbd55d0fe7..2bdb5c55d15a 100644 --- a/x/auth/legacy/legacytx/service_msg_test.go +++ b/x/auth/legacy/legacytx/service_msg_test.go @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/cosmos/cosmos-sdk/client/tx" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/testutil/network" "github.com/cosmos/cosmos-sdk/testutil/testdata" sdk "github.com/cosmos/cosmos-sdk/types" @@ -96,7 +97,7 @@ func (s IntegrationTestSuite) TestServiceMsg() { txBuilder := val.ClientCtx.TxConfig.NewTxBuilder() // This sets a ServiceMsg Msg/Send. - // Thanks for the `proto.Marshal` override at the end of this file, tx + // Thanks to the `proto.Marshal` override at the end of this file, tx // encoding works nicely. err := txBuilder.SetMsgs(&serviceMsg{ MethodName: "/cosmos.bank.v1beta1.Msg/Send", @@ -143,18 +144,21 @@ func TestIntegrationTestSuite(t *testing.T) { suite.Run(t, new(IntegrationTestSuite)) } -// newMarshaler is the interface representing objects that can marshal themselves. -// This exists to overwrite `proto.Marshal` behavior for ServiceMsg. -// -// DO NOT DEPEND ON THIS. -type svcMsgMarshaler interface { - XXX_Size() int - XXX_Marshal(b []byte, deterministic bool) ([]byte, error) -} - // Overwrite the `proto.MessageName` and `proto.Marshal` return values for ServiceMsg. func (msg *serviceMsg) XXX_MessageName() string { return strings.TrimPrefix(msg.MethodName, "/") } -func (msg *serviceMsg) XXX_Size() int { return msg.Request.(svcMsgMarshaler).XXX_Size() } +func (msg *serviceMsg) XXX_Size() int { + any, err := codectypes.NewAnyWithValue(msg.Request) + if err != nil { + panic(err) + } + + return any.XXX_Size() +} func (msg *serviceMsg) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return msg.Request.(svcMsgMarshaler).XXX_Marshal(b, deterministic) + any, err := codectypes.NewAnyWithValue(msg.Request) + if err != nil { + return nil, err + } + + return any.XXX_Marshal(b, deterministic) } From 7e05b80bb82b6de64b8f8bcd6e58cdeb69e2e2e0 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Thu, 22 Apr 2021 16:36:30 +0200 Subject: [PATCH 34/47] Fix test --- server/grpc/server_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/server/grpc/server_test.go b/server/grpc/server_test.go index 21a9a82372f8..db43ccfddebf 100644 --- a/server/grpc/server_test.go +++ b/server/grpc/server_test.go @@ -213,7 +213,6 @@ func (s *IntegrationTestSuite) TestGRPCServer_BroadcastTx() { // See issue https://github.com/cosmos/cosmos-sdk/issues/7662. func (s *IntegrationTestSuite) TestGRPCServerInvalidHeaderHeights() { t := s.T() - val0 := s.network.Validators[0] // We should reject connections with invalid block heights off the bat. invalidHeightStrs := []struct { From 68b7e5fd968b9c0d024082348260b1a2f4d3ed72 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Fri, 23 Apr 2021 10:54:27 +0200 Subject: [PATCH 35/47] Address review --- CHANGELOG.md | 2 +- baseapp/msg_service_router.go | 4 ++-- x/auth/legacy/legacytx/service_msg_test.go | 18 +++--------------- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dade0e28e47f..0cab13dcfe8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,7 +86,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (auth/tx) [\#8926](https://github.com/cosmos/cosmos-sdk/pull/8926) The `ProtoTxProvider` interface used as a workaround for transaction simulation has been removed. * (x/bank) [\#8798](https://github.com/cosmos/cosmos-sdk/pull/8798) `GetTotalSupply` is removed in favour of `GetPaginatedTotalSupply` * (x/bank/types) [\#9061](https://github.com/cosmos/cosmos-sdk/pull/9061) `AddressFromBalancesStore` now returns an error for invalid key instead of panic. -* [\#9139](https://github.com/cosmos/cosmos-sdk/pull/9139) ServiceMsgs as defined in ADR-031 have been removed, so that the SDK adheres to the Protobuf spec of `Any` packing. This has multiple consequences: +* [\#9139](https://github.com/cosmos/cosmos-sdk/pull/9139) The TypeURLs for `Msg` routing have been changed to comply with the Protobuf `Any` spec. `ServiceMsg` TypeURLs (e.g. `/cosmos.bank.v1beta1.Msg/Send`), though still supported, are being deprecated in favor or `Msg` type TypeURLs (e.g. `/cosmos.bank.v1beta1.MsgSend`). This has multiple consequences: * The `sdk.ServiceMsg` struct has been removed. * `sdk.Msg` now only contains `ValidateBasic` and `GetSigners` methods. The remaining methods `GetSignBytes`, `Route` and `Type` are moved to `legacytx.LegacyMsg`. * The `RegisterCustomTypeURL` function and the `cosmos.base.v1beta1.ServiceMsg` interface have been removed from the interface registry. diff --git a/baseapp/msg_service_router.go b/baseapp/msg_service_router.go index 5a91944c7bea..ee55f5082715 100644 --- a/baseapp/msg_service_router.go +++ b/baseapp/msg_service_router.go @@ -38,8 +38,8 @@ func (msr *MsgServiceRouter) Handler(msg sdk.Msg) MsgServiceHandler { // HandlerbyTypeURL returns the MsgServiceHandler for a given query route path or nil // if not found. -func (msr *MsgServiceRouter) HandlerbyTypeURL(msgName string) MsgServiceHandler { - return msr.routes[msgName] +func (msr *MsgServiceRouter) HandlerbyTypeURL(typeURL string) MsgServiceHandler { + return msr.routes[typeURL] } // RegisterService implements the gRPC Server.RegisterService method. sd is a gRPC diff --git a/x/auth/legacy/legacytx/service_msg_test.go b/x/auth/legacy/legacytx/service_msg_test.go index 2bdb5c55d15a..c153e1e9b6a7 100644 --- a/x/auth/legacy/legacytx/service_msg_test.go +++ b/x/auth/legacy/legacytx/service_msg_test.go @@ -1,3 +1,5 @@ +// +build norace + // This file contains legacy code for testing `ServiceMsg`s. Following the issue // https://github.com/cosmos/cosmos-sdk/issues/9063, `ServiceMsg`s have been // deprecated, but we still support them in v043. These tests ensures that txs @@ -11,7 +13,6 @@ import ( "strings" "testing" - "github.com/gogo/protobuf/proto" "github.com/stretchr/testify/suite" "github.com/cosmos/cosmos-sdk/client/tx" @@ -25,26 +26,13 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank/types" ) -// MsgRequest is the interface a transaction message, defined as a proto -// service method, must fulfill. -type msgRequest interface { - proto.Message - // ValidateBasic does a simple validation check that - // doesn't require access to any other information. - ValidateBasic() error - // Signers returns the addrs of signers that must sign. - // CONTRACT: All signatures must be present to be valid. - // CONTRACT: Returns addrs in some deterministic order. - GetSigners() []sdk.AccAddress -} - // ServiceMsg is the struct into which an Any whose typeUrl matches a service // method format (ex. `/cosmos.gov.v1beta1.Msg/SubmitProposal`) unpacks. type serviceMsg struct { // MethodName is the fully-qualified service method name. MethodName string // Request is the request payload. - Request msgRequest + Request sdk.Msg } var _ sdk.Msg = &serviceMsg{} From f25b2c68f755d125a41cec765081347048bb2955 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Tue, 27 Apr 2021 10:49:03 +0200 Subject: [PATCH 36/47] Remove support for ServiceMsg typeURLs --- codec/types/interface_registry.go | 13 -- types/msgservice/msg_service.go | 4 +- types/tx/types.go | 43 +----- x/auth/legacy/legacytx/service_msg_test.go | 152 --------------------- 4 files changed, 5 insertions(+), 207 deletions(-) delete mode 100644 x/auth/legacy/legacytx/service_msg_test.go diff --git a/codec/types/interface_registry.go b/codec/types/interface_registry.go index 4e1e95e71c7b..5d7e72e890c0 100644 --- a/codec/types/interface_registry.go +++ b/codec/types/interface_registry.go @@ -46,19 +46,6 @@ type InterfaceRegistry interface { // registry.RegisterImplementations((*sdk.Msg)(nil), &MsgSend{}, &MsgMultiSend{}) RegisterImplementations(iface interface{}, impls ...proto.Message) - // RegisterCustomTypeURL allows a protobuf message to be registered as a - // google.protobuf.Any with a custom typeURL (besides its own canonical - // typeURL). iface should be an interface as type, as in RegisterInterface - // and RegisterImplementations. - // - // Ex: - // This will allow us to pack service methods in Any's using the full method name - // as the type URL and the request body as the value, and allow us to unpack - // such packed methods using the normal UnpackAny method for the interface iface. - // - // Deprecated. TODO Remove as part of https://github.com/cosmos/cosmos-sdk/issues/9172. - RegisterCustomTypeURL(iface interface{}, typeURL string, impl proto.Message) - // ListAllInterfaces list the type URLs of all registered interfaces. ListAllInterfaces() []string diff --git a/types/msgservice/msg_service.go b/types/msgservice/msg_service.go index d8df1efc8fea..382913590cad 100644 --- a/types/msgservice/msg_service.go +++ b/types/msgservice/msg_service.go @@ -31,9 +31,7 @@ func RegisterMsgServiceDesc(registry codectypes.InterfaceRegistry, sd *grpc.Serv } registry.RegisterImplementations((*sdk.Msg)(nil), msg) - // Support ServiceMsg's custom TypeURL for now. - // TODO Remove as part of https://github.com/cosmos/cosmos-sdk/issues/9172. - registry.RegisterCustomTypeURL((*sdk.Msg)(nil), fqMethod, msg) + return nil }, noopInterceptor) diff --git a/types/tx/types.go b/types/tx/types.go index 5ba366f45296..691314beaf27 100644 --- a/types/tx/types.go +++ b/types/tx/types.go @@ -4,8 +4,6 @@ import ( "fmt" "strings" - "github.com/gogo/protobuf/proto" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -176,44 +174,11 @@ func isServiceMsg(typeURL string) bool { // UnpackInterfaces implements the UnpackInterfaceMessages.UnpackInterfaces method func (m *TxBody) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { for _, any := range m.Messages { - // We gracefully keep support for ServiceMsgs, even though they are - // deprecated. - // TODO Remove ServiceMsgs in https://github.com/cosmos/cosmos-sdk/issues/9172. - if isServiceMsg(any.TypeUrl) { - // Recall that a ServiceMsg is packed inside an `Any`, its Any.Value - // being the encoded bytes of another `Any` packing the request - // parameter (a skd.Msg). So there's an `Any` packed inside another - // `Any`. - // Our strategy here is to: - // - create an `Any` called `requestAny`, will represents the request - // parameter, - // - unmarshal the ServiceMsg `Any`'s `Any.Value` into `requestAny`, - // - then use the interface registry to unpack `requestAny` into a - // sdk.Msg. - // - // The initial ServiceMsg `Any` is replaced by the request parameter - // `sdk.Msg` `Any`. - var requestAny codectypes.Any - err := proto.Unmarshal(any.Value, &requestAny) - if err != nil { - return err - } - - var msg sdk.Msg - err = unpacker.UnpackAny(&requestAny, &msg) - if err != nil { - return err - } - - *any = requestAny - } else { - var msg sdk.Msg - err := unpacker.UnpackAny(any, &msg) - if err != nil { - return err - } + var msg sdk.Msg + err := unpacker.UnpackAny(any, &msg) + if err != nil { + return err } - } return nil diff --git a/x/auth/legacy/legacytx/service_msg_test.go b/x/auth/legacy/legacytx/service_msg_test.go deleted file mode 100644 index c153e1e9b6a7..000000000000 --- a/x/auth/legacy/legacytx/service_msg_test.go +++ /dev/null @@ -1,152 +0,0 @@ -// +build norace - -// This file contains legacy code for testing `ServiceMsg`s. Following the issue -// https://github.com/cosmos/cosmos-sdk/issues/9063, `ServiceMsg`s have been -// deprecated, but we still support them in v043. These tests ensures that txs -// containing `ServiceMsg`s still work. -// -// This file should be removed as part of https://github.com/cosmos/cosmos-sdk/issues/9172. -package legacytx_test - -import ( - "context" - "strings" - "testing" - - "github.com/stretchr/testify/suite" - - "github.com/cosmos/cosmos-sdk/client/tx" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/testutil/network" - "github.com/cosmos/cosmos-sdk/testutil/testdata" - sdk "github.com/cosmos/cosmos-sdk/types" - txtypes "github.com/cosmos/cosmos-sdk/types/tx" - "github.com/cosmos/cosmos-sdk/types/tx/signing" - authclient "github.com/cosmos/cosmos-sdk/x/auth/client" - "github.com/cosmos/cosmos-sdk/x/bank/types" -) - -// ServiceMsg is the struct into which an Any whose typeUrl matches a service -// method format (ex. `/cosmos.gov.v1beta1.Msg/SubmitProposal`) unpacks. -type serviceMsg struct { - // MethodName is the fully-qualified service method name. - MethodName string - // Request is the request payload. - Request sdk.Msg -} - -var _ sdk.Msg = &serviceMsg{} - -func (msg *serviceMsg) ProtoMessage() {} -func (msg *serviceMsg) Reset() {} -func (msg *serviceMsg) String() string { return "ServiceMsg" } - -// ValidateBasic implements Msg.ValidateBasic method. -func (msg *serviceMsg) ValidateBasic() error { - return msg.Request.ValidateBasic() -} - -// GetSigners implements Msg.GetSigners method. -func (msg *serviceMsg) GetSigners() []sdk.AccAddress { - return msg.Request.GetSigners() -} - -type IntegrationTestSuite struct { - suite.Suite - - cfg network.Config - network *network.Network -} - -func (s *IntegrationTestSuite) SetupSuite() { - s.T().Log("setting up integration test suite") - - cfg := network.DefaultConfig() - cfg.NumValidators = 1 - - s.cfg = cfg - s.network = network.New(s.T(), cfg) - s.Require().NotNil(s.network) - - _, err := s.network.WaitForHeight(1) - s.Require().NoError(err) -} - -func (s *IntegrationTestSuite) TearDownSuite() { - s.T().Log("tearing down integration test suite") - s.network.Cleanup() -} - -// TestServiceMsg tests sending txs with a ServiceMsg. -func (s IntegrationTestSuite) TestServiceMsg() { - val := s.network.Validators[0] - - // prepare txBuilder with msg - txBuilder := val.ClientCtx.TxConfig.NewTxBuilder() - - // This sets a ServiceMsg Msg/Send. - // Thanks to the `proto.Marshal` override at the end of this file, tx - // encoding works nicely. - err := txBuilder.SetMsgs(&serviceMsg{ - MethodName: "/cosmos.bank.v1beta1.Msg/Send", - Request: &types.MsgSend{ - FromAddress: val.Address.String(), - ToAddress: val.Address.String(), - Amount: sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 23)}, - }, - }) - s.Require().NoError(err) - - txBuilder.SetFeeAmount(sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 23)}) - txBuilder.SetGasLimit(testdata.NewTestGasLimit()) - - // setup txFactory - txFactory := tx.Factory{}. - WithChainID(val.ClientCtx.ChainID). - WithKeybase(val.ClientCtx.Keyring). - WithTxConfig(val.ClientCtx.TxConfig). - WithSignMode(signing.SignMode_SIGN_MODE_DIRECT) - - // Sign Tx. - err = authclient.SignTx(txFactory, val.ClientCtx, val.Moniker, txBuilder, false, true) - s.Require().NoError(err) - - txBytes, err := val.ClientCtx.TxConfig.TxEncoder()(txBuilder.GetTx()) - s.Require().NoError(err) - - // Broadcast the tx via gRPC. - queryClient := txtypes.NewServiceClient(val.ClientCtx) - - grpcRes, err := queryClient.BroadcastTx( - context.Background(), - &txtypes.BroadcastTxRequest{ - Mode: txtypes.BroadcastMode_BROADCAST_MODE_SYNC, - TxBytes: txBytes, - }, - ) - s.Require().NoError(err) - s.Require().Equal(uint32(0), grpcRes.TxResponse.Code) -} - -func TestIntegrationTestSuite(t *testing.T) { - suite.Run(t, new(IntegrationTestSuite)) -} - -// Overwrite the `proto.MessageName` and `proto.Marshal` return values for ServiceMsg. -func (msg *serviceMsg) XXX_MessageName() string { return strings.TrimPrefix(msg.MethodName, "/") } -func (msg *serviceMsg) XXX_Size() int { - any, err := codectypes.NewAnyWithValue(msg.Request) - if err != nil { - panic(err) - } - - return any.XXX_Size() -} -func (msg *serviceMsg) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - any, err := codectypes.NewAnyWithValue(msg.Request) - if err != nil { - return nil, err - } - - return any.XXX_Marshal(b, deterministic) -} From 31e5c2aa389b504a5641675e94da6880e2566ef8 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Tue, 27 Apr 2021 10:53:17 +0200 Subject: [PATCH 37/47] Fix lint --- types/tx/types.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/types/tx/types.go b/types/tx/types.go index 691314beaf27..9fe923aa20cb 100644 --- a/types/tx/types.go +++ b/types/tx/types.go @@ -2,7 +2,6 @@ package tx import ( "fmt" - "strings" codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" @@ -165,12 +164,6 @@ func (t *Tx) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { return nil } -// isServiceMsg checks if a type URL corresponds to a service method name, -// i.e. /cosmos.bank.Msg/Send vs /cosmos.bank.MsgSend -func isServiceMsg(typeURL string) bool { - return strings.Count(typeURL, "/") >= 2 -} - // UnpackInterfaces implements the UnpackInterfaceMessages.UnpackInterfaces method func (m *TxBody) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { for _, any := range m.Messages { From d6ab3ebde913e6b14c6f31ea78b5e2bf76f15289 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Tue, 27 Apr 2021 10:56:31 +0200 Subject: [PATCH 38/47] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cab13dcfe8f..37cc8cd299cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,7 +86,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (auth/tx) [\#8926](https://github.com/cosmos/cosmos-sdk/pull/8926) The `ProtoTxProvider` interface used as a workaround for transaction simulation has been removed. * (x/bank) [\#8798](https://github.com/cosmos/cosmos-sdk/pull/8798) `GetTotalSupply` is removed in favour of `GetPaginatedTotalSupply` * (x/bank/types) [\#9061](https://github.com/cosmos/cosmos-sdk/pull/9061) `AddressFromBalancesStore` now returns an error for invalid key instead of panic. -* [\#9139](https://github.com/cosmos/cosmos-sdk/pull/9139) The TypeURLs for `Msg` routing have been changed to comply with the Protobuf `Any` spec. `ServiceMsg` TypeURLs (e.g. `/cosmos.bank.v1beta1.Msg/Send`), though still supported, are being deprecated in favor or `Msg` type TypeURLs (e.g. `/cosmos.bank.v1beta1.MsgSend`). This has multiple consequences: +* [\#9139](https://github.com/cosmos/cosmos-sdk/pull/9139) `ServiceMsg` TypeURLs (e.g. `/cosmos.bank.v1beta1.Msg/Send`) have been removed, as they don't comply to the Probobuf `Any` spec. Please use `Msg` type TypeURLs (e.g. `/cosmos.bank.v1beta1.MsgSend`). This has multiple consequences: * The `sdk.ServiceMsg` struct has been removed. * `sdk.Msg` now only contains `ValidateBasic` and `GetSigners` methods. The remaining methods `GetSignBytes`, `Route` and `Type` are moved to `legacytx.LegacyMsg`. * The `RegisterCustomTypeURL` function and the `cosmos.base.v1beta1.ServiceMsg` interface have been removed from the interface registry. From 3e7d3aeb9aef21cbcb21b43eb23f8fa99a2a377d Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Tue, 27 Apr 2021 11:25:17 +0200 Subject: [PATCH 39/47] Fix tests --- x/bank/client/testutil/suite.go | 56 -------------------------------- x/feegrant/simulation/genesis.go | 2 +- 2 files changed, 1 insertion(+), 57 deletions(-) diff --git a/x/bank/client/testutil/suite.go b/x/bank/client/testutil/suite.go index e9fd8954c9d8..342c780253e0 100644 --- a/x/bank/client/testutil/suite.go +++ b/x/bank/client/testutil/suite.go @@ -470,62 +470,6 @@ func (s *IntegrationTestSuite) TestNewSendTxCmd() { } } -// TestBankMsgService does a basic test of whether or not service Msg's as defined -// in ADR 031 work in the most basic end-to-end case. -func (s *IntegrationTestSuite) TestBankMsgService() { - val := s.network.Validators[0] - - testCases := []struct { - name string - from, to sdk.AccAddress - amount sdk.Coins - args []string - expectErr bool - expectedCode uint32 - respType proto.Message - rawLogContains string - }{ - { - "valid transaction", - val.Address, - val.Address, - sdk.NewCoins( - sdk.NewCoin(fmt.Sprintf("%stoken", val.Moniker), sdk.NewInt(10)), - sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10)), - ), - []string{ - fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), - fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), - fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), - }, - false, - 0, - &sdk.TxResponse{}, - "/cosmos.bank.v1beta1.Msg/Send", // indicates we are using ServiceMsg and not a regular Msg - }, - } - - for _, tc := range testCases { - tc := tc - - s.Run(tc.name, func() { - clientCtx := val.ClientCtx - - bz, err := MsgSendExec(clientCtx, tc.from, tc.to, tc.amount, tc.args...) - if tc.expectErr { - s.Require().Error(err) - } else { - s.Require().NoError(err) - - s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(bz.Bytes(), tc.respType), bz.String()) - txResp := tc.respType.(*sdk.TxResponse) - s.Require().Equal(tc.expectedCode, txResp.Code) - s.Require().Contains(txResp.RawLog, tc.rawLogContains) - } - }) - } -} - func NewCoin(denom string, amount sdk.Int) *sdk.Coin { coin := sdk.NewCoin(denom, amount) return &coin diff --git a/x/feegrant/simulation/genesis.go b/x/feegrant/simulation/genesis.go index dee9b3b0fe5c..85fadcda4126 100644 --- a/x/feegrant/simulation/genesis.go +++ b/x/feegrant/simulation/genesis.go @@ -52,7 +52,7 @@ func generateRandomAllowances(granter, grantee sdk.AccAddress, r *rand.Rand) typ filteredAllowance, err := types.NewFeeAllowanceGrant(granter, grantee, &types.AllowedMsgFeeAllowance{ Allowance: basicAllowance.GetAllowance(), - AllowedMessages: []string{"/cosmos.gov.v1beta1.Msg/SubmitProposal"}, + AllowedMessages: []string{"/cosmos.gov.v1beta1.MsgSubmitProposal"}, }) if err != nil { panic(err) From 2000ebf2b9a9ee9e7540f4a6d7d23f9fd2cf82cf Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Tue, 27 Apr 2021 11:58:22 +0200 Subject: [PATCH 40/47] Use sdk.MsgTypeURL everywhere --- CHANGELOG.md | 1 + baseapp/baseapp.go | 4 ++-- server/rosetta/converter.go | 2 +- types/tx_msg.go | 7 +------ x/gov/client/utils/query.go | 14 +++++++------- 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37cc8cd299cb..773a551a95cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * CLI: removed `--text` flag from `show-node-id` command; the text format for public keys is not used any more - instead we use ProtoJSON. * (types) [\#9079](https://github.com/cosmos/cosmos-sdk/issues/9079) Add `AddAmount`/`SubAmount` methods to `sdk.Coin`. * [\#8628](https://github.com/cosmos/cosmos-sdk/issues/8628) Commands no longer print outputs using `stderr` by default +* [\#9139](https://github.com/cosmos/cosmos-sdk/pull/9139) Querying events via `ServiceMsg` TypeURLs (e.g. `/cosmos.bank.v1beta1.Msg/Send`) does not work anymore, please use concrete `Msg` TypeURLs instead (e.g. `/cosmos.bank.v1beta1/MsgSend`). ### API Breaking Changes diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 9b1e0242415f..ed7b20fc91ad 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -712,7 +712,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s if handler := app.msgServiceRouter.Handler(msg); handler != nil { // ADR 031 request type routing msgResult, err = handler(ctx, msg) - msgEventAction = sdk.MsgName(msg) + msgEventAction = sdk.MsgTypeURL(msg) } else if legacyMsg, ok := msg.(legacytx.LegacyMsg); ok { // legacy sdk.Msg routing msgRoute := legacyMsg.Route() @@ -742,7 +742,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s // separate each result. events = events.AppendEvents(msgEvents) - txMsgData.Data = append(txMsgData.Data, &sdk.MsgData{MsgType: sdk.MsgName(msg), Data: msgResult.Data}) + txMsgData.Data = append(txMsgData.Data, &sdk.MsgData{MsgType: sdk.MsgTypeURL(msg), Data: msgResult.Data}) msgLogs = append(msgLogs, sdk.NewABCIMessageLog(uint32(i), msgResult.Log, msgEvents)) } diff --git a/server/rosetta/converter.go b/server/rosetta/converter.go index 8351df0b0cc6..819c78da6f1d 100644 --- a/server/rosetta/converter.go +++ b/server/rosetta/converter.go @@ -240,7 +240,7 @@ func (c converter) Meta(msg sdk.Msg) (meta map[string]interface{}, err error) { // with the message proto name as type, and the raw fields // as metadata func (c converter) Ops(status string, msg sdk.Msg) ([]*rosettatypes.Operation, error) { - opName := sdk.MsgName(msg) + opName := sdk.MsgTypeURL(msg) meta, err := c.Meta(msg) if err != nil { diff --git a/types/tx_msg.go b/types/tx_msg.go index 38eaa7e30cb8..d7f15e83f078 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -77,12 +77,7 @@ type TxDecoder func(txBytes []byte) (Tx, error) // TxEncoder marshals transaction to bytes type TxEncoder func(tx Tx) ([]byte, error) -// MsgName returns the protobuf MessageName of a sdk.Msg. -func MsgName(msg Msg) string { - return proto.MessageName(msg) -} - // MsgTypeURL returns the TypeURL of a `sdk.Msg`. func MsgTypeURL(msg Msg) string { - return fmt.Sprintf("/%s", MsgName(msg)) + return fmt.Sprintf("/%s", proto.MessageName(msg)) } diff --git a/x/gov/client/utils/query.go b/x/gov/client/utils/query.go index f87058c2e13c..c0734c0ff1dd 100644 --- a/x/gov/client/utils/query.go +++ b/x/gov/client/utils/query.go @@ -46,7 +46,7 @@ func QueryDepositsByTxQuery(clientCtx client.Context, params types.QueryProposal }, // Query proto Msgs event action []string{ - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgName(&types.MsgDeposit{})), + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgDeposit{})), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalDeposit, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), }, ) @@ -98,7 +98,7 @@ func QueryVotesByTxQuery(clientCtx client.Context, params types.QueryProposalVot }, // Query Vote proto Msgs []string{ - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgName(&types.MsgVote{})), + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgVote{})), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), }, // Query legacy VoteWeighted Msgs @@ -108,7 +108,7 @@ func QueryVotesByTxQuery(clientCtx client.Context, params types.QueryProposalVot }, // Query VoteWeighted proto Msgs []string{ - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgName(&types.MsgVoteWeighted{})), + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgVoteWeighted{})), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), }, ) @@ -168,7 +168,7 @@ func QueryVoteByTxQuery(clientCtx client.Context, params types.QueryVoteParams) }, // Query Vote proto Msgs []string{ - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgName(&types.MsgVote{})), + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgVote{})), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, []byte(params.Voter.String())), }, @@ -180,7 +180,7 @@ func QueryVoteByTxQuery(clientCtx client.Context, params types.QueryVoteParams) }, // Query VoteWeighted proto Msgs []string{ - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgName(&types.MsgVoteWeighted{})), + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgVoteWeighted{})), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalVote, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, []byte(params.Voter.String())), }, @@ -236,7 +236,7 @@ func QueryDepositByTxQuery(clientCtx client.Context, params types.QueryDepositPa }, // Query proto Msgs event action []string{ - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgName(&types.MsgDeposit{})), + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgDeposit{})), fmt.Sprintf("%s.%s='%s'", types.EventTypeProposalDeposit, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", params.ProposalID))), fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, []byte(params.Depositor.String())), }, @@ -281,7 +281,7 @@ func QueryProposerByTxQuery(clientCtx client.Context, proposalID uint64) (Propos }, // Query proto Msgs event action []string{ - fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgName(&types.MsgSubmitProposal{})), + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgSubmitProposal{})), fmt.Sprintf("%s.%s='%s'", types.EventTypeSubmitProposal, types.AttributeKeyProposalID, []byte(fmt.Sprintf("%d", proposalID))), }, ) From a688c733ecff4b392500e3324afdceb0ac5073d3 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Tue, 27 Apr 2021 12:18:12 +0200 Subject: [PATCH 41/47] Fix tests --- server/rosetta/converter.go | 3 ++- x/auth/client/testutil/suite.go | 2 +- x/auth/tx/service_test.go | 24 +++++++++++++----------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/server/rosetta/converter.go b/server/rosetta/converter.go index 819c78da6f1d..1f4c9000e1d9 100644 --- a/server/rosetta/converter.go +++ b/server/rosetta/converter.go @@ -7,6 +7,7 @@ import ( "reflect" auth "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/gogo/protobuf/proto" "github.com/tendermint/tendermint/crypto" @@ -240,7 +241,7 @@ func (c converter) Meta(msg sdk.Msg) (meta map[string]interface{}, err error) { // with the message proto name as type, and the raw fields // as metadata func (c converter) Ops(status string, msg sdk.Msg) ([]*rosettatypes.Operation, error) { - opName := sdk.MsgTypeURL(msg) + opName := proto.MessageName(msg) meta, err := c.Meta(msg) if err != nil { diff --git a/x/auth/client/testutil/suite.go b/x/auth/client/testutil/suite.go index b7f5925b17e8..5e449e1f6aee 100644 --- a/x/auth/client/testutil/suite.go +++ b/x/auth/client/testutil/suite.go @@ -283,7 +283,7 @@ func (s *IntegrationTestSuite) TestCLIQueryTxCmd() { "happy case", []string{txRes.TxHash, fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, false, - "cosmos.bank.v1beta1.MsgSend", + "/cosmos.bank.v1beta1.MsgSend", }, } diff --git a/x/auth/tx/service_test.go b/x/auth/tx/service_test.go index 9d89e706293e..98b2fbcbe677 100644 --- a/x/auth/tx/service_test.go +++ b/x/auth/tx/service_test.go @@ -32,6 +32,8 @@ import ( banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" ) +var bankMsgSendEventAction = fmt.Sprintf("message.action='%s'", sdk.MsgTypeURL(&banktypes.MsgSend{})) + type IntegrationTestSuite struct { suite.Suite @@ -192,7 +194,7 @@ func (s IntegrationTestSuite) TestGetTxEvents_GRPC() { { "request with order-by", &tx.GetTxsEventRequest{ - Events: []string{"message.action='cosmos.bank.v1beta1.MsgSend'"}, + Events: []string{bankMsgSendEventAction}, OrderBy: tx.OrderBy_ORDER_BY_ASC, }, false, "", @@ -200,14 +202,14 @@ func (s IntegrationTestSuite) TestGetTxEvents_GRPC() { { "without pagination", &tx.GetTxsEventRequest{ - Events: []string{"message.action='cosmos.bank.v1beta1.MsgSend'"}, + Events: []string{bankMsgSendEventAction}, }, false, "", }, { "with pagination", &tx.GetTxsEventRequest{ - Events: []string{"message.action='cosmos.bank.v1beta1.MsgSend'"}, + Events: []string{bankMsgSendEventAction}, Pagination: &query.PageRequest{ CountTotal: false, Offset: 0, @@ -219,7 +221,7 @@ func (s IntegrationTestSuite) TestGetTxEvents_GRPC() { { "with multi events", &tx.GetTxsEventRequest{ - Events: []string{"message.action='cosmos.bank.v1beta1.MsgSend'", "message.module='bank'"}, + Events: []string{bankMsgSendEventAction, "message.module='bank'"}, }, false, "", }, @@ -262,43 +264,43 @@ func (s IntegrationTestSuite) TestGetTxEvents_GRPCGateway() { }, { "without pagination", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s", val.APIAddress, "message.action='cosmos.bank.v1beta1.MsgSend'"), + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s", val.APIAddress, bankMsgSendEventAction), false, "", }, { "with pagination", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&pagination.offset=%d&pagination.limit=%d", val.APIAddress, "message.action='cosmos.bank.v1beta1.MsgSend'", 0, 10), + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&pagination.offset=%d&pagination.limit=%d", val.APIAddress, bankMsgSendEventAction, 0, 10), false, "", }, { "valid request: order by asc", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s&order_by=ORDER_BY_ASC", val.APIAddress, "message.action='cosmos.bank.v1beta1.MsgSend'", "message.module='bank'"), + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s&order_by=ORDER_BY_ASC", val.APIAddress, bankMsgSendEventAction, "message.module='bank'"), false, "", }, { "valid request: order by desc", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s&order_by=ORDER_BY_DESC", val.APIAddress, "message.action='cosmos.bank.v1beta1.MsgSend'", "message.module='bank'"), + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s&order_by=ORDER_BY_DESC", val.APIAddress, bankMsgSendEventAction, "message.module='bank'"), false, "", }, { "invalid request: invalid order by", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s&order_by=invalid_order", val.APIAddress, "message.action='cosmos.bank.v1beta1.MsgSend'", "message.module='bank'"), + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s&order_by=invalid_order", val.APIAddress, bankMsgSendEventAction, "message.module='bank'"), true, "is not a valid tx.OrderBy", }, { "expect pass with multiple-events", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s", val.APIAddress, "message.action='cosmos.bank.v1beta1.MsgSend'", "message.module='bank'"), + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s", val.APIAddress, bankMsgSendEventAction, "message.module='bank'"), false, "", }, { "expect pass with escape event", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s", val.APIAddress, "message.action%3D'cosmos.bank.v1beta1.MsgSend'"), + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s", val.APIAddress, "message.action%3D'/cosmos.bank.v1beta1.MsgSend'"), false, "", }, From 666fe92911c7ba2b94f40fe133557c7cd9137384 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Tue, 27 Apr 2021 14:06:36 +0200 Subject: [PATCH 42/47] Fix rosetta, run make rosetta-data --- contrib/rosetta/node/data.tar.gz | Bin 38036 -> 38106 bytes server/rosetta/converter.go | 3 +-- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/contrib/rosetta/node/data.tar.gz b/contrib/rosetta/node/data.tar.gz index 43575efb8c8a8c1c2aaf94f053a62bb203ebc713..b9641689e0ede3d4559be433b5cf1c486b15ccfb 100644 GIT binary patch literal 38106 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%0D*k811IY==?kl?k-(6zirc?B-4 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

-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 diff --git a/server/rosetta/converter.go b/server/rosetta/converter.go index 1f4c9000e1d9..819c78da6f1d 100644 --- a/server/rosetta/converter.go +++ b/server/rosetta/converter.go @@ -7,7 +7,6 @@ import ( "reflect" auth "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/gogo/protobuf/proto" "github.com/tendermint/tendermint/crypto" @@ -241,7 +240,7 @@ func (c converter) Meta(msg sdk.Msg) (meta map[string]interface{}, err error) { // with the message proto name as type, and the raw fields // as metadata func (c converter) Ops(status string, msg sdk.Msg) ([]*rosettatypes.Operation, error) { - opName := proto.MessageName(msg) + opName := sdk.MsgTypeURL(msg) meta, err := c.Meta(msg) if err != nil { From aa2b26c4f2953d730c932a976074a4026f10e28c Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Tue, 27 Apr 2021 15:14:37 +0200 Subject: [PATCH 43/47] Fix rosetta thanks to froydi --- contrib/rosetta/configuration/transfer.ros | 2 +- contrib/rosetta/node/data.tar.gz | Bin 38106 -> 40794 bytes server/rosetta/client_online.go | 3 +-- server/rosetta/converter.go | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/contrib/rosetta/configuration/transfer.ros b/contrib/rosetta/configuration/transfer.ros index 74ebd2ddf50c..201a8ab7c449 100644 --- a/contrib/rosetta/configuration/transfer.ros +++ b/contrib/rosetta/configuration/transfer.ros @@ -83,7 +83,7 @@ transfer(3){ transfer.operations = [ { "operation_identifier":{"index":0}, - "type":"cosmos.bank.v1beta1.MsgSend", + "type":"/cosmos.bank.v1beta1.MsgSend", "account":{{sender.account_identifier}}, "metadata": { "amount": [ diff --git a/contrib/rosetta/node/data.tar.gz b/contrib/rosetta/node/data.tar.gz index b9641689e0ede3d4559be433b5cf1c486b15ccfb..3c37adcc162f05b37a08fd082a7c2c0ecb1919c4 100644 GIT binary patch literal 40794 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

$s&_OSd&EV-5n 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%0D*k811IY==?kl?k-(6zirc?B-4 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 Date: Thu, 29 Apr 2021 17:22:06 +0200 Subject: [PATCH 44/47] Address reviews --- CHANGELOG.md | 2 +- baseapp/baseapp.go | 16 ++++--- baseapp/msg_service_router.go | 12 +++-- server/grpc/server_test.go | 62 +++++++++++++++----------- types/tx/types.go | 6 ++- x/authz/client/rest/grpc_query_test.go | 2 +- 6 files changed, 57 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29fdb7e37a71..aeec7c830bd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,7 +60,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * CLI: removed `--text` flag from `show-node-id` command; the text format for public keys is not used any more - instead we use ProtoJSON. * (types) [\#9079](https://github.com/cosmos/cosmos-sdk/issues/9079) Add `AddAmount`/`SubAmount` methods to `sdk.Coin`. * [\#8628](https://github.com/cosmos/cosmos-sdk/issues/8628) Commands no longer print outputs using `stderr` by default -* [\#9139](https://github.com/cosmos/cosmos-sdk/pull/9139) Querying events via `ServiceMsg` TypeURLs (e.g. `/cosmos.bank.v1beta1.Msg/Send`) does not work anymore, please use concrete `Msg` TypeURLs instead (e.g. `/cosmos.bank.v1beta1/MsgSend`). +* [\#9139](https://github.com/cosmos/cosmos-sdk/pull/9139) Querying events via `ServiceMsg` TypeURLs (e.g. `/cosmos.bank.v1beta1.Msg/Send`) does not work anymore, please use concrete `Msg` TypeURLs instead (e.g. `/cosmos.bank.v1beta1.MsgSend`). ### API Breaking Changes diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index ed7b20fc91ad..6c66e6e008e3 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -704,19 +704,23 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s } var ( - msgResult *sdk.Result - msgEventAction string // name to use as value in event `message.action` - err error + msgResult *sdk.Result + eventMsgName string // name to use as value in event `message.action` + err error ) if handler := app.msgServiceRouter.Handler(msg); handler != nil { // ADR 031 request type routing msgResult, err = handler(ctx, msg) - msgEventAction = sdk.MsgTypeURL(msg) + eventMsgName = sdk.MsgTypeURL(msg) } else if legacyMsg, ok := msg.(legacytx.LegacyMsg); ok { // legacy sdk.Msg routing + // Assuming that the app developer has migrated all their Msgs to + // proto messages and has registered all `Msg services`, then this + // path should never be called, because all those Msgs should be + // registered within the `msgServiceRouter` already. msgRoute := legacyMsg.Route() - msgEventAction = legacyMsg.Type() + eventMsgName = legacyMsg.Type() handler := app.router.Route(ctx, msgRoute) if handler == nil { return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message route: %s; message index: %d", msgRoute, i) @@ -732,7 +736,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s } msgEvents := sdk.Events{ - sdk.NewEvent(sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyAction, msgEventAction)), + sdk.NewEvent(sdk.EventTypeMessage, sdk.NewAttribute(sdk.AttributeKeyAction, eventMsgName)), } msgEvents = msgEvents.AppendEvents(msgResult.GetEvents()) diff --git a/baseapp/msg_service_router.go b/baseapp/msg_service_router.go index ee55f5082715..086bcbd267c5 100644 --- a/baseapp/msg_service_router.go +++ b/baseapp/msg_service_router.go @@ -71,10 +71,7 @@ func (msr *MsgServiceRouter) RegisterService(sd *grpc.ServiceDesc, handler inter requestTypeName = sdk.MsgTypeURL(msg) return nil - }, func(_ context.Context, _ interface{}, _ *grpc.UnaryServerInfo, _ grpc.UnaryHandler) (interface{}, error) { - return nil, nil - }, - ) + }, noopInterceptor) // Check that the service Msg fully-qualified method name has already // been registered (via RegisterInterfaces). If the user registers a @@ -109,7 +106,7 @@ func (msr *MsgServiceRouter) RegisterService(sd *grpc.ServiceDesc, handler inter ) } - handler := func(ctx sdk.Context, req sdk.Msg) (*sdk.Result, error) { + msr.routes[requestTypeName] = func(ctx sdk.Context, req sdk.Msg) (*sdk.Result, error) { ctx = ctx.WithEventManager(sdk.NewEventManager()) interceptor := func(goCtx context.Context, _ interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { goCtx = context.WithValue(goCtx, sdk.SdkContextKey, ctx) @@ -129,8 +126,6 @@ func (msr *MsgServiceRouter) RegisterService(sd *grpc.ServiceDesc, handler inter return sdk.WrapServiceResult(ctx, resMsg, err) } - - msr.routes[requestTypeName] = handler } } @@ -140,3 +135,6 @@ func (msr *MsgServiceRouter) SetInterfaceRegistry(interfaceRegistry codectypes.I } func noopDecoder(_ interface{}) error { return nil } +func noopInterceptor(_ context.Context, _ interface{}, _ *grpc.UnaryServerInfo, _ grpc.UnaryHandler) (interface{}, error) { + return nil, nil +} diff --git a/server/grpc/server_test.go b/server/grpc/server_test.go index db43ccfddebf..b2534df0006c 100644 --- a/server/grpc/server_test.go +++ b/server/grpc/server_test.go @@ -27,7 +27,6 @@ import ( txtypes "github.com/cosmos/cosmos-sdk/types/tx" "github.com/cosmos/cosmos-sdk/types/tx/signing" authclient "github.com/cosmos/cosmos-sdk/x/auth/client" - "github.com/cosmos/cosmos-sdk/x/bank/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" ) @@ -164,32 +163,7 @@ func (s *IntegrationTestSuite) TestGRPCServer_GetTxsEvent() { func (s *IntegrationTestSuite) TestGRPCServer_BroadcastTx() { val0 := s.network.Validators[0] - // prepare txBuilder with msg - txBuilder := val0.ClientCtx.TxConfig.NewTxBuilder() - feeAmount := sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 10)} - gasLimit := testdata.NewTestGasLimit() - - // This sets a legacy Proto MsgSend. - err := txBuilder.SetMsgs(&types.MsgSend{ - FromAddress: val0.Address.String(), - ToAddress: val0.Address.String(), - Amount: sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 10)}, - }) - s.Require().NoError(err) - - txBuilder.SetFeeAmount(feeAmount) - txBuilder.SetGasLimit(gasLimit) - - // setup txFactory - txFactory := clienttx.Factory{}. - WithChainID(val0.ClientCtx.ChainID). - WithKeybase(val0.ClientCtx.Keyring). - WithTxConfig(val0.ClientCtx.TxConfig). - WithSignMode(signing.SignMode_SIGN_MODE_DIRECT) - - // Sign Tx. - err = authclient.SignTx(txFactory, val0.ClientCtx, val0.Moniker, txBuilder, false, true) - s.Require().NoError(err) + txBuilder := s.mkTxBuilder() txBytes, err := val0.ClientCtx.TxConfig.TxEncoder()(txBuilder.GetTx()) s.Require().NoError(err) @@ -237,6 +211,40 @@ func (s *IntegrationTestSuite) TestGRPCServerInvalidHeaderHeights() { } } +// mkTxBuilder creates a TxBuilder containing a signed tx from validator 0. +func (s IntegrationTestSuite) mkTxBuilder() client.TxBuilder { + val := s.network.Validators[0] + s.Require().NoError(s.network.WaitForNextBlock()) + + // prepare txBuilder with msg + txBuilder := val.ClientCtx.TxConfig.NewTxBuilder() + feeAmount := sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 10)} + gasLimit := testdata.NewTestGasLimit() + s.Require().NoError( + txBuilder.SetMsgs(&banktypes.MsgSend{ + FromAddress: val.Address.String(), + ToAddress: val.Address.String(), + Amount: sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 10)}, + }), + ) + txBuilder.SetFeeAmount(feeAmount) + txBuilder.SetGasLimit(gasLimit) + txBuilder.SetMemo("foobar") + + // setup txFactory + txFactory := clienttx.Factory{}. + WithChainID(val.ClientCtx.ChainID). + WithKeybase(val.ClientCtx.Keyring). + WithTxConfig(val.ClientCtx.TxConfig). + WithSignMode(signing.SignMode_SIGN_MODE_DIRECT) + + // Sign Tx. + err := authclient.SignTx(txFactory, val.ClientCtx, val.Moniker, txBuilder, false, true) + s.Require().NoError(err) + + return txBuilder +} + func TestIntegrationTestSuite(t *testing.T) { suite.Run(t, new(IntegrationTestSuite)) } diff --git a/types/tx/types.go b/types/tx/types.go index 9fe923aa20cb..84ce81edcbf8 100644 --- a/types/tx/types.go +++ b/types/tx/types.go @@ -25,7 +25,11 @@ func (t *Tx) GetMsgs() []sdk.Msg { anys := t.Body.Messages res := make([]sdk.Msg, len(anys)) for i, any := range anys { - res[i] = any.GetCachedValue().(sdk.Msg) + cached := any.GetCachedValue() + if cached == nil { + panic("Any cached value is nil. Transaction messages must be correctly packed Any values.") + } + res[i] = cached.(sdk.Msg) } return res } diff --git a/x/authz/client/rest/grpc_query_test.go b/x/authz/client/rest/grpc_query_test.go index 88e1b54eeeee..a94c65d6366f 100644 --- a/x/authz/client/rest/grpc_query_test.go +++ b/x/authz/client/rest/grpc_query_test.go @@ -190,7 +190,7 @@ func (s *IntegrationTestSuite) TestQueryAuthorizationsGRPC() { "", func() {}, func(authorizations *types.QueryAuthorizationsResponse) { - s.Require().Equal(1, len(authorizations.Authorizations)) + s.Require().Len(authorizations.Authorizations), 1) }, }, { From 774a4abc80c9eb44fa5ea992120da23a77e78f9d Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Thu, 29 Apr 2021 17:36:50 +0200 Subject: [PATCH 45/47] Fix test --- server/grpc/server_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/grpc/server_test.go b/server/grpc/server_test.go index b2534df0006c..e3bd71fe4feb 100644 --- a/server/grpc/server_test.go +++ b/server/grpc/server_test.go @@ -16,6 +16,7 @@ import ( "google.golang.org/grpc/metadata" rpb "google.golang.org/grpc/reflection/grpc_reflection_v1alpha" + "github.com/cosmos/cosmos-sdk/client" reflectionv1 "github.com/cosmos/cosmos-sdk/client/grpc/reflection" clienttx "github.com/cosmos/cosmos-sdk/client/tx" reflectionv2 "github.com/cosmos/cosmos-sdk/server/grpc/reflection/v2alpha1" @@ -97,6 +98,7 @@ func (s *IntegrationTestSuite) TestGRPCServer_BankBalance() { &banktypes.QueryBalanceRequest{Address: val0.Address.String(), Denom: denom}, grpc.Header(&header), ) + s.Require().NoError(err) blockHeight = header.Get(grpctypes.GRPCBlockHeightHeader) s.Require().NotEmpty(blockHeight[0]) // blockHeight is []string, first element is block height. } From d3c3381df794b379ac53694994fd50f8add4bfa3 Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Fri, 30 Apr 2021 12:38:34 +0200 Subject: [PATCH 46/47] Remove stray log --- x/authz/keeper/keeper_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/x/authz/keeper/keeper_test.go b/x/authz/keeper/keeper_test.go index 8c6308dc9ef1..91ea9b5fcbd1 100644 --- a/x/authz/keeper/keeper_test.go +++ b/x/authz/keeper/keeper_test.go @@ -1,7 +1,6 @@ package keeper_test import ( - "fmt" "testing" "time" @@ -165,7 +164,6 @@ func (s *TestSuite) TestKeeperFees() { executeMsgs, err = msgs.GetMessages() s.Require().NoError(err) - fmt.Println("TEST granteeAddr=", granteeAddr, "executeMsgs=", executeMsgs) result, err = app.AuthzKeeper.DispatchActions(s.ctx, granteeAddr, executeMsgs) s.Require().NoError(err) s.Require().NotNil(result) From e95ae323af7845c16a55a1e40e51b30bc5fb37ac Mon Sep 17 00:00:00 2001 From: Amaury M <1293565+amaurym@users.noreply.github.com> Date: Fri, 30 Apr 2021 12:48:24 +0200 Subject: [PATCH 47/47] Update CL --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3e6f6128a05..ae290136e41b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,7 +61,10 @@ Ref: https://keepachangelog.com/en/1.0.0/ * CLI: removed `--text` flag from `show-node-id` command; the text format for public keys is not used any more - instead we use ProtoJSON. * (types) [\#9079](https://github.com/cosmos/cosmos-sdk/issues/9079) Add `AddAmount`/`SubAmount` methods to `sdk.Coin`. * [\#8628](https://github.com/cosmos/cosmos-sdk/issues/8628) Commands no longer print outputs using `stderr` by default -* [\#9139](https://github.com/cosmos/cosmos-sdk/pull/9139) Querying events via `ServiceMsg` TypeURLs (e.g. `/cosmos.bank.v1beta1.Msg/Send`) does not work anymore, please use concrete `Msg` TypeURLs instead (e.g. `/cosmos.bank.v1beta1.MsgSend`). +* [\#9139](https://github.com/cosmos/cosmos-sdk/pull/9139) Querying events: + * via `ServiceMsg` TypeURLs (e.g. `message.action='/cosmos.bank.v1beta1.Msg/Send'`) does not work anymore, + * via legacy `msg.Type()` (e.g. `message.action='send'`) is being deprecated, new `Msg`s won't emit these events. + * Please use concrete `Msg` TypeURLs instead (e.g. `message.action='/cosmos.bank.v1beta1.MsgSend'`). ### API Breaking Changes