Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(core): re-add handlers #21575

Merged
merged 11 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 115 additions & 15 deletions core/appmodule/v2/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package appmodulev2

import (
"context"
"fmt"

transaction "cosmossdk.io/core/transaction"
)
Expand All @@ -16,47 +17,146 @@ type (
PostMsgHandler = func(ctx context.Context, msg, msgResp transaction.Msg) error
)

// msg handler

// PreMsgRouter is a router that allows you to register PreMsgHandlers for specific message types.
type PreMsgRouter interface {
// RegisterPreHandler will register a specific message handler hooking into the message with
// the provided name.
RegisterPreHandler(msgName string, handler PreMsgHandler)
RegisterPreMsgHandler(handler PreMsgHandler) error
// RegisterGlobalPreHandler will register a global message handler hooking into any message
// being executed.
RegisterGlobalPreHandler(handler PreMsgHandler)
RegisterGlobalPreMsgHandler(handler PreMsgHandler) error
}

// HasPreMsgHandlers is an interface that modules must implement if they want to register PreMsgHandlers.
type HasPreMsgHandlers interface {
RegisterPreMsgHandlers(router PreMsgRouter)
}

type MsgRouter interface {
Register(msgName string, handler Handler)
}

type HasMsgHandlers interface {
RegisterMsgHandlers(router MsgRouter)
// RegisterMsgPreHandler is a helper function that modules can use to not lose type safety when registering PreMsgHandler to the
// PreMsgRouter. Example usage:
// ```go
//
// func (h Handlers) BeforeSend(ctx context.Context, req *types.MsgSend) (*types.QueryBalanceResponse, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Response type here is invalid

// ... before send logic ...
// }
//
// func (m Module) RegisterPreMsgHandlers(router appmodule.PreMsgRouter) {
// handlers := keeper.NewHandlers(m.keeper)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a way to use it. it's to simply define before send on the keeper and handle it via keeper, if we wanto to do it msg server style and have a wrapper like handlers then that is also fine.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like to think of the keeper as something only for state (basically, just holding public/private collections), and the business logic is somewhere else.

// appmodule.RegisterMsgPreHandler(router, handlers.BeforeSend)
// }
//
// ```
func RegisterMsgPreHandler[Req transaction.Msg](
router PreMsgRouter,
handler func(ctx context.Context, msg Req) error,
) {
untypedHandler := func(ctx context.Context, m transaction.Msg) error {
typed, ok := m.(Req)
if !ok {
return fmt.Errorf("unexpected type %T, wanted: %T", m, *new(Req))
}
return handler(ctx, typed)
}
router.RegisterPreMsgHandler(untypedHandler)
}

// PostMsgRouter is a router that allows you to register PostMsgHandlers for specific message types.
type PostMsgRouter interface {
// RegisterPostHandler will register a specific message handler hooking after the execution of message with
// the provided name.
RegisterPostHandler(msgName string, handler PostMsgHandler)
RegisterPostMsgHandler(handler PostMsgHandler) error
// RegisterGlobalPostHandler will register a global message handler hooking after the execution of any message.
RegisterGlobalPostHandler(handler PostMsgHandler)
RegisterGlobalPostMsgHandler(handler PostMsgHandler) error
}

// HasPostMsgHandlers is an interface that modules must implement if they want to register PostMsgHandlers.
type HasPostMsgHandlers interface {
RegisterPostMsgHandlers(router PostMsgRouter)
}

// query handler
// RegisterPostHandler is a helper function that modules can use to not lose type safety when registering handlers to the
// PostMsgRouter. Example usage:
// ```go
//
// func (h Handlers) AfterSend(ctx context.Context, req *types.MsgSend, resp *types.MsgSendResponse) error {
// ... query logic ...
// }
//
// func (m Module) RegisterPostMsgHandlers(router appmodule.PostMsgRouter) {
// handlers := keeper.NewHandlers(m.keeper)
// appmodule.RegisterPostMsgHandler(router, handlers.AfterSend)
// }
//
// ```
func RegisterPostMsgHandler[Req, Resp transaction.Msg](
router PostMsgRouter,
handler func(ctx context.Context, msg Req, msgResp Resp) error,
) {
untypedHandler := func(ctx context.Context, m, mResp transaction.Msg) error {
typed, ok := m.(Req)
if !ok {
return fmt.Errorf("unexpected type %T, wanted: %T", m, *new(Req))
}
typedResp, ok := mResp.(Resp)
if !ok {
return fmt.Errorf("unexpected type %T, wanted: %T", m, *new(Resp))
}
return handler(ctx, typed, typedResp)
}
router.RegisterPostMsgHandler(untypedHandler)
}

type QueryRouter interface {
Register(queryName string, handler Handler)
// MsgRouter is a router that allows you to register Handlers for specific message types.
type MsgRouter interface {
RegisterHandler(handler Handler) error
}

// HasMsgHandlers is an interface that modules must implement if they want to register Handlers.
type HasMsgHandlers interface {
RegisterMsgHandlers(router MsgRouter)
}

// QueryRouter is a router that allows you to register QueryHandlers for specific query types.
type QueryRouter = MsgRouter

// HasQueryHandlers is an interface that modules must implement if they want to register QueryHandlers.
type HasQueryHandlers interface {
RegisterQueryHandlers(router QueryRouter)
}

// RegisterMsgHandler is a helper function that modules can use to not lose type safety when registering handlers to the MsgRouter and Query Router.
// Example usage:
// ```go
//
// func (h Handlers) Mint(ctx context.Context, req *types.MsgMint) (*types.MsgMintResponse, error) {
// ... query logic ...
julienrbrt marked this conversation as resolved.
Show resolved Hide resolved
// }
//
// func (h Handlers) QueryBalance(ctx context.Context, req *types.QueryBalanceRequest) (*types.QueryBalanceResponse, error) {
// ... query logic ...
// }
//
// func (m Module) RegisterMsgHandlers(router appmodule.MsgRouter) {
// handlers := keeper.NewHandlers(m.keeper)
// appmodule.RegisterHandler(router, handlers.MsgMint)
// }
//
// func (m Module) RegisterQueryHandlers(router appmodule.QueryRouter) {
// handlers := keeper.NewHandlers(m.keeper)
// appmodule.RegisterHandler(router, handlers.QueryBalance)
// }
//
// ```
func RegisterHandler[R MsgRouter, Req, Resp transaction.Msg](
router R,
handler func(ctx context.Context, msg Req) (msgResp Resp, err error),
) {
untypedHandler := func(ctx context.Context, m transaction.Msg) (transaction.Msg, error) {
typed, ok := m.(Req)
if !ok {
return nil, fmt.Errorf("unexpected type %T, wanted: %T", m, *new(Req))
}
return handler(ctx, typed)
}
router.RegisterHandler(untypedHandler)
}
9 changes: 5 additions & 4 deletions runtime/v2/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -604,15 +604,16 @@ func registerServices[T transaction.Tx](s hasServicesV1, app *App[T], registry *
err: nil,
}

err := s.RegisterServices(c)
if err != nil {
if err := s.RegisterServices(c); err != nil {
return fmt.Errorf("unable to register services: %w", err)
}

// merge maps
for path, decoder := range c.grpcQueryDecoders {
app.GRPCMethodsToMessageMap[path] = decoder
}
return nil

return c.err
}

var _ grpc.ServiceRegistrar = (*configurator)(nil)
Expand Down Expand Up @@ -706,7 +707,7 @@ func registerMethod(
return "", err
}

return string(requestName), stfRouter.RegisterHandler(string(requestName), func(
return string(requestName), stfRouter.RegisterHandler(func(
ctx context.Context,
msg transaction.Msg,
) (resp transaction.Msg, err error) {
Expand Down
54 changes: 49 additions & 5 deletions server/v2/stf/stf_router.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"context"
"errors"
"fmt"
"reflect"
Fixed Show fixed Hide fixed
"strings"

gogoproto "github.com/cosmos/gogoproto/proto"
Expand Down Expand Up @@ -32,7 +33,12 @@
globalPostHandlers []appmodulev2.PostMsgHandler
}

func (b *MsgRouterBuilder) RegisterHandler(msgType string, handler appmodulev2.Handler) error {
func (b *MsgRouterBuilder) RegisterHandler(handler appmodulev2.Handler) error {
msgType, err := msgTypeURLFromHandler(handler)
if err != nil {
return err
}

// panic on override
if _, ok := b.handlers[msgType]; ok {
return fmt.Errorf("handler already registered: %s", msgType)
Expand All @@ -41,20 +47,34 @@
return nil
}

func (b *MsgRouterBuilder) RegisterGlobalPreHandler(handler appmodulev2.PreMsgHandler) {
func (b *MsgRouterBuilder) RegisterGlobalPreMsgHandler(handler appmodulev2.PreMsgHandler) error {
b.globalPreHandlers = append(b.globalPreHandlers, handler)
return nil
}

func (b *MsgRouterBuilder) RegisterPreHandler(msgType string, handler appmodulev2.PreMsgHandler) {
func (b *MsgRouterBuilder) RegisterPreMsgHandler(handler appmodulev2.PreMsgHandler) error {
msgType, err := msgTypeURLFromHandler(handler)
if err != nil {
return err
}

b.preHandlers[msgType] = append(b.preHandlers[msgType], handler)
return nil
}

func (b *MsgRouterBuilder) RegisterPostHandler(msgType string, handler appmodulev2.PostMsgHandler) {
func (b *MsgRouterBuilder) RegisterPostMsgHandler(handler appmodulev2.PostMsgHandler) error {
msgType, err := msgTypeURLFromHandler(handler)
if err != nil {
return err
}

b.postHandlers[msgType] = append(b.postHandlers[msgType], handler)
return nil
}

func (b *MsgRouterBuilder) RegisterGlobalPostHandler(handler appmodulev2.PostMsgHandler) {
func (b *MsgRouterBuilder) RegisterGlobalPostMsgHandler(handler appmodulev2.PostMsgHandler) error {
b.globalPostHandlers = append(b.globalPostHandlers, handler)
return nil
}

func (b *MsgRouterBuilder) HandlerExists(msgType string) bool {
Expand Down Expand Up @@ -135,6 +155,30 @@
}
}

func msgTypeURLFromHandler(handler any) (string, error) {
handlerType := reflect.TypeOf(handler)
if handlerType.Kind() != reflect.Func {
return "", fmt.Errorf("handler must be a function")
}
if handlerType.NumIn() != 2 && handlerType.NumIn() != 3 {
Fixed Show fixed Hide fixed
return "", fmt.Errorf("handler must have 2-3 input parameters")
}

// Get the type of the second parameter (transaction.Msg)
msgType := handlerType.In(1)

if !msgType.Implements(reflect.TypeOf((*transaction.Msg)(nil)).Elem()) {
return "", fmt.Errorf("second parameter must implement transaction.Msg")
}

msgName := msgTypeURL(*reflect.New(msgType).Interface().(*gogoproto.Message))
if msgName == "" {
return "", fmt.Errorf("could not get message name")
}

return msgName, nil
}

// msgTypeURL returns the TypeURL of a proto message.
func msgTypeURL(msg gogoproto.Message) string {
return gogoproto.MessageName(msg)
Expand Down
1 change: 0 additions & 1 deletion server/v2/stf/stf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ func addMsgHandlerToSTF[T any, PT interface {
t.Helper()
msgRouterBuilder := NewMsgRouterBuilder()
err := msgRouterBuilder.RegisterHandler(
msgTypeURL(PT(new(T))),
func(ctx context.Context, msg transaction.Msg) (msgResp transaction.Msg, err error) {
typedReq := msg.(PT)
typedResp, err := handler(ctx, typedReq)
Expand Down
Loading