fix(server/v2/comebft): wire missing services + fix simulation (#21964)

This commit is contained in:
Julien Robert
2024-11-19 13:49:33 +00:00
committed by GitHub
parent f96b3b00e3
commit d697a3de0f
25 changed files with 2222 additions and 203 deletions
+58 -10
View File
@@ -11,6 +11,9 @@ import (
abci "github.com/cometbft/cometbft/abci/types"
abciproto "github.com/cometbft/cometbft/api/cometbft/abci/v1"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
gogoproto "github.com/cosmos/gogoproto/proto"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
@@ -27,7 +30,6 @@ import (
"cosmossdk.io/log"
"cosmossdk.io/schema/appdata"
"cosmossdk.io/server/v2/appmanager"
"cosmossdk.io/server/v2/cometbft/client/grpc/cmtservice"
"cosmossdk.io/server/v2/cometbft/handlers"
"cosmossdk.io/server/v2/cometbft/mempool"
"cosmossdk.io/server/v2/cometbft/types"
@@ -37,13 +39,18 @@ import (
consensustypes "cosmossdk.io/x/consensus/types"
)
const (
QueryPathApp = "app"
QueryPathP2P = "p2p"
QueryPathStore = "store"
)
var _ abci.Application = (*Consensus[transaction.Tx])(nil)
type Consensus[T transaction.Tx] struct {
logger log.Logger
appName, version string
app appmanager.AppManager[T]
appCloser func() error
txCodec transaction.Codec[T]
store types.Store
streaming streaming.Manager
@@ -78,7 +85,6 @@ func NewConsensus[T transaction.Tx](
logger log.Logger,
appName string,
app appmanager.AppManager[T],
appCloser func() error,
mp mempool.Mempool[T],
indexedEvents map[string]struct{},
queryHandlersMap map[string]appmodulev2.Handler,
@@ -91,7 +97,6 @@ func NewConsensus[T transaction.Tx](
appName: appName,
version: getCometBFTServerVersion(),
app: app,
appCloser: appCloser,
cfg: cfg,
store: store,
logger: logger,
@@ -221,17 +226,17 @@ func (c *Consensus[T]) Query(ctx context.Context, req *abciproto.QueryRequest) (
}
switch path[0] {
case cmtservice.QueryPathApp:
case QueryPathApp:
resp, err = c.handlerQueryApp(ctx, path, req)
case cmtservice.QueryPathStore:
resp, err = c.handleQueryStore(path, c.store, req)
case QueryPathStore:
resp, err = c.handleQueryStore(path, req)
case cmtservice.QueryPathP2P:
case QueryPathP2P:
resp, err = c.handleQueryP2P(path)
default:
resp = QueryResult(errorsmod.Wrap(cometerrors.ErrUnknownRequest, "unknown query path"), c.cfg.AppTomlConfig.Trace)
resp = QueryResult(errorsmod.Wrapf(cometerrors.ErrUnknownRequest, "unknown query path %s", req.Path), c.cfg.AppTomlConfig.Trace)
}
if err != nil {
@@ -267,6 +272,50 @@ func (c *Consensus[T]) maybeRunGRPCQuery(ctx context.Context, req *abci.QueryReq
handlerFullName = string(md.Input().FullName())
}
// special case for simulation as it is an external gRPC registered on the grpc server component
// and not on the app itself, so it won't pass the router afterwards.
if req.Path == "/cosmos.tx.v1beta1.Service/Simulate" {
simulateRequest := &txtypes.SimulateRequest{}
err = gogoproto.Unmarshal(req.Data, simulateRequest)
if err != nil {
return nil, true, fmt.Errorf("unable to decode gRPC request with path %s from ABCI.Query: %w", req.Path, err)
}
tx, err := c.txCodec.Decode(simulateRequest.TxBytes)
if err != nil {
return nil, true, fmt.Errorf("failed to decode tx: %w", err)
}
txResult, _, err := c.app.Simulate(ctx, tx)
if err != nil {
return nil, true, fmt.Errorf("%v with gas used: '%d'", err, txResult.GasUsed)
}
msgResponses := make([]*codectypes.Any, 0, len(txResult.Resp))
// pack the messages into Any
for _, msg := range txResult.Resp {
anyMsg, err := codectypes.NewAnyWithValue(msg)
if err != nil {
return nil, true, fmt.Errorf("failed to pack message response: %w", err)
}
msgResponses = append(msgResponses, anyMsg)
}
resp := &txtypes.SimulateResponse{
GasInfo: &sdk.GasInfo{
GasUsed: txResult.GasUsed,
GasWanted: txResult.GasWanted,
},
Result: &sdk.Result{
MsgResponses: msgResponses,
},
}
res, err := queryResponse(resp, req.Height)
return res, true, err
}
handler, found := c.queryHandlersMap[handlerFullName]
if !found {
return nil, true, fmt.Errorf("no query handler found for %s", req.Path)
@@ -281,7 +330,6 @@ func (c *Consensus[T]) maybeRunGRPCQuery(ctx context.Context, req *abci.QueryReq
resp := QueryResult(err, c.cfg.AppTomlConfig.Trace)
resp.Height = req.Height
return resp, true, err
}
resp, err = queryResponse(res, req.Height)
+1 -1
View File
@@ -699,7 +699,7 @@ func setUpConsensus(t *testing.T, gasLimit uint64, mempool mempool.Mempool[mock.
nil,
)
return NewConsensus[mock.Tx](log.NewNopLogger(), "testing-app", am, func() error { return nil },
return NewConsensus[mock.Tx](log.NewNopLogger(), "testing-app", am,
mempool, map[string]struct{}{}, nil, mockStore,
Config{AppTomlConfig: DefaultAppTomlConfig()}, mock.TxCodec{}, "test")
}
@@ -1,71 +0,0 @@
package cmtservice
import (
autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
cmtv1beta1 "cosmossdk.io/api/cosmos/base/tendermint/v1beta1"
)
var CometBFTAutoCLIDescriptor = &autocliv1.ServiceCommandDescriptor{
Service: cmtv1beta1.Service_ServiceDesc.ServiceName,
RpcCommandOptions: []*autocliv1.RpcCommandOptions{
{
RpcMethod: "GetNodeInfo",
Use: "node-info",
Short: "Query the current node info",
},
{
RpcMethod: "GetSyncing",
Use: "syncing",
Short: "Query node syncing status",
},
{
RpcMethod: "GetLatestBlock",
Use: "block-latest",
Short: "Query for the latest committed block",
},
{
RpcMethod: "GetBlockByHeight",
Use: "block-by-height <height>",
Short: "Query for a committed block by height",
Long: "Query for a specific committed block using the CometBFT RPC `block_by_height` method",
PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "height"}},
},
{
RpcMethod: "GetLatestValidatorSet",
Use: "validator-set",
Alias: []string{"validator-set-latest", "comet-validator-set", "cometbft-validator-set", "tendermint-validator-set"},
Short: "Query for the latest validator set",
},
{
RpcMethod: "GetValidatorSetByHeight",
Use: "validator-set-by-height <height>",
Short: "Query for a validator set by height",
PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "height"}},
},
{
RpcMethod: "ABCIQuery",
Skip: true,
},
},
}
// NewCometBFTCommands is a fake `appmodule.Module` to be considered as a module
// and be added in AutoCLI.
func NewCometBFTCommands() *cometModule {
return &cometModule{}
}
type cometModule struct{}
func (m cometModule) IsOnePerModuleType() {}
func (m cometModule) IsAppModule() {}
func (m cometModule) Name() string {
return "comet"
}
func (m cometModule) AutoCLIOptions() *autocliv1.ModuleOptions {
return &autocliv1.ModuleOptions{
Query: CometBFTAutoCLIDescriptor,
}
}
@@ -1,56 +0,0 @@
package cmtservice
import (
"context"
"strings"
abci "github.com/cometbft/cometbft/api/cometbft/abci/v1"
gogogrpc "github.com/cosmos/gogoproto/grpc"
gogoprotoany "github.com/cosmos/gogoproto/types/any"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"cosmossdk.io/core/address"
"cosmossdk.io/server/v2/cometbft/client/rpc"
cmtservice "github.com/cosmos/cosmos-sdk/client/grpc/cmtservice"
)
var _ gogoprotoany.UnpackInterfacesMessage = &cmtservice.GetLatestValidatorSetResponse{}
const (
QueryPathApp = "app"
QueryPathP2P = "p2p"
QueryPathStore = "store"
)
type abciQueryFn = func(context.Context, *abci.QueryRequest) (*abci.QueryResponse, error)
// RegisterTendermintService registers the CometBFT queries on the gRPC router.
func RegisterTendermintService(
client rpc.CometRPC,
server gogogrpc.Server,
queryFn abciQueryFn,
consensusCodec address.Codec,
) {
cmtservice.RegisterServiceServer(server, cmtservice.NewQueryServer(client, queryFn, consensusCodec))
}
// RegisterGRPCGatewayRoutes mounts the CometBFT service's GRPC-gateway routes on the
// given Mux.
func RegisterGRPCGatewayRoutes(clientConn gogogrpc.ClientConn, mux *runtime.ServeMux) {
_ = cmtservice.RegisterServiceHandlerClient(context.Background(), mux, cmtservice.NewServiceClient(clientConn))
}
// SplitABCIQueryPath splits a string path using the delimiter '/'.
//
// e.g. "this/is/funny" becomes []string{"this", "is", "funny"}
func SplitABCIQueryPath(requestPath string) (path []string) {
path = strings.Split(requestPath, "/")
// first element is empty string
if len(path) > 0 && path[0] == "" {
path = path[1:]
}
return path
}
+11 -5
View File
@@ -50,9 +50,15 @@ func QueryBlocks(ctx context.Context, rpcClient CometRPC, page, limit int, query
return nil, err
}
result := NewSearchBlocksResult(int64(resBlocks.TotalCount), int64(len(blocks)), int64(page), int64(limit), blocks)
return result, nil
totalPages := calcTotalPages(int64(resBlocks.TotalCount), int64(limit))
return &sdk.SearchBlocksResult{
TotalCount: int64(resBlocks.TotalCount),
Count: int64(len(blocks)),
PageNumber: int64(page),
PageTotal: totalPages,
Limit: int64(limit),
Blocks: blocks,
}, nil
}
// GetBlockByHeight gets block by height
@@ -65,7 +71,7 @@ func GetBlockByHeight(ctx context.Context, rpcClient CometRPC, height *int64) (*
return nil, err
}
out, err := NewResponseResultBlock(resBlock)
out, err := responseResultBlock(resBlock)
if err != nil {
return nil, err
}
@@ -90,7 +96,7 @@ func GetBlockByHash(ctx context.Context, rpcClient CometRPC, hashHexString strin
} else if resBlock.Block == nil {
return nil, fmt.Errorf("block not found with hash: %s", hashHexString)
}
out, err := NewResponseResultBlock(resBlock)
out, err := responseResultBlock(resBlock)
if err != nil {
return nil, err
}
+3 -17
View File
@@ -6,8 +6,6 @@ import (
cmttypes "github.com/cometbft/cometbft/api/cometbft/types/v1"
coretypes "github.com/cometbft/cometbft/rpc/core/types"
gogoproto "github.com/cosmos/gogoproto/proto"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// formatBlockResults parses the indexed blocks into a slice of BlockResponse objects.
@@ -17,7 +15,7 @@ func formatBlockResults(resBlocks []*coretypes.ResultBlock) ([]*cmttypes.Block,
out = make([]*cmttypes.Block, len(resBlocks))
)
for i := range resBlocks {
out[i], err = NewResponseResultBlock(resBlocks[i])
out[i], err = responseResultBlock(resBlocks[i])
if err != nil {
return nil, fmt.Errorf("unable to create response block from comet result block: %v: %w", resBlocks[i], err)
}
@@ -29,20 +27,8 @@ func formatBlockResults(resBlocks []*coretypes.ResultBlock) ([]*cmttypes.Block,
return out, nil
}
func NewSearchBlocksResult(totalCount, count, page, limit int64, blocks []*cmttypes.Block) *sdk.SearchBlocksResult {
totalPages := calcTotalPages(totalCount, limit)
return &sdk.SearchBlocksResult{
TotalCount: totalCount,
Count: count,
PageNumber: page,
PageTotal: totalPages,
Limit: limit,
Blocks: blocks,
}
}
// NewResponseResultBlock returns a BlockResponse given a ResultBlock from CometBFT
func NewResponseResultBlock(res *coretypes.ResultBlock) (*cmttypes.Block, error) {
// responseResultBlock returns a BlockResponse given a ResultBlock from CometBFT
func responseResultBlock(res *coretypes.ResultBlock) (*cmttypes.Block, error) {
blkProto, err := res.Block.ToProto()
if err != nil {
return nil, err
+2 -2
View File
@@ -33,10 +33,10 @@ require (
github.com/cometbft/cometbft/api v1.0.0-rc.1
github.com/cosmos/cosmos-sdk v0.53.0
github.com/cosmos/gogoproto v1.7.0
github.com/grpc-ecosystem/grpc-gateway v1.16.0
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.9.0
google.golang.org/grpc v1.68.0
google.golang.org/protobuf v1.35.2
sigs.k8s.io/yaml v1.4.0
)
@@ -111,6 +111,7 @@ require (
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
@@ -186,7 +187,6 @@ require (
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect
google.golang.org/grpc v1.68.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gotest.tools/v3 v3.5.1 // indirect
+220
View File
@@ -0,0 +1,220 @@
package cometbft
import (
"context"
v1 "github.com/cometbft/cometbft/api/cometbft/abci/v1"
"github.com/cosmos/gogoproto/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
cmtv1beta1 "cosmossdk.io/api/cosmos/base/tendermint/v1beta1"
"cosmossdk.io/core/server"
"cosmossdk.io/core/transaction"
errorsmod "cosmossdk.io/errors/v2"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/grpc/cmtservice"
nodeservice "github.com/cosmos/cosmos-sdk/client/grpc/node"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
)
// GRPCServiceRegistrar returns a function that registers the CometBFT gRPC service
// Those services are defined for backward compatibility.
// Eventually, they will be removed in favor of the new gRPC services.
func (c *Consensus[T]) GRPCServiceRegistrar(
clientCtx client.Context,
cfg server.ConfigMap,
) func(srv *grpc.Server) error {
return func(srv *grpc.Server) error {
cmtservice.RegisterServiceServer(srv, cmtservice.NewQueryServer(clientCtx.Client, c.Query, clientCtx.ConsensusAddressCodec))
txtypes.RegisterServiceServer(srv, txServer[T]{clientCtx, c})
nodeservice.RegisterServiceServer(srv, nodeServer[T]{cfg, c})
return nil
}
}
// CometBFTAutoCLIDescriptor is the auto-generated CLI descriptor for the CometBFT service
var CometBFTAutoCLIDescriptor = &autocliv1.ServiceCommandDescriptor{
Service: cmtv1beta1.Service_ServiceDesc.ServiceName,
RpcCommandOptions: []*autocliv1.RpcCommandOptions{
{
RpcMethod: "GetNodeInfo",
Use: "node-info",
Short: "Query the current node info",
},
{
RpcMethod: "GetSyncing",
Use: "syncing",
Short: "Query node syncing status",
},
{
RpcMethod: "GetLatestBlock",
Use: "block-latest",
Short: "Query for the latest committed block",
},
{
RpcMethod: "GetBlockByHeight",
Use: "block-by-height <height>",
Short: "Query for a committed block by height",
Long: "Query for a specific committed block using the CometBFT RPC `block_by_height` method",
PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "height"}},
},
{
RpcMethod: "GetLatestValidatorSet",
Use: "validator-set",
Alias: []string{"validator-set-latest", "comet-validator-set", "cometbft-validator-set", "tendermint-validator-set"},
Short: "Query for the latest validator set",
},
{
RpcMethod: "GetValidatorSetByHeight",
Use: "validator-set-by-height <height>",
Short: "Query for a validator set by height",
PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "height"}},
},
{
RpcMethod: "ABCIQuery",
Skip: true,
},
},
}
type txServer[T transaction.Tx] struct {
clientCtx client.Context
consensus *Consensus[T]
}
// BroadcastTx implements tx.ServiceServer.
func (t txServer[T]) BroadcastTx(ctx context.Context, req *txtypes.BroadcastTxRequest) (*txtypes.BroadcastTxResponse, error) {
return client.TxServiceBroadcast(ctx, t.clientCtx, req)
}
// GetBlockWithTxs implements tx.ServiceServer.
func (t txServer[T]) GetBlockWithTxs(context.Context, *txtypes.GetBlockWithTxsRequest) (*txtypes.GetBlockWithTxsResponse, error) {
return nil, status.Error(codes.Unimplemented, "not implemented")
}
// GetTx implements tx.ServiceServer.
func (t txServer[T]) GetTx(context.Context, *txtypes.GetTxRequest) (*txtypes.GetTxResponse, error) {
return nil, status.Error(codes.Unimplemented, "not implemented")
}
// GetTxsEvent implements tx.ServiceServer.
func (t txServer[T]) GetTxsEvent(context.Context, *txtypes.GetTxsEventRequest) (*txtypes.GetTxsEventResponse, error) {
return nil, status.Error(codes.Unimplemented, "not implemented")
}
// Simulate implements tx.ServiceServer.
func (t txServer[T]) Simulate(ctx context.Context, req *txtypes.SimulateRequest) (*txtypes.SimulateResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid empty tx")
}
txBytes := req.TxBytes
if txBytes == nil && req.Tx != nil {
// This block is for backwards-compatibility.
// We used to support passing a `Tx` in req. But if we do that, sig
// verification might not pass, because the .Marshal() below might not
// be the same marshaling done by the client.
var err error
txBytes, err = proto.Marshal(req.Tx)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid tx; %v", err)
}
}
if txBytes == nil {
return nil, status.Errorf(codes.InvalidArgument, "empty txBytes is not allowed")
}
tx, err := t.consensus.txCodec.Decode(txBytes)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to decode tx")
}
txResult, _, err := t.consensus.app.Simulate(ctx, tx)
if err != nil {
return nil, status.Errorf(codes.Unknown, "%v with gas used: '%d'", err, txResult.GasUsed)
}
msgResponses := make([]*codectypes.Any, 0, len(txResult.Resp))
// pack the messages into Any
for _, msg := range txResult.Resp {
anyMsg, err := codectypes.NewAnyWithValue(msg)
if err != nil {
return nil, status.Errorf(codes.Unknown, "failed to pack message response: %v", err)
}
msgResponses = append(msgResponses, anyMsg)
}
return &txtypes.SimulateResponse{
GasInfo: &sdk.GasInfo{
GasUsed: txResult.GasUsed,
GasWanted: txResult.GasWanted,
},
Result: &sdk.Result{
MsgResponses: msgResponses,
},
}, nil
}
// TxDecode implements tx.ServiceServer.
func (t txServer[T]) TxDecode(context.Context, *txtypes.TxDecodeRequest) (*txtypes.TxDecodeResponse, error) {
return nil, status.Error(codes.Unimplemented, "not implemented")
}
// TxDecodeAmino implements tx.ServiceServer.
func (t txServer[T]) TxDecodeAmino(context.Context, *txtypes.TxDecodeAminoRequest) (*txtypes.TxDecodeAminoResponse, error) {
return nil, status.Error(codes.Unimplemented, "not implemented")
}
// TxEncode implements tx.ServiceServer.
func (t txServer[T]) TxEncode(context.Context, *txtypes.TxEncodeRequest) (*txtypes.TxEncodeResponse, error) {
return nil, status.Error(codes.Unimplemented, "not implemented")
}
// TxEncodeAmino implements tx.ServiceServer.
func (t txServer[T]) TxEncodeAmino(context.Context, *txtypes.TxEncodeAminoRequest) (*txtypes.TxEncodeAminoResponse, error) {
return nil, status.Error(codes.Unimplemented, "not implemented")
}
var _ txtypes.ServiceServer = txServer[transaction.Tx]{}
type nodeServer[T transaction.Tx] struct {
cfg server.ConfigMap
consensus *Consensus[T]
}
func (s nodeServer[T]) Config(ctx context.Context, _ *nodeservice.ConfigRequest) (*nodeservice.ConfigResponse, error) {
minGasPricesStr := ""
minGasPrices, ok := s.cfg["server"].(map[string]interface{})["minimum-gas-prices"]
if ok {
minGasPricesStr = minGasPrices.(string)
}
return &nodeservice.ConfigResponse{
MinimumGasPrice: minGasPricesStr,
PruningKeepRecent: "ambiguous in v2",
PruningInterval: "ambiguous in v2",
HaltHeight: s.consensus.cfg.AppTomlConfig.HaltHeight,
}, nil
}
func (s nodeServer[T]) Status(ctx context.Context, _ *nodeservice.StatusRequest) (*nodeservice.StatusResponse, error) {
nodeInfo, err := s.consensus.Info(ctx, &v1.InfoRequest{})
if err != nil {
return nil, err
}
return &nodeservice.StatusResponse{
Height: uint64(nodeInfo.LastBlockHeight),
Timestamp: nil,
AppHash: nil,
ValidatorHash: nodeInfo.LastBlockAppHash,
}, nil
}
+1 -2
View File
@@ -8,7 +8,6 @@ import (
crypto "github.com/cometbft/cometbft/api/cometbft/crypto/v1"
errorsmod "cosmossdk.io/errors/v2"
"cosmossdk.io/server/v2/cometbft/types"
cometerrors "cosmossdk.io/server/v2/cometbft/types/errors"
)
@@ -84,7 +83,7 @@ func (c *Consensus[T]) handlerQueryApp(ctx context.Context, path []string, req *
return nil, errorsmod.Wrapf(cometerrors.ErrUnknownRequest, "unknown query: %s", path)
}
func (c *Consensus[T]) handleQueryStore(path []string, _ types.Store, req *abci.QueryRequest) (*abci.QueryResponse, error) {
func (c *Consensus[T]) handleQueryStore(path []string, req *abci.QueryRequest) (*abci.QueryResponse, error) {
req.Path = "/" + strings.Join(path[1:], "/")
if req.Height <= 1 && req.Prove {
return nil, errorsmod.Wrap(
-1
View File
@@ -116,7 +116,6 @@ func New[T transaction.Tx](
logger,
appName,
appManager,
nil,
srv.serverOptions.Mempool(cfg),
indexEvents,
queryHandlers,