Skip to content

Commit

Permalink
Implement grpc query
Browse files Browse the repository at this point in the history
  • Loading branch information
chipshort committed Feb 21, 2024
1 parent d94360c commit 9cc1a5a
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 5 deletions.
53 changes: 53 additions & 0 deletions x/wasm/keeper/query_plugin_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package keeper
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"strings"
"testing"
Expand Down Expand Up @@ -116,6 +117,58 @@ func TestReflectStargateQuery(t *testing.T) {
assert.Equal(t, simpleBalance.Amount[0].Denom, expectedBalance[0].Denom)
}

func TestReflectGrpcQuery(t *testing.T) {
queryPlugins := (*reflectPlugins()).Merge(&QueryPlugins{
Grpc: func(ctx sdk.Context, request *wasmvmtypes.GrpcQuery) (proto.Message, error) {
if request.Path == "cosmos.bank.v1beta1.Query/AllBalances" {
return &banktypes.QueryAllBalancesResponse{
Balances: sdk.NewCoins(),
}, nil
}
return nil, errors.New("unsupported request")
},
})
cdc := MakeEncodingConfig(t).Codec
ctx, keepers := CreateTestInput(t, false, ReflectCapabilities, WithMessageEncoders(reflectEncoders(cdc)), WithQueryPlugins(&queryPlugins))
keeper := keepers.WasmKeeper

creator := RandomAccountAddress(t)

// upload code
codeID, _, err := keepers.ContractKeeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil)
require.NoError(t, err)
require.Equal(t, uint64(1), codeID)

// creator instantiates a contract
contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "reflect contract 1", sdk.NewCoins())
require.NoError(t, err)
require.NotEmpty(t, contractAddr)

// now grpc query for the bank balance
cosmosBankQuery := banktypes.NewQueryAllBalancesRequest(creator, nil, false)
cosmosBankQueryBz, err := proto.Marshal(cosmosBankQuery)
require.NoError(t, err)
reflectQuery := wasmvmtypes.QueryRequest{
Grpc: &wasmvmtypes.GrpcQuery{
Path: "cosmos.bank.v1beta1.Query/AllBalances",
Data: cosmosBankQueryBz,
},
}
reflectQueryBz, err := json.Marshal(testdata.ReflectQueryMsg{
Chain: &testdata.ChainQuery{Request: &reflectQuery},
})
require.NoError(t, err)
// query the reflect contract
reflectRespBz, err := keeper.QuerySmart(ctx, contractAddr, reflectQueryBz)
require.NoError(t, err)
var reflectResp testdata.ChainResponse
mustUnmarshal(t, reflectRespBz, &reflectResp)
// now unmarshal the protobuf response
var grpcBalance banktypes.QueryAllBalancesResponse
err = proto.Unmarshal(reflectResp.Data, &grpcBalance)
require.NoError(t, err)
}

func TestReflectTotalSupplyQuery(t *testing.T) {
cdc := MakeEncodingConfig(t).Codec
ctx, keepers := CreateTestInput(t, false, ReflectCapabilities, WithMessageEncoders(reflectEncoders(cdc)), WithQueryPlugins(reflectPlugins()))
Expand Down
65 changes: 61 additions & 4 deletions x/wasm/keeper/query_plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ type QueryPlugins struct {
IBC func(ctx sdk.Context, caller sdk.AccAddress, request *wasmvmtypes.IBCQuery) ([]byte, error)
Staking func(ctx sdk.Context, request *wasmvmtypes.StakingQuery) ([]byte, error)
Stargate func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error)
Grpc func(ctx sdk.Context, request *wasmvmtypes.GrpcQuery) (proto.Message, error)
Wasm func(ctx sdk.Context, request *wasmvmtypes.WasmQuery) ([]byte, error)
Distribution func(ctx sdk.Context, request *wasmvmtypes.DistributionQuery) ([]byte, error)
}
Expand All @@ -113,12 +114,15 @@ func DefaultQueryPlugins(
channelKeeper types.ChannelKeeper,
wasm wasmQueryKeeper,
) QueryPlugins {
// By default, we reject all stargate and gRPC queries.
// The chain needs to provide a querier plugin that only allows deterministic queries.
return QueryPlugins{
Bank: BankQuerier(bank),
Custom: NoCustomQuerier,
IBC: IBCQuerier(wasm, channelKeeper),
Staking: StakingQuerier(staking, distKeeper),
Stargate: RejectStargateQuerier(),
Grpc: RejectGrpcQuerier,
Wasm: WasmQuerier(wasm),
Distribution: DistributionQuerier(distKeeper),
}
Expand All @@ -144,6 +148,9 @@ func (e QueryPlugins) Merge(o *QueryPlugins) QueryPlugins {
if o.Stargate != nil {
e.Stargate = o.Stargate
}
if o.Grpc != nil {
e.Grpc = o.Grpc
}
if o.Wasm != nil {
e.Wasm = o.Wasm
}
Expand All @@ -167,6 +174,14 @@ func (e QueryPlugins) HandleQuery(ctx sdk.Context, caller sdk.AccAddress, req wa
return e.Staking(ctx, req.Staking)
case req.Stargate != nil:
return e.Stargate(ctx, req.Stargate)
case req.Grpc != nil:
resp, err := e.Grpc(ctx, req.Grpc)
if err != nil {
return nil, err
}
// Marshalling the response here instead of inside the query
// plugin makes sure that the response is always protobuf encoded.
return proto.Marshal(resp)
case req.Wasm != nil:
return e.Wasm(ctx, req.Wasm)
case req.Distribution != nil:
Expand Down Expand Up @@ -317,27 +332,69 @@ func IBCQuerier(wasm contractMetaDataSource, channelKeeper types.ChannelKeeper)
}
}

func RejectGrpcQuerier(ctx sdk.Context, request *wasmvmtypes.GrpcQuery) (proto.Message, error) {
return nil, wasmvmtypes.UnsupportedRequest{Kind: "gRPC queries are disabled"}
}

// AcceptListGrpcQuerier supports a preconfigured set of gRPC queries only.
// All arguments must be non nil.
//
// Warning: Chains need to test and maintain their accept list carefully.
// There were critical consensus breaking issues in the past with non-deterministic behavior in the SDK.
//
// These queries can be set via WithQueryPlugins option in the wasm keeper constructor:
// WithQueryPlugins(&QueryPlugins{Grpc: AcceptListGrpcQuerier(acceptList, queryRouter, codec)})
func AcceptListGrpcQuerier(acceptList AcceptedQueries, queryRouter GRPCQueryRouter, codec codec.Codec) func(ctx sdk.Context, request *wasmvmtypes.GrpcQuery) (proto.Message, error) {
return func(ctx sdk.Context, request *wasmvmtypes.GrpcQuery) (proto.Message, error) {
protoResponse, accepted := acceptList[request.Path]
if !accepted {
return nil, wasmvmtypes.UnsupportedRequest{Kind: fmt.Sprintf("'%s' path is not allowed from the contract", request.Path)}
}

handler := queryRouter.Route(request.Path)
if handler == nil {
return nil, wasmvmtypes.UnsupportedRequest{Kind: fmt.Sprintf("No route to query '%s'", request.Path)}
}

res, err := handler(ctx, &abci.RequestQuery{
Data: request.Data,
Path: request.Path,
})
if err != nil {
return nil, err
}

// decode the query response into the expected protobuf message
err = codec.Unmarshal(res.Value, protoResponse)
if err != nil {
return nil, err
}

return protoResponse, nil
}
}

// RejectStargateQuerier rejects all stargate queries
func RejectStargateQuerier() func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) {
return func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) {
return nil, wasmvmtypes.UnsupportedRequest{Kind: "Stargate queries are disabled"}
}
}

// AcceptedStargateQueries define accepted Stargate queries as a map with path as key and response type as value.
// AcceptedQueries define accepted Stargate or gRPC queries as a map with path as key and response type as value.
// For example:
// acceptList["/cosmos.auth.v1beta1.Query/Account"]= &authtypes.QueryAccountResponse{}
type AcceptedStargateQueries map[string]proto.Message
type AcceptedQueries map[string]proto.Message

// AcceptListStargateQuerier supports a preconfigured set of stargate queries only.
// All arguments must be non nil.
//
// Warning: Chains need to test and maintain their accept list carefully.
// There were critical consensus breaking issues in the past with non-deterministic behavior in the SDK.
//
// This queries can be set via WithQueryPlugins option in the wasm keeper constructor:
// These queries can be set via WithQueryPlugins option in the wasm keeper constructor:
// WithQueryPlugins(&QueryPlugins{Stargate: AcceptListStargateQuerier(acceptList, queryRouter, codec)})
func AcceptListStargateQuerier(acceptList AcceptedStargateQueries, queryRouter GRPCQueryRouter, codec codec.Codec) func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) {
func AcceptListStargateQuerier(acceptList AcceptedQueries, queryRouter GRPCQueryRouter, codec codec.Codec) func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) {
return func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) {
protoResponse, accepted := acceptList[request.Path]
if !accepted {
Expand Down
2 changes: 1 addition & 1 deletion x/wasm/keeper/query_plugins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,7 @@ func TestAcceptListStargateQuerier(t *testing.T) {
require.NoError(t, err)

addrs := app.AddTestAddrsIncremental(wasmApp, ctx, 2, sdkmath.NewInt(1_000_000))
accepted := keeper.AcceptedStargateQueries{
accepted := keeper.AcceptedQueries{
"/cosmos.auth.v1beta1.Query/Account": &authtypes.QueryAccountResponse{},
"/no/route/to/this": &authtypes.QueryAccountResponse{},
}
Expand Down

0 comments on commit 9cc1a5a

Please sign in to comment.