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

fix(baseapp): hybrid handlers do not drop any #22866

Merged
merged 1 commit into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
25 changes: 25 additions & 0 deletions baseapp/grpcrouter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,31 @@ func TestGRPCRouterHybridHandlers(t *testing.T) {
}
assertRouterBehaviour(helper)
})

t.Run("any cached value is not dropped", func(t *testing.T) {
tac0turtle marked this conversation as resolved.
Show resolved Hide resolved
// ref: https://github.com/cosmos/cosmos-sdk/issues/22779
qr := baseapp.NewGRPCQueryRouter()
interfaceRegistry := testdata.NewTestInterfaceRegistry()
testdata.RegisterInterfaces(interfaceRegistry)
qr.SetInterfaceRegistry(interfaceRegistry)
testdata.RegisterQueryServer(qr, testdata.QueryImpl{})
helper := &baseapp.QueryServiceTestHelper{
GRPCQueryRouter: qr,
Ctx: sdk.Context{}.WithContext(context.Background()),
}

anyMsg, err := types.NewAnyWithValue(&testdata.Dog{})
require.NoError(t, err)

handler := qr.HybridHandlerByRequestName("testpb.TestAnyRequest")[0]

resp := new(testdata.TestAnyResponse)
err = handler(helper.Ctx, &testdata.TestAnyRequest{
AnyAnimal: anyMsg,
}, resp)
require.NoError(t, err)
require.NotNil(t, resp.HasAnimal.Animal.GetCachedValue())
})
}

func TestRegisterQueryServiceTwice(t *testing.T) {
Expand Down
28 changes: 10 additions & 18 deletions baseapp/internal/protocompat/protocompat.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ 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"
Expand Down Expand Up @@ -124,19 +123,13 @@ func makeGogoHybridHandler(prefMethod protoreflect.MethodDescriptor, cdc codec.B
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)
setPointer(msg, inReq)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure Type Safety in setPointer Function Usage

The setPointer function is used at lines 126, 132, 164, and 170 to replace proto.Merge. However, it operates without type or nil checks, which could lead to runtime panics if dst and src are nil or of incompatible types.

Consider adding type assertions and nil checks to setPointer to ensure safe usage. Here's a suggested modification:

func setPointer(dst, src any) {
+   dstValue := reflect.ValueOf(dst)
+   srcValue := reflect.ValueOf(src)
+   if !dstValue.IsValid() || !srcValue.IsValid() {
+       panic("dst and src must be valid")
+   }
+   if dstValue.IsNil() || srcValue.IsNil() {
+       panic("dst and src must be non-nil")
+   }
+   if dstValue.Elem().Type() != srcValue.Elem().Type() {
+       panic("dst and src must have the same type")
+   }
    dstValue.Elem().Set(srcValue.Elem())
}

And update the calls to handle potential errors or panics appropriately.

Also applies to: 132-132, 164-164, 170-170

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))
setPointer(outResp, resp)
return nil
}, nil
}
Expand Down Expand Up @@ -168,20 +161,13 @@ func makeGogoHybridHandler(prefMethod protoreflect.MethodDescriptor, cdc codec.B
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)
setPointer(msg, 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))
setPointer(outResp, resp)
return nil
default:
panic("unreachable")
Expand Down Expand Up @@ -246,3 +232,9 @@ func ResponseFullNameFromMethodDesc(sd *grpc.ServiceDesc, method grpc.MethodDesc
}
return methodDesc.Output().FullName(), nil
}

// since proto.Merge breaks due to the custom cosmos sdk any, we are forced to do this ugly setPointer hack.
// ref: https://github.com/cosmos/cosmos-sdk/issues/22779
func setPointer(dst, src any) {
reflect.ValueOf(dst).Elem().Set(reflect.ValueOf(src).Elem())
}
Comment on lines +236 to +240
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add Type Checks and Error Handling in setPointer Function

The setPointer function currently lacks type safety and nil checks, which might cause runtime panics if misused.

Modify setPointer to include type checks and error handling:

func setPointer(dst, src any) error {
    dstValue := reflect.ValueOf(dst)
    srcValue := reflect.ValueOf(src)
    if !dstValue.IsValid() || !srcValue.IsValid() {
        return fmt.Errorf("dst and src must be valid")
    }
    if dstValue.IsNil() || srcValue.IsNil() {
        return fmt.Errorf("dst and src must be non-nil")
    }
    dstElem := dstValue.Elem()
    srcElem := srcValue.Elem()
    if dstElem.Type() != srcElem.Type() {
        return fmt.Errorf("dst and src must have the same type")
    }
    dstElem.Set(srcElem)
    return nil
}

Ensure that calls to setPointer handle the returned error:

- setPointer(msg, inReq)
+ if err := setPointer(msg, inReq); err != nil {
+     return err
+ }

Loading