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

[Clients] embed ParamsQuerier into ServiceQueryClient #998

Draft
wants to merge 7 commits into
base: issues/543/params-querier/proof
Choose a base branch
from
Draft
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
29 changes: 25 additions & 4 deletions pkg/client/interface.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:generate mockgen -destination=../../testutil/mockclient/grpc_conn_mock.go -package=mockclient github.com/cosmos/gogoproto/grpc ClientConn
//go:generate mockgen -destination=../../testutil/mockclient/events_query_client_mock.go -package=mockclient . Dialer,Connection,EventsQueryClient
//go:generate mockgen -destination=../../testutil/mockclient/block_client_mock.go -package=mockclient . Block,BlockClient
//go:generate mockgen -destination=../../testutil/mockclient/delegation_client_mock.go -package=mockclient . DelegationClient
Expand Down Expand Up @@ -267,6 +268,8 @@ type AccountQueryClient interface {
// ApplicationQueryClient defines an interface that enables the querying of the
// on-chain application information
type ApplicationQueryClient interface {
ParamsQuerier[*apptypes.Params]

// GetApplication queries the chain for the details of the application provided
GetApplication(ctx context.Context, appAddress string) (apptypes.Application, error)

Expand Down Expand Up @@ -299,8 +302,8 @@ type SessionQueryClient interface {
// SharedQueryClient defines an interface that enables the querying of the
// on-chain shared module params.
type SharedQueryClient interface {
// GetParams queries the chain for the current shared module parameters.
GetParams(ctx context.Context) (*sharedtypes.Params, error)
ParamsQuerier[*sharedtypes.Params]

// GetSessionGracePeriodEndHeight returns the block height at which the grace period
// for the session that includes queryHeight elapses.
// The grace period is the number of blocks after the session ends during which relays
Expand Down Expand Up @@ -333,6 +336,8 @@ type BlockQueryClient interface {
// protobuf message. Since the generated go types don't include interface types, this
// is necessary to prevent dependency cycles.
type ProofParams interface {
cosmostypes.Msg

GetProofRequestProbability() float64
GetProofRequirementThreshold() *cosmostypes.Coin
GetProofMissingPenalty() *cosmostypes.Coin
Expand All @@ -342,13 +347,14 @@ type ProofParams interface {
// ProofQueryClient defines an interface that enables the querying of the
// on-chain proof module params.
type ProofQueryClient interface {
// GetParams queries the chain for the current shared module parameters.
GetParams(ctx context.Context) (ProofParams, error)
ParamsQuerier[ProofParams]
}

// ServiceQueryClient defines an interface that enables the querying of the
// on-chain service information
type ServiceQueryClient interface {
ParamsQuerier[*servicetypes.Params]

// GetService queries the chain for the details of the service provided
GetService(ctx context.Context, serviceId string) (sharedtypes.Service, error)
GetServiceRelayDifficulty(ctx context.Context, serviceId string) (servicetypes.RelayMiningDifficulty, error)
Expand Down Expand Up @@ -377,3 +383,18 @@ type HistoricalQueryCache[T any] interface {
// SetAtHeight adds or updates a value at a specific height
SetAtHeight(key string, value T, height int64) error
}

// ParamsQuerier represents a generic querier for module parameters.
// This interface should be implemented by any module-specific querier
// that needs to access and cache on-chain parameters.
//
// DEV_NOTE: Can't use cosmostypes.Msg instead of any because M
// would be a pointer but Keeper#GetParams() returns a value. 🙄
type ParamsQuerier[P any] interface {
// GetParams queries the chain for the current module parameters, where
// P is the params type of a given module (e.g. sharedtypes.Params).
GetParams(ctx context.Context) (P, error)
// GetParamsAtHeight returns the parameters as they were at the specified
// height, where M is the params type of a given module (e.g. sharedtypes.Params).
GetParamsAtHeight(ctx context.Context, height int64) (P, error)
}
34 changes: 28 additions & 6 deletions pkg/client/query/appquerier.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import (
"context"

"cosmossdk.io/depinject"
grpc "github.com/cosmos/gogoproto/grpc"
gogogrpc "github.com/cosmos/gogoproto/grpc"

"github.com/pokt-network/poktroll/pkg/client"
apptypes "github.com/pokt-network/poktroll/x/application/types"
sharedtypes "github.com/pokt-network/poktroll/x/shared/types"
)

var _ client.ApplicationQueryClient = (*appQuerier)(nil)
Expand All @@ -16,19 +17,40 @@ var _ client.ApplicationQueryClient = (*appQuerier)(nil)
// querying of on-chain application information through a single exposed method
// which returns an apptypes.Application interface
type appQuerier struct {
clientConn grpc.ClientConn
client.ParamsQuerier[*apptypes.Params]

clientConn gogogrpc.ClientConn
applicationQuerier apptypes.QueryClient
}

// NewApplicationQuerier returns a new instance of a client.ApplicationQueryClient
// by injecting the dependecies provided by the depinject.Config
//
// Required dependencies:
// - clientCtx
func NewApplicationQuerier(deps depinject.Config) (client.ApplicationQueryClient, error) {
aq := &appQuerier{}
// - clientCtx (gogogrpc.ClientConn)
func NewApplicationQuerier(
deps depinject.Config,
opts ...ParamsQuerierOptionFn,
) (client.ApplicationQueryClient, error) {
cfg := DefaultParamsQuerierConfig()
for _, opt := range opts {
opt(cfg)
}

paramsQuerier, err := NewCachedParamsQuerier[*apptypes.Params, apptypes.ApplicationQueryClient](
deps, apptypes.NewAppQueryClient,
WithModuleInfo[*sharedtypes.Params](sharedtypes.ModuleName, sharedtypes.ErrSharedParamInvalid),
WithParamsCacheOptions(cfg.CacheOpts...),
)
if err != nil {
return nil, err
}

aq := &appQuerier{
ParamsQuerier: paramsQuerier,
}

if err := depinject.Inject(
if err = depinject.Inject(
deps,
&aq.clientConn,
); err != nil {
Expand Down
57 changes: 57 additions & 0 deletions pkg/client/query/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package query

import (
sdkerrors "cosmossdk.io/errors"
cosmostypes "github.com/cosmos/cosmos-sdk/types"

"github.com/pokt-network/poktroll/pkg/client/query/cache"
)

// ParamsQuerierConfig holds the configuration for parameter queriers
type ParamsQuerierConfig struct {
// CacheOpts are the options passed to create the params cache
CacheOpts []cache.CacheOption
// ModuleName is used for logging and error context
ModuleName string
// ModuleParamError is the base error type for parameter query errors
ModuleParamError *sdkerrors.Error
}

// ParamsQuerierOptionFn defines a function that configures a ParamsQuerierConfig
type ParamsQuerierOptionFn func(*ParamsQuerierConfig)

// DefaultParamsQuerierConfig returns the default configuration for parameter queriers
func DefaultParamsQuerierConfig() *ParamsQuerierConfig {
return &ParamsQuerierConfig{
CacheOpts: []cache.CacheOption{
// TODO_IN_THIS_COMMIT: extract to constants.
cache.WithHistoricalMode(100),
// TODO_IN_THIS_COMMIT: reconcile the fact that MaxKeys doesn't apply to historical mode...
cache.WithMaxKeys(1),
// TODO_IN_THIS_COMMIT: extract to constants.
cache.WithEvictionPolicy(cache.FirstInFirstOut),
},
}
}

// WithModuleInfo sets the module-specific information for the querier
func WithModuleInfo[R cosmostypes.Msg](moduleName string, moduleParamError *sdkerrors.Error) ParamsQuerierOptionFn {
return func(cfg *ParamsQuerierConfig) {
cfg.ModuleName = moduleName
cfg.ModuleParamError = moduleParamError
}
}

// WithParamsCacheOptions adds cache configuration options to the params querier
func WithParamsCacheOptions(opts ...cache.CacheOption) ParamsQuerierOptionFn {
return func(cfg *ParamsQuerierConfig) {
cfg.CacheOpts = append(cfg.CacheOpts, opts...)
}
}

// WithCacheOptions adds cache configuration options to the shared querier
func WithCacheOptions(opts ...cache.CacheOption) ParamsQuerierOptionFn {
return func(cfg *ParamsQuerierConfig) {
cfg.CacheOpts = append(cfg.CacheOpts, opts...)
}
}
131 changes: 131 additions & 0 deletions pkg/client/query/paramsquerier.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package query

import (
"context"
"errors"

"cosmossdk.io/depinject"
cosmostypes "github.com/cosmos/cosmos-sdk/types"
gogogrpc "github.com/cosmos/gogoproto/grpc"

"github.com/pokt-network/poktroll/pkg/client"
"github.com/pokt-network/poktroll/pkg/client/query/cache"
"github.com/pokt-network/poktroll/pkg/polylog"
)

var _ client.ParamsQuerier[cosmostypes.Msg] = (*cachedParamsQuerier[cosmostypes.Msg, paramsQuerierIface[cosmostypes.Msg]])(nil)

// paramsQuerierIface is an interface which generated query clients MUST implement
// to be compatible with the cachedParamsQuerier.
// DEV_NOTE: It is mainly required due to syntactic constraints imposed by the generics
// (i.e. otherwise, P here MUST be a value type, and there's no way to express that Q
// (below) SHOULD be the concrete type of P in NewCachedParamsQuerier).
type paramsQuerierIface[P cosmostypes.Msg] interface {
GetParams(context.Context) (P, error)
}

// NewCachedParamsQuerier creates a new params querier with the given configuration
func NewCachedParamsQuerier[P cosmostypes.Msg, Q paramsQuerierIface[P]](
deps depinject.Config,
queryClientConstructor func(conn gogogrpc.ClientConn) Q,
opts ...ParamsQuerierOptionFn,
) (_ client.ParamsQuerier[P], err error) {
cfg := DefaultParamsQuerierConfig()
for _, opt := range opts {
opt(cfg)
}

querier := &cachedParamsQuerier[P, Q]{
config: cfg,
paramsCache: cache.NewInMemoryCache[P](cfg.CacheOpts...),
}

if err = depinject.Inject(
deps,
&querier.clientConn,
); err != nil {
return nil, err
}

querier.queryClient = queryClientConstructor(querier.clientConn)

return querier, nil
}

// TODO_IN_THIS_COMMIT: update godoc...
// cachedParamsQuerier provides common functionality for all params queriers.
// It handles parameter caching and chain querying in a generic way, where
// R is the type of the parameters and Q is the type of the query client.
type cachedParamsQuerier[P cosmostypes.Msg, Q paramsQuerierIface[P]] struct {
clientConn gogogrpc.ClientConn
queryClient Q
paramsCache client.HistoricalQueryCache[P]
config *ParamsQuerierConfig
}

// TODO_IN_THIS_COMMIT: update godoc...
// GetParams implements the common parameter querying with caching
func (bq *cachedParamsQuerier[P, Q]) GetParams(ctx context.Context) (P, error) {
logger := polylog.Ctx(ctx).With(
"querier", bq.config.ModuleName,
"method", "GetParams",
)

// Check cache first
var paramsZero P
cached, err := bq.paramsCache.Get("params")
switch {
case err == nil:
logger.Debug().Msg("cache hit")
return cached, nil
case !errors.Is(err, cache.ErrCacheMiss):
return paramsZero, err
}

logger.Debug().Msg("cache miss")

// Query chain on cache miss
params, err := bq.queryClient.GetParams(ctx)
if err != nil {
if bq.config.ModuleParamError != nil {
return paramsZero, bq.config.ModuleParamError.Wrap(err.Error())
}
return paramsZero, err
}

// Cache the result before returning
if err = bq.paramsCache.Set("params", params); err != nil {
return paramsZero, err
}

return params, nil
}

// TODO_IN_THIS_COMMIT: update godoc...
// GetParamsAtHeight returns parameters as they were at a specific height
func (bq *cachedParamsQuerier[P, Q]) GetParamsAtHeight(ctx context.Context, height int64) (P, error) {
logger := polylog.Ctx(ctx).With(
"querier", bq.config.ModuleName,
"method", "GetParamsAtHeight",
"height", height,
)

// Try to get from cache at specific height
cached, err := bq.paramsCache.GetAtHeight("params", height)
switch {
case err == nil:
logger.Debug().Msg("cache hit")
return cached, nil
case !errors.Is(err, cache.ErrCacheMiss):
return cached, err
}

logger.Debug().Msg("cache miss")

// TODO_MAINNET(@bryanchriswhite): Implement querying historical params from chain
err = cache.ErrCacheMiss.Wrapf("TODO: on-chain historical data not implemented")
logger.Error().Msgf("%s", err)

// Meanwhile, return current params as fallback. 😬
return bq.GetParams(ctx)
}
51 changes: 34 additions & 17 deletions pkg/client/query/proofquerier.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
package query

import (
"context"

"cosmossdk.io/depinject"
"github.com/cosmos/gogoproto/grpc"

"github.com/pokt-network/poktroll/pkg/client"
prooftypes "github.com/pokt-network/poktroll/x/proof/types"
)

// TODO_IN_THIS_COMMIT: comment explaining why we can't use client.ProofQueryClient;
// tl;dr, it defines ian interface for ProofParams to avoid a dependency cycle
// (i.e. instead of importing prooftypes).
var _ client.ProofQueryClient = (*proofQuerier)(nil)

// proofQuerier is a wrapper around the prooftypes.QueryClient that enables the
// querying of on-chain proof module params.
type proofQuerier struct {
//client.ParamsQuerier[*prooftypes.Params]
client.ParamsQuerier[client.ProofParams]

clientConn grpc.ClientConn
proofQuerier prooftypes.QueryClient
}
Expand All @@ -22,10 +28,33 @@ type proofQuerier struct {
//
// Required dependencies:
// - grpc.ClientConn
func NewProofQuerier(deps depinject.Config) (client.ProofQueryClient, error) {
querier := &proofQuerier{}
func NewProofQuerier(
deps depinject.Config,
paramsQuerierOpts ...ParamsQuerierOptionFn,
// TODO_IN_THIS_COMMIT: comment explaining why we can't use client.ProofQueryClient;
// tl;dr, it defines ian interface for ProofParams to avoid a dependency cycle
// (i.e. instead of importing prooftypes).
// ) (paramsQuerierIface[*prooftypes.Params], error) {
) (paramsQuerierIface[client.ProofParams], error) {
paramsQuerierCfg := DefaultParamsQuerierConfig()
for _, opt := range paramsQuerierOpts {
opt(paramsQuerierCfg)
}

paramsQuerier, err := NewCachedParamsQuerier[client.ProofParams, prooftypes.ProofQueryClient](
deps, prooftypes.NewProofQueryClient,
WithModuleInfo[*prooftypes.Params](prooftypes.ModuleName, prooftypes.ErrProofParamInvalid),
WithParamsCacheOptions(paramsQuerierCfg.CacheOpts...),
)
if err != nil {
return nil, err
}

if err := depinject.Inject(
querier := &proofQuerier{
ParamsQuerier: paramsQuerier,
}

if err = depinject.Inject(
deps,
&querier.clientConn,
); err != nil {
Expand All @@ -36,15 +65,3 @@ func NewProofQuerier(deps depinject.Config) (client.ProofQueryClient, error) {

return querier, nil
}

// GetParams queries the chain for the current proof module parameters.
func (pq *proofQuerier) GetParams(
ctx context.Context,
) (client.ProofParams, error) {
req := &prooftypes.QueryParamsRequest{}
res, err := pq.proofQuerier.Params(ctx, req)
if err != nil {
return nil, err
}
return &res.Params, nil
}
Loading
Loading