Skip to content

Commit 8409aec

Browse files
songgaoyealjo242technicallyty
authored
fix(client): Add GetHeightFromMetadataStrict API to grpc client for better error handling. (#25804)
Co-authored-by: Alex | Cosmos Labs <[email protected]> Co-authored-by: Tyler <[email protected]>
1 parent 626a8bd commit 8409aec

3 files changed

Lines changed: 87 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ Ref: https://keepachangelog.com/en/1.0.0/
9595
* (cli) [#25485](https://github.com/cosmos/cosmos-sdk/pull/25485) Avoid failed to convert address field in `withdraw-validator-commission` cmd.
9696
* (baseapp) [#25642](https://github.com/cosmos/cosmos-sdk/pull/25642) Mark pre-block events for indexing based on local configuration.
9797
* (x/bank) [#25751](https://github.com/cosmos/cosmos-sdk/pull/25751) Fix recipient address in events.
98-
* (server/config) [#25806] (https://github.com/cosmos/cosmos-sdk/pull/25806) fix: add missing commas in historical gRPC config template #25806.
98+
* (server/config) [#25806](https://github.com/cosmos/cosmos-sdk/pull/25806) fix: add missing commas in historical gRPC config template.
99+
* (client) [#25804](https://github.com/cosmos/cosmos-sdk/pull/25804) Add `GetHeightFromMetadataStrict` API to `grpc` client for better error handling.
99100

100101
### Deprecated
101102

client/grpc_query.go

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -75,23 +75,38 @@ var fallBackCodec = codec.NewProtoCodec(types.NewInterfaceRegistry())
7575

7676
// GetHeightFromMetadata extracts the block height from gRPC metadata in the context.
7777
// Returns 0 if no valid height is found.
78+
//
79+
// Deprecated: This function silently ignores parse errors and negative heights for backward compatibility.
80+
// Use GetHeightFromMetadataStrict for stricter error handling.
7881
func GetHeightFromMetadata(grpcCtx gocontext.Context) int64 {
82+
height, _ := GetHeightFromMetadataStrict(grpcCtx)
83+
return height
84+
}
85+
86+
// GetHeightFromMetadataStrict extracts the block height from gRPC metadata in the context.
87+
// Returns an error if the height header is present but invalid (parse error or negative).
88+
// Returns 0 with no error if no height header is present.
89+
func GetHeightFromMetadataStrict(grpcCtx gocontext.Context) (int64, error) {
7990
md, ok := metadata.FromOutgoingContext(grpcCtx)
8091
if !ok {
81-
return 0
92+
return 0, nil
8293
}
8394
heights := md.Get(grpctypes.GRPCBlockHeightHeader)
8495
if len(heights) == 0 {
85-
return 0
96+
return 0, nil
8697
}
8798
height, err := strconv.ParseInt(heights[0], 10, 64)
8899
if err != nil {
89-
return 0
100+
return 0, errorsmod.Wrapf(
101+
sdkerrors.ErrInvalidRequest,
102+
"invalid height header %q: %v", grpctypes.GRPCBlockHeightHeader, err)
90103
}
91104
if height < 0 {
92-
return 0
105+
return 0, errorsmod.Wrapf(
106+
sdkerrors.ErrInvalidRequest,
107+
"height (%d) from %q must be >= 0", height, grpctypes.GRPCBlockHeightHeader)
93108
}
94-
return height
109+
return height, nil
95110
}
96111

97112
// Invoke implements the grpc ClientConn.Invoke method
@@ -102,15 +117,19 @@ func (ctx Context) Invoke(grpcCtx gocontext.Context, method string, req, reply a
102117
// 2-2. or we are querying for state, in which case we call ABCI's Query if grpc client not set.
103118

104119
// In both cases, we don't allow empty request args (it will panic unexpectedly).
105-
if reflect.ValueOf(req).IsNil() {
120+
if req == nil {
121+
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "request cannot be nil")
122+
}
123+
reqVal := reflect.ValueOf(req)
124+
if reqVal.Kind() == reflect.Ptr && reqVal.IsNil() {
106125
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "request cannot be nil")
107126
}
108127

109128
// Case 1. Broadcasting a Tx.
110129
if reqProto, ok := req.(*tx.BroadcastTxRequest); ok {
111130
res, ok := reply.(*tx.BroadcastTxResponse)
112131
if !ok {
113-
return errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "expected %T, got %T", (*tx.BroadcastTxResponse)(nil), req)
132+
return errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "expected %T, got %T", (*tx.BroadcastTxResponse)(nil), reply)
114133
}
115134

116135
broadcastRes, err := TxServiceBroadcast(grpcCtx, ctx, reqProto)
@@ -119,7 +138,7 @@ func (ctx Context) Invoke(grpcCtx gocontext.Context, method string, req, reply a
119138
}
120139
*res = *broadcastRes
121140

122-
return err
141+
return nil
123142
}
124143

125144
if ctx.GRPCClient != nil {
@@ -128,7 +147,10 @@ func (ctx Context) Invoke(grpcCtx gocontext.Context, method string, req, reply a
128147
if ctx.GRPCConnProvider != nil {
129148
height := ctx.Height
130149
if height <= 0 {
131-
height = GetHeightFromMetadata(grpcCtx)
150+
height, err = GetHeightFromMetadataStrict(grpcCtx)
151+
if err != nil {
152+
return err
153+
}
132154
}
133155

134156
grpcConn = ctx.GRPCConnProvider.GetGRPCConn(height)
@@ -143,11 +165,9 @@ func (ctx Context) Invoke(grpcCtx gocontext.Context, method string, req, reply a
143165
}
144166

145167
// parse height header
146-
height := GetHeightFromMetadata(grpcCtx)
147-
if height < 0 {
148-
return errorsmod.Wrapf(
149-
sdkerrors.ErrInvalidRequest,
150-
"client.Context.Invoke: height (%d) from %q must be >= 0", height, grpctypes.GRPCBlockHeightHeader)
168+
height, err := GetHeightFromMetadataStrict(grpcCtx)
169+
if err != nil {
170+
return err
151171
}
152172

153173
if height > 0 {

client/grpc_query_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,3 +330,54 @@ func TestGetHeightFromMetadata(t *testing.T) {
330330
})
331331
}
332332
}
333+
334+
func TestGetHeightFromMetadataStrict(t *testing.T) {
335+
tests := []struct {
336+
name string
337+
setupContext func() context.Context
338+
expectedHeight int64
339+
expectError bool
340+
}{
341+
{
342+
name: "valid height",
343+
setupContext: func() context.Context {
344+
md := metadata.Pairs(grpctypes.GRPCBlockHeightHeader, "123")
345+
return metadata.NewOutgoingContext(context.Background(), md)
346+
},
347+
expectedHeight: 123,
348+
},
349+
{
350+
name: "no metadata",
351+
setupContext: context.Background,
352+
},
353+
{
354+
name: "negative height errors",
355+
setupContext: func() context.Context {
356+
md := metadata.Pairs(grpctypes.GRPCBlockHeightHeader, "-10")
357+
return metadata.NewOutgoingContext(context.Background(), md)
358+
},
359+
expectError: true,
360+
},
361+
{
362+
name: "invalid height errors",
363+
setupContext: func() context.Context {
364+
md := metadata.Pairs(grpctypes.GRPCBlockHeightHeader, "foo")
365+
return metadata.NewOutgoingContext(context.Background(), md)
366+
},
367+
expectError: true,
368+
},
369+
}
370+
371+
for _, tt := range tests {
372+
t.Run(tt.name, func(t *testing.T) {
373+
ctx := tt.setupContext()
374+
height, err := client.GetHeightFromMetadataStrict(ctx)
375+
if tt.expectError {
376+
require.Error(t, err)
377+
return
378+
}
379+
require.NoError(t, err)
380+
require.Equal(t, tt.expectedHeight, height)
381+
})
382+
}
383+
}

0 commit comments

Comments
 (0)