-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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(baseapp)!: remove hybrid handlers #22831
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,27 +6,22 @@ import ( | |
"reflect" | ||
|
||
gogoproto "github.com/cosmos/gogoproto/proto" | ||
"github.com/golang/protobuf/proto" //nolint: staticcheck // needed because gogoproto.Merge does not work consistently. See NOTE: comments. | ||
"google.golang.org/grpc" | ||
proto2 "google.golang.org/protobuf/proto" | ||
"google.golang.org/protobuf/reflect/protoreflect" | ||
"google.golang.org/protobuf/reflect/protoregistry" | ||
"google.golang.org/protobuf/runtime/protoiface" | ||
|
||
"github.com/cosmos/cosmos-sdk/codec" | ||
) | ||
|
||
var ( | ||
gogoType = reflect.TypeOf((*gogoproto.Message)(nil)).Elem() | ||
protov2Type = reflect.TypeOf((*proto2.Message)(nil)).Elem() | ||
protov2MarshalOpts = proto2.MarshalOptions{Deterministic: true} | ||
gogoType = reflect.TypeOf((*gogoproto.Message)(nil)).Elem() | ||
protov2Type = reflect.TypeOf((*proto2.Message)(nil)).Elem() | ||
) | ||
|
||
type Handler = func(ctx context.Context, request, response protoiface.MessageV1) error | ||
|
||
// MakeHybridHandler returns a handler that can handle both gogo and protov2 messages, no matter | ||
// MakeHandler returns a handler that can handle both gogo and protov2 messages, no matter | ||
// if the handler is a gogo or protov2 handler. | ||
func MakeHybridHandler(cdc codec.BinaryCodec, sd *grpc.ServiceDesc, method grpc.MethodDesc, handler interface{}) (Handler, error) { | ||
func MakeHandler(sd *grpc.ServiceDesc, method grpc.MethodDesc, handler interface{}) (Handler, error) { | ||
Comment on lines
+22
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Update the function comment to accurately describe the function's behavior The comment for |
||
methodFullName := protoreflect.FullName(fmt.Sprintf("%s.%s", sd.ServiceName, method.MethodName)) | ||
desc, err := gogoproto.HybridResolver.FindDescriptorByName(methodFullName) | ||
if err != nil { | ||
|
@@ -42,150 +37,36 @@ func MakeHybridHandler(cdc codec.BinaryCodec, sd *grpc.ServiceDesc, method grpc. | |
return nil, err | ||
} | ||
if isProtov2Handler { | ||
return makeProtoV2HybridHandler(methodDesc, cdc, method, handler) | ||
return nil, fmt.Errorf("protov2 handlers are not allowed %s", methodFullName) | ||
} | ||
return makeGogoHybridHandler(methodDesc, cdc, method, handler) | ||
return makeGogoHandler(methodDesc, method, handler) | ||
} | ||
|
||
// makeProtoV2HybridHandler returns a handler that can handle both gogo and protov2 messages. | ||
func makeProtoV2HybridHandler(prefMethod protoreflect.MethodDescriptor, cdc codec.BinaryCodec, method grpc.MethodDesc, handler any) (Handler, error) { | ||
// it's a protov2 handler, if a gogo counterparty is not found we cannot handle gogo messages. | ||
gogoExists := gogoproto.MessageType(string(prefMethod.Output().FullName())) != nil | ||
if !gogoExists { | ||
return func(ctx context.Context, inReq, outResp protoiface.MessageV1) error { | ||
protov2Request, ok := inReq.(proto2.Message) | ||
if !ok { | ||
return fmt.Errorf("invalid request type %T, method %s does not accept gogoproto messages", inReq, prefMethod.FullName()) | ||
} | ||
resp, err := method.Handler(handler, ctx, func(msg any) error { | ||
proto2.Merge(msg.(proto2.Message), protov2Request) | ||
return nil | ||
}, nil) | ||
if err != nil { | ||
return err | ||
} | ||
// merge on the resp | ||
proto2.Merge(outResp.(proto2.Message), resp.(proto2.Message)) | ||
return nil | ||
}, nil | ||
} | ||
func makeGogoHandler(prefMethod protoreflect.MethodDescriptor, method grpc.MethodDesc, handler any) (Handler, error) { | ||
return func(ctx context.Context, inReq, outResp protoiface.MessageV1) error { | ||
// we check if the request is a protov2 message. | ||
switch m := inReq.(type) { | ||
case proto2.Message: | ||
// we can just call the handler after making a copy of the message, for safety reasons. | ||
resp, err := method.Handler(handler, ctx, func(msg any) error { | ||
proto2.Merge(msg.(proto2.Message), m) | ||
return nil | ||
}, nil) | ||
if err != nil { | ||
return err | ||
} | ||
// merge on the resp | ||
proto2.Merge(outResp.(proto2.Message), resp.(proto2.Message)) | ||
return nil | ||
case gogoproto.Message: | ||
// we need to marshal and unmarshal the request. | ||
requestBytes, err := cdc.Marshal(m) | ||
if err != nil { | ||
return err | ||
} | ||
resp, err := method.Handler(handler, ctx, func(msg any) error { | ||
// unmarshal request into the message. | ||
return proto2.Unmarshal(requestBytes, msg.(proto2.Message)) | ||
}, nil) | ||
if err != nil { | ||
return err | ||
} | ||
// the response is a protov2 message, so we cannot just return it. | ||
// since the request came as gogoproto, we expect the response | ||
// to also be gogoproto. | ||
respBytes, err := protov2MarshalOpts.Marshal(resp.(proto2.Message)) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// unmarshal response into a gogo message. | ||
return cdc.Unmarshal(respBytes, outResp.(gogoproto.Message)) | ||
default: | ||
panic("unreachable") | ||
// we do not handle protov2 | ||
_, ok := inReq.(proto2.Message) | ||
if ok { | ||
return fmt.Errorf("invalid request type %T, method %s does not accept protov2 messages", inReq, prefMethod.FullName()) | ||
} | ||
}, nil | ||
} | ||
|
||
func makeGogoHybridHandler(prefMethod protoreflect.MethodDescriptor, cdc codec.BinaryCodec, method grpc.MethodDesc, handler any) (Handler, error) { | ||
// it's a gogo handler, we check if the existing protov2 counterparty exists. | ||
_, err := protoregistry.GlobalTypes.FindMessageByName(prefMethod.Output().FullName()) | ||
if err != nil { | ||
// this can only be a gogo message. | ||
return func(ctx context.Context, inReq, outResp protoiface.MessageV1) error { | ||
_, ok := inReq.(proto2.Message) | ||
if ok { | ||
return fmt.Errorf("invalid request type %T, method %s does not accept protov2 messages", inReq, prefMethod.FullName()) | ||
} | ||
resp, err := method.Handler(handler, ctx, func(msg any) error { | ||
// merge! ref: https://github.com/cosmos/cosmos-sdk/issues/18003 | ||
// NOTE: using gogoproto.Merge will fail for some reason unknown to me, but | ||
// using proto.Merge with gogo messages seems to work fine. | ||
proto.Merge(msg.(gogoproto.Message), inReq) | ||
return nil | ||
}, nil) | ||
if err != nil { | ||
return err | ||
} | ||
// merge resp, ref: https://github.com/cosmos/cosmos-sdk/issues/18003 | ||
// NOTE: using gogoproto.Merge will fail for some reason unknown to me, but | ||
// using proto.Merge with gogo messages seems to work fine. | ||
proto.Merge(outResp.(gogoproto.Message), resp.(gogoproto.Message)) | ||
return nil | ||
}, nil | ||
} | ||
// this is a gogo handler, and we have a protov2 counterparty. | ||
return func(ctx context.Context, inReq, outResp protoiface.MessageV1) error { | ||
switch m := inReq.(type) { | ||
case proto2.Message: | ||
// we need to marshal and unmarshal the request. | ||
requestBytes, err := protov2MarshalOpts.Marshal(m) | ||
if err != nil { | ||
return err | ||
} | ||
resp, err := method.Handler(handler, ctx, func(msg any) error { | ||
// unmarshal request into the message. | ||
return cdc.Unmarshal(requestBytes, msg.(gogoproto.Message)) | ||
}, nil) | ||
if err != nil { | ||
return err | ||
} | ||
// the response is a gogo message, so we cannot just return it. | ||
// since the request came as protov2, we expect the response | ||
// to also be protov2. | ||
respBytes, err := cdc.Marshal(resp.(gogoproto.Message)) | ||
if err != nil { | ||
return err | ||
} | ||
// now we unmarshal back into a protov2 message. | ||
return proto2.Unmarshal(respBytes, outResp.(proto2.Message)) | ||
case gogoproto.Message: | ||
// we can just call the handler after making a copy of the message, for safety reasons. | ||
resp, err := method.Handler(handler, ctx, func(msg any) error { | ||
// ref: https://github.com/cosmos/cosmos-sdk/issues/18003 | ||
asGogoProto := msg.(gogoproto.Message) | ||
// NOTE: using gogoproto.Merge will fail for some reason unknown to me, but | ||
// using proto.Merge with gogo messages seems to work fine. | ||
proto.Merge(asGogoProto, m) | ||
return nil | ||
}, nil) | ||
if err != nil { | ||
return err | ||
} | ||
// merge on the resp, ref: https://github.com/cosmos/cosmos-sdk/issues/18003 | ||
// NOTE: using gogoproto.Merge will fail for some reason unknown to me, but | ||
// using proto.Merge with gogo messages seems to work fine. | ||
proto.Merge(outResp.(gogoproto.Message), resp.(gogoproto.Message)) | ||
resp, err := method.Handler(handler, ctx, func(msg any) error { | ||
// reflection to copy from inReq to msg | ||
dstVal := reflect.ValueOf(msg).Elem() | ||
srcVal := reflect.ValueOf(inReq).Elem() | ||
dstVal.Set(srcVal) | ||
return nil | ||
default: | ||
panic("unreachable") | ||
}, nil) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// reflection to copy from resp to outResp | ||
dstVal := reflect.ValueOf(outResp).Elem() | ||
srcVal := reflect.ValueOf(resp).Elem() | ||
dstVal.Set(srcVal) | ||
|
||
return nil | ||
}, nil | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -81,7 +81,7 @@ func (failingMsgRouter) ResponseNameByMsgName(msgName string) string { | |
panic("message router not set") | ||
} | ||
|
||
func (failingMsgRouter) HybridHandlerByMsgName(msgName string) func(ctx context.Context, req, resp protoiface.MessageV1) error { | ||
func (failingMsgRouter) HandlerByMsgName(msgName string) func(ctx context.Context, req, resp protoiface.MessageV1) error { | ||
panic("message router not set") | ||
} | ||
Comment on lines
+84
to
86
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Update test file to match the renamed method The method
🔗 Analysis chainLGTM! Verify consistent renaming across the codebase The method rename from 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Verify consistent renaming of HybridHandlerByMsgName across codebase
rg -l "HybridHandlerByMsgName"
Length of output: 65 Script: #!/bin/bash
# Check the context of HybridHandlerByMsgName usage in the test file
rg -A 5 "HybridHandlerByMsgName" baseapp/msg_service_router_test.go
Length of output: 230 |
||
|
||
|
@@ -91,7 +91,7 @@ type failingQueryRouter struct { | |
baseapp.QueryRouter | ||
} | ||
|
||
func (failingQueryRouter) HybridHandlerByRequestName(name string) []func(ctx context.Context, req, resp protoiface.MessageV1) error { | ||
func (failingQueryRouter) HandlerByRequestName(name string) []func(ctx context.Context, req, resp protoiface.MessageV1) error { | ||
panic("query router not set") | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to break this interface name?
This is a public interface and could break consumers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is fine, sourcegraph tells me there are no users.
We ask user to migrate to use core services anyway via environment, so those API should be hidden from them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, but couldn't we lower the impact of this change by just maintaining the name of this function for the interface?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we gain anything by breaking this name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The impact is low as there are no consumers. Hybrid handlers never got fully used as we decided after 0.50 to not support proto v1 & proto v2. Keeping such API name is more confusing than simplifying the API imho.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since there are no consumers and the code is no longer working hybrid i feel like its fine. Currently users dont make it this far and the ones that do cant actually swap the router other than in 52+ because there was no interface before and the baseapp struct took the concrete implementation.