Skip to content

Commit 460b947

Browse files
authored
Fix AcceptListGrpcQuerier concurrency issues (#2065)
* Fix AcceptListGrpcQuerier concurrency issues * Fix tests * Fix * Fix
1 parent 96ae094 commit 460b947

File tree

4 files changed

+128
-8
lines changed

4 files changed

+128
-8
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ require (
5151
github.com/distribution/reference v0.5.0
5252
github.com/rs/zerolog v1.33.0
5353
github.com/spf13/viper v1.19.0
54+
golang.org/x/sync v0.10.0
5455
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142
5556
)
5657

@@ -201,7 +202,6 @@ require (
201202
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 // indirect
202203
golang.org/x/net v0.30.0 // indirect
203204
golang.org/x/oauth2 v0.23.0 // indirect
204-
golang.org/x/sync v0.10.0 // indirect
205205
golang.org/x/sys v0.28.0 // indirect
206206
golang.org/x/term v0.27.0 // indirect
207207
golang.org/x/text v0.21.0 // indirect

tests/integration/query_plugin_integration_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -952,8 +952,8 @@ func TestAcceptListStargateQuerier(t *testing.T) {
952952

953953
addrs := app.AddTestAddrsIncremental(wasmApp, ctx, 2, sdkmath.NewInt(1_000_000))
954954
accepted := wasmKeeper.AcceptedQueries{
955-
"/cosmos.auth.v1beta1.Query/Account": &authtypes.QueryAccountResponse{},
956-
"/no/route/to/this": &authtypes.QueryAccountResponse{},
955+
"/cosmos.auth.v1beta1.Query/Account": func() proto.Message { return &authtypes.QueryAccountResponse{} },
956+
"/no/route/to/this": func() proto.Message { return &authtypes.QueryAccountResponse{} },
957957
}
958958

959959
marshal := func(pb proto.Message) []byte {

x/wasm/keeper/query_plugins.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ func RejectGrpcQuerier(ctx sdk.Context, request *wasmvmtypes.GrpcQuery) (proto.M
346346
// WithQueryPlugins(&QueryPlugins{Grpc: AcceptListGrpcQuerier(acceptList, queryRouter, codec)})
347347
func AcceptListGrpcQuerier(acceptList AcceptedQueries, queryRouter GRPCQueryRouter, codec codec.Codec) func(ctx sdk.Context, request *wasmvmtypes.GrpcQuery) (proto.Message, error) {
348348
return func(ctx sdk.Context, request *wasmvmtypes.GrpcQuery) (proto.Message, error) {
349-
protoResponse, accepted := acceptList[request.Path]
349+
protoResponseFn, accepted := acceptList[request.Path]
350350
if !accepted {
351351
return nil, wasmvmtypes.UnsupportedRequest{Kind: fmt.Sprintf("'%s' path is not allowed from the contract", request.Path)}
352352
}
@@ -364,6 +364,7 @@ func AcceptListGrpcQuerier(acceptList AcceptedQueries, queryRouter GRPCQueryRout
364364
return nil, err
365365
}
366366

367+
protoResponse := protoResponseFn()
367368
// decode the query response into the expected protobuf message
368369
err = codec.Unmarshal(res.Value, protoResponse)
369370
if err != nil {
@@ -381,10 +382,15 @@ func RejectStargateQuerier() func(ctx sdk.Context, request *wasmvmtypes.Stargate
381382
}
382383
}
383384

384-
// AcceptedQueries define accepted Stargate or gRPC queries as a map with path as key and response type as value.
385+
// AcceptedQueries defines accepted Stargate or gRPC queries as a map where the key is the query path
386+
// and the value is a function returning a proto.Message.
387+
//
385388
// For example:
386-
// acceptList["/cosmos.auth.v1beta1.Query/Account"]= &authtypes.QueryAccountResponse{}
387-
type AcceptedQueries map[string]proto.Message
389+
//
390+
// acceptList["/cosmos.auth.v1beta1.Query/Account"] = func() proto.Message {
391+
// return &authtypes.QueryAccountResponse{}
392+
// }
393+
type AcceptedQueries map[string]func() proto.Message
388394

389395
// AcceptListStargateQuerier supports a preconfigured set of stargate queries only.
390396
// All arguments must be non nil.
@@ -396,7 +402,7 @@ type AcceptedQueries map[string]proto.Message
396402
// WithQueryPlugins(&QueryPlugins{Stargate: AcceptListStargateQuerier(acceptList, queryRouter, codec)})
397403
func AcceptListStargateQuerier(acceptList AcceptedQueries, queryRouter GRPCQueryRouter, codec codec.Codec) func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) {
398404
return func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) {
399-
protoResponse, accepted := acceptList[request.Path]
405+
protoResponseFn, accepted := acceptList[request.Path]
400406
if !accepted {
401407
return nil, wasmvmtypes.UnsupportedRequest{Kind: fmt.Sprintf("'%s' path is not allowed from the contract", request.Path)}
402408
}
@@ -414,6 +420,7 @@ func AcceptListStargateQuerier(acceptList AcceptedQueries, queryRouter GRPCQuery
414420
return nil, err
415421
}
416422

423+
protoResponse := protoResponseFn()
417424
return ConvertProtoToJSONMarshal(codec, protoResponse, res.Value)
418425
}
419426
}

x/wasm/keeper/query_plugins_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,20 @@ package keeper_test
33
import (
44
"context"
55
"encoding/json"
6+
"fmt"
67
"math"
8+
"sync/atomic"
79
"testing"
810

911
wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types"
12+
abci "github.com/cometbft/cometbft/abci/types"
1013
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
1114
dbm "github.com/cosmos/cosmos-db"
15+
"github.com/cosmos/gogoproto/proto"
1216
channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types"
1317
"github.com/stretchr/testify/assert"
1418
"github.com/stretchr/testify/require"
19+
"golang.org/x/sync/errgroup"
1520

1621
errorsmod "cosmossdk.io/errors"
1722
"cosmossdk.io/log"
@@ -20,6 +25,8 @@ import (
2025
storemetrics "cosmossdk.io/store/metrics"
2126
storetypes "cosmossdk.io/store/types"
2227

28+
"github.com/cosmos/cosmos-sdk/baseapp"
29+
"github.com/cosmos/cosmos-sdk/codec"
2330
sdk "github.com/cosmos/cosmos-sdk/types"
2431
"github.com/cosmos/cosmos-sdk/types/query"
2532
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
@@ -682,3 +689,109 @@ func TestConvertSDKDecCoinToWasmDecCoin(t *testing.T) {
682689
})
683690
}
684691
}
692+
693+
var _ keeper.GRPCQueryRouter = mockedQueryRouter{}
694+
695+
type mockedQueryRouter struct {
696+
codec codec.Codec
697+
}
698+
699+
func (m mockedQueryRouter) Route(_ string) baseapp.GRPCQueryHandler {
700+
return func(ctx sdk.Context, req *abci.RequestQuery) (*abci.ResponseQuery, error) {
701+
balanceReq := &banktypes.QueryBalanceRequest{}
702+
if err := m.codec.Unmarshal(req.Data, balanceReq); err != nil {
703+
return nil, err
704+
}
705+
706+
coin := sdk.NewInt64Coin(balanceReq.Denom, 1)
707+
balanceRes := &banktypes.QueryBalanceResponse{
708+
Balance: &coin,
709+
}
710+
711+
resValue, err := m.codec.Marshal(balanceRes)
712+
if err != nil {
713+
return nil, err
714+
}
715+
716+
return &abci.ResponseQuery{
717+
Value: resValue,
718+
}, nil
719+
}
720+
}
721+
722+
func TestGRPCQuerier(t *testing.T) {
723+
const (
724+
denom1 = "denom1"
725+
denom2 = "denom2"
726+
)
727+
_, keepers := keeper.CreateTestInput(t, false, keeper.AvailableCapabilities)
728+
cdc := keepers.EncodingConfig.Codec
729+
730+
acceptedQueries := keeper.AcceptedQueries{
731+
"/bank.Balance": func() proto.Message { return &banktypes.QueryBalanceResponse{} },
732+
}
733+
734+
router := mockedQueryRouter{
735+
codec: cdc,
736+
}
737+
querier := keeper.AcceptListStargateQuerier(acceptedQueries, router, keepers.EncodingConfig.Codec)
738+
739+
addr := keeper.RandomBech32AccountAddress(t)
740+
741+
eg := errgroup.Group{}
742+
errorsCount := atomic.Uint64{}
743+
for range 50 {
744+
for _, denom := range []string{denom1, denom2} {
745+
denom := denom // copy
746+
eg.Go(func() error {
747+
queryReq := &banktypes.QueryBalanceRequest{
748+
Address: addr,
749+
Denom: denom,
750+
}
751+
grpcData, err := cdc.Marshal(queryReq)
752+
if err != nil {
753+
return err
754+
}
755+
756+
wasmGrpcReq := &wasmvmtypes.StargateQuery{
757+
Data: grpcData,
758+
Path: "/bank.Balance",
759+
}
760+
761+
wasmGrpcRes, err := querier(sdk.Context{}, wasmGrpcReq)
762+
if err != nil {
763+
return err
764+
}
765+
766+
queryRes := &banktypes.QueryBalanceResponse{}
767+
if err := cdc.UnmarshalJSON(wasmGrpcRes, queryRes); err != nil {
768+
return err
769+
}
770+
771+
expectedCoin := sdk.NewInt64Coin(denom, 1)
772+
if queryRes.Balance == nil {
773+
fmt.Printf(
774+
"Error: expected %s, got nil\n",
775+
expectedCoin.String(),
776+
)
777+
errorsCount.Add(1)
778+
return nil
779+
}
780+
if queryRes.Balance.String() != expectedCoin.String() {
781+
fmt.Printf(
782+
"Error: expected %s, got %s\n",
783+
expectedCoin.String(),
784+
queryRes.Balance.String(),
785+
)
786+
errorsCount.Add(1)
787+
return nil
788+
}
789+
790+
return nil
791+
})
792+
}
793+
}
794+
795+
require.NoError(t, eg.Wait())
796+
require.Zero(t, errorsCount.Load())
797+
}

0 commit comments

Comments
 (0)