Skip to content

Commit

Permalink
feat: add ParamsQuerier
Browse files Browse the repository at this point in the history
  • Loading branch information
bryanchriswhite committed Dec 12, 2024
1 parent 9535023 commit 49183d7
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 4 deletions.
6 changes: 2 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 @@ -384,10 +385,7 @@ type HistoricalQueryCache[T any] interface {
// 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 {
type ParamsQuerier[P cosmostypes.Msg] 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)
Expand Down
52 changes: 52 additions & 0 deletions pkg/client/query/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package query

import (
sdkerrors "cosmossdk.io/errors"

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

const (
defaultPruneOlderThan = 100
defaultMaxKeys = 1000
)

// paramsQuerierConfig is the configuration for parameter queriers. It is intended
// to be configured via ParamsQuerierOptionFn functions.
type paramsQuerierConfig struct {
// CacheOpts are the options passed to create the params cache
CacheOpts []cache.QueryCacheOptionFn
// ModuleName is used for logging and error context
ModuleName string
// ModuleParamError is the base error type for parameter query errors
ModuleParamError *sdkerrors.Error
}

// ParamsQuerierOptionFn is a function which receives a paramsQuerierConfig for configuration.
type ParamsQuerierOptionFn func(*paramsQuerierConfig)

// DefaultParamsQuerierConfig returns the default configuration for parameter queriers
func DefaultParamsQuerierConfig() *paramsQuerierConfig {
return &paramsQuerierConfig{
CacheOpts: []cache.QueryCacheOptionFn{
cache.WithHistoricalMode(defaultPruneOlderThan),
cache.WithMaxKeys(defaultMaxKeys),
cache.WithEvictionPolicy(cache.FirstInFirstOut),
},
}
}

// WithModuleInfo sets the module name and param error for the querier.
func WithModuleInfo(moduleName string, moduleParamError *sdkerrors.Error) ParamsQuerierOptionFn {
return func(cfg *paramsQuerierConfig) {
cfg.ModuleName = moduleName
cfg.ModuleParamError = moduleParamError
}
}

// WithQueryCacheOptions is used to configure the params HistoricalQueryCache.
func WithQueryCacheOptions(opts ...cache.QueryCacheOptionFn) ParamsQuerierOptionFn {
return func(cfg *paramsQuerierConfig) {
cfg.CacheOpts = append(cfg.CacheOpts, opts...)
}
}
137 changes: 137 additions & 0 deletions pkg/client/query/paramsquerier.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
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 in terms of 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 query client
// constructor and the configuration which results from applying the given options.
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
}

// cachedParamsQuerier provides a generic implementation of cached param querying.
// It handles parameter caching and chain querying in a generic way, where
// P is a pointer type of the parameters, and Q is the interface type of the
// corresponding query client.
type cachedParamsQuerier[P cosmostypes.Msg, Q paramsQuerierIface[P]] struct {
clientConn gogogrpc.ClientConn
queryClient Q
paramsCache client.HistoricalQueryCache[P]
config *paramsQuerierConfig
}

// GetParams returns the latest cached params, if any; otherwise, it queries the
// current on-chain params and caches them.
func (bq *cachedParamsQuerier[P, Q]) GetParams(ctx context.Context) (P, error) {
logger := polylog.Ctx(ctx).With(
"module", bq.config.ModuleName,
"method", "GetParams",
)

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

logger.Debug().Msgf("%s", err)

// 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
}

// GetParamsAtHeight returns parameters as they were as of the given height, **if
// that height is present in the cache**. Otherwise, it queries the current params
// and returns them.
//
// TODO_MAINNET(@bryanchriswhite): Once on-chain historical data is available,
// update this to query for the historical params, rather than returning the
// current params, if the case of a cache miss.
func (bq *cachedParamsQuerier[P, Q]) GetParamsAtHeight(ctx context.Context, height int64) (P, error) {
logger := polylog.Ctx(ctx).With(
"module", 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("params cache hit")
return cached, nil
case !errors.Is(err, cache.ErrCacheMiss):
return cached, err
}

logger.Debug().Msgf("%s", err)

// 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)
}

0 comments on commit 49183d7

Please sign in to comment.