diff --git a/pkg/client/interface.go b/pkg/client/interface.go index 079083240..93956d6a7 100644 --- a/pkg/client/interface.go +++ b/pkg/client/interface.go @@ -300,8 +300,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 @@ -320,7 +320,7 @@ type SharedQueryClient interface { // for the session that includes queryHeight can be committed for a given supplier. GetEarliestSupplierProofCommitHeight(ctx context.Context, queryHeight int64, supplierOperatorAddr string) (int64, error) // GetComputeUnitsToTokensMultiplier returns the multiplier used to convert compute units to tokens. - GetComputeUnitsToTokensMultiplier(ctx context.Context) (uint64, error) + GetComputeUnitsToTokensMultiplier(ctx context.Context, queryHeight int64) (uint64, error) } // BlockQueryClient defines an interface that enables the querying of diff --git a/pkg/client/query/sharedquerier.go b/pkg/client/query/sharedquerier.go index 06e0ed90a..bd6518e20 100644 --- a/pkg/client/query/sharedquerier.go +++ b/pkg/client/query/sharedquerier.go @@ -13,60 +13,60 @@ import ( var _ client.SharedQueryClient = (*sharedQuerier)(nil) // sharedQuerier is a wrapper around the sharedtypes.QueryClient that enables the -// querying of on-chain shared information through a single exposed method -// which returns an sharedtypes.Session struct +// querying of on-chain shared information. type sharedQuerier struct { + client.ParamsQuerier[*sharedtypes.Params] + clientConn grpc.ClientConn sharedQuerier sharedtypes.QueryClient blockQuerier client.BlockQueryClient } // NewSharedQuerier returns a new instance of a client.SharedQueryClient by -// injecting the dependecies provided by the depinject.Config. +// injecting the dependencies provided by the depinject.Config. // // Required dependencies: // - clientCtx (grpc.ClientConn) // - client.BlockQueryClient -func NewSharedQuerier(deps depinject.Config) (client.SharedQueryClient, error) { - querier := &sharedQuerier{} +func NewSharedQuerier( + deps depinject.Config, + paramsQuerierOpts ...ParamsQuerierOptionFn, +) (client.SharedQueryClient, error) { + paramsQuerierCfg := DefaultParamsQuerierConfig() + for _, opt := range paramsQuerierOpts { + opt(paramsQuerierCfg) + } + + paramsQuerier, err := NewCachedParamsQuerier[*sharedtypes.Params, sharedtypes.SharedQueryClient]( + deps, sharedtypes.NewSharedQueryClient, + WithModuleInfo(sharedtypes.ModuleName, sharedtypes.ErrSharedParamInvalid), + WithQueryCacheOptions(paramsQuerierCfg.CacheOpts...), + ) + if err != nil { + return nil, err + } + + sq := &sharedQuerier{ + ParamsQuerier: paramsQuerier, + } - if err := depinject.Inject( + if err = depinject.Inject( deps, - &querier.clientConn, - &querier.blockQuerier, + &sq.clientConn, + &sq.blockQuerier, ); err != nil { return nil, err } - querier.sharedQuerier = sharedtypes.NewQueryClient(querier.clientConn) + sq.sharedQuerier = sharedtypes.NewQueryClient(sq.clientConn) - return querier, nil -} - -// GetParams queries & returns the shared module on-chain parameters. -// -// TODO_TECHDEBT(#543): We don't really want to have to query the params for every method call. -// Once `ModuleParamsClient` is implemented, use its replay observable's `#Last()` method -// to get the most recently (asynchronously) observed (and cached) value. -func (sq *sharedQuerier) GetParams(ctx context.Context) (*sharedtypes.Params, error) { - req := &sharedtypes.QueryParamsRequest{} - res, err := sq.sharedQuerier.Params(ctx, req) - if err != nil { - return nil, ErrQuerySessionParams.Wrapf("[%v]", err) - } - return &res.Params, nil + return sq, nil } // GetClaimWindowOpenHeight returns the block height at which the claim window of // the session that includes queryHeight opens. -// -// TODO_MAINNET(#543): We don't really want to have to query the params for every method call. -// Once `ModuleParamsClient` is implemented, use its replay observable's `#Last()` method -// to get the most recently (asynchronously) observed (and cached) value. -// TODO_MAINNET(@bryanchriswhite,#543): We also don't really want to use the current value of the params. Instead, -// we should be using the value that the params had for the session which includes queryHeight. func (sq *sharedQuerier) GetClaimWindowOpenHeight(ctx context.Context, queryHeight int64) (int64, error) { - sharedParams, err := sq.GetParams(ctx) + sharedParams, err := sq.GetParamsAtHeight(ctx, queryHeight) if err != nil { return 0, err } @@ -75,14 +75,8 @@ func (sq *sharedQuerier) GetClaimWindowOpenHeight(ctx context.Context, queryHeig // GetProofWindowOpenHeight returns the block height at which the proof window of // the session that includes queryHeight opens. -// -// TODO_MAINNET(#543): We don't really want to have to query the params for every method call. -// Once `ModuleParamsClient` is implemented, use its replay observable's `#Last()` method -// to get the most recently (asynchronously) observed (and cached) value. -// TODO_MAINNET(@bryanchriswhite,#543): We also don't really want to use the current value of the params. Instead, -// we should be using the value that the params had for the session which includes queryHeight. func (sq *sharedQuerier) GetProofWindowOpenHeight(ctx context.Context, queryHeight int64) (int64, error) { - sharedParams, err := sq.GetParams(ctx) + sharedParams, err := sq.GetParamsAtHeight(ctx, queryHeight) if err != nil { return 0, err } @@ -103,7 +97,7 @@ func (sq *sharedQuerier) GetSessionGracePeriodEndHeight( ctx context.Context, queryHeight int64, ) (int64, error) { - sharedParams, err := sq.GetParams(ctx) + sharedParams, err := sq.GetParamsAtHeight(ctx, queryHeight) if err != nil { return 0, err } @@ -118,8 +112,12 @@ func (sq *sharedQuerier) GetSessionGracePeriodEndHeight( // to get the most recently (asynchronously) observed (and cached) value. // TODO_MAINNET(@bryanchriswhite, #543): We also don't really want to use the current value of the params. // Instead, we should be using the value that the params had for the session which includes queryHeight. -func (sq *sharedQuerier) GetEarliestSupplierClaimCommitHeight(ctx context.Context, queryHeight int64, supplierOperatorAddr string) (int64, error) { - sharedParams, err := sq.GetParams(ctx) +func (sq *sharedQuerier) GetEarliestSupplierClaimCommitHeight( + ctx context.Context, + queryHeight int64, + supplierOperatorAddr string, +) (int64, error) { + sharedParams, err := sq.GetParamsAtHeight(ctx, queryHeight) if err != nil { return 0, err } @@ -151,8 +149,12 @@ func (sq *sharedQuerier) GetEarliestSupplierClaimCommitHeight(ctx context.Contex // to get the most recently (asynchronously) observed (and cached) value. // TODO_MAINNET(@bryanchriswhite, #543): We also don't really want to use the current value of the params. // Instead, we should be using the value that the params had for the session which includes queryHeight. -func (sq *sharedQuerier) GetEarliestSupplierProofCommitHeight(ctx context.Context, queryHeight int64, supplierOperatorAddr string) (int64, error) { - sharedParams, err := sq.GetParams(ctx) +func (sq *sharedQuerier) GetEarliestSupplierProofCommitHeight( + ctx context.Context, + queryHeight int64, + supplierOperatorAddr string, +) (int64, error) { + sharedParams, err := sq.GetParamsAtHeight(ctx, queryHeight) if err != nil { return 0, err } @@ -180,8 +182,8 @@ func (sq *sharedQuerier) GetEarliestSupplierProofCommitHeight(ctx context.Contex // to get the most recently (asynchronously) observed (and cached) value. // TODO_MAINNET(@bryanchriswhite, #543): We also don't really want to use the current value of the params. // Instead, we should be using the value that the params had for the session which includes queryHeight. -func (sq *sharedQuerier) GetComputeUnitsToTokensMultiplier(ctx context.Context) (uint64, error) { - sharedParams, err := sq.GetParams(ctx) +func (sq *sharedQuerier) GetComputeUnitsToTokensMultiplier(ctx context.Context, queryHeight int64) (uint64, error) { + sharedParams, err := sq.GetParamsAtHeight(ctx, queryHeight) if err != nil { return 0, err } diff --git a/pkg/client/query/sharedquerier_test.go b/pkg/client/query/sharedquerier_test.go new file mode 100644 index 000000000..784df8ae2 --- /dev/null +++ b/pkg/client/query/sharedquerier_test.go @@ -0,0 +1,123 @@ +package query_test + +import ( + "context" + "testing" + "time" + + "cosmossdk.io/depinject" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "google.golang.org/grpc" + + "github.com/pokt-network/poktroll/pkg/client" + "github.com/pokt-network/poktroll/pkg/client/query" + "github.com/pokt-network/poktroll/pkg/client/query/cache" + _ "github.com/pokt-network/poktroll/pkg/polylog/polyzero" + "github.com/pokt-network/poktroll/testutil/mockclient" + sharedtypes "github.com/pokt-network/poktroll/x/shared/types" +) + +type SharedQuerierTestSuite struct { + suite.Suite + ctrl *gomock.Controller + ctx context.Context + querier client.SharedQueryClient + TTL time.Duration + clientConnMock *mockclient.MockClientConn + blockClientMock *mockclient.MockCometRPC +} + +func TestSharedQuerierSuite(t *testing.T) { + suite.Run(t, new(SharedQuerierTestSuite)) +} + +func (s *SharedQuerierTestSuite) SetupTest() { + s.ctrl = gomock.NewController(s.T()) + s.ctx = context.Background() + s.clientConnMock = mockclient.NewMockClientConn(s.ctrl) + s.blockClientMock = mockclient.NewMockCometRPC(s.ctrl) + s.TTL = 200 * time.Millisecond + + deps := depinject.Supply(s.clientConnMock, s.blockClientMock) + + // Create a shared querier with test-specific cache settings. + querier, err := query.NewSharedQuerier(deps, + query.WithQueryCacheOptions( + cache.WithTTL(s.TTL), + cache.WithHistoricalMode(100), + ), + ) + require.NoError(s.T(), err) + require.NotNil(s.T(), querier) + + s.querier = querier +} + +func (s *SharedQuerierTestSuite) TearDownTest() { + s.ctrl.Finish() +} + +func (s *SharedQuerierTestSuite) TestRetrievesAndCachesParamsValues() { + multiplier := uint64(1000) + + s.expectMockConnToReturnParamsWithMultiplierOnce(multiplier) + + // Initial get should be a cache miss. + params1, err := s.querier.GetParams(s.ctx) + s.NoError(err) + s.Equal(multiplier, params1.ComputeUnitsToTokensMultiplier) + + // Second get should be a cache hit. + params2, err := s.querier.GetParams(s.ctx) + s.NoError(err) + s.Equal(multiplier, params2.ComputeUnitsToTokensMultiplier) + + // Third get, after 90% of the TTL - should still be a cache hit. + time.Sleep(time.Duration(float64(s.TTL) * .9)) + params3, err := s.querier.GetParams(s.ctx) + s.NoError(err) + s.Equal(multiplier, params3.ComputeUnitsToTokensMultiplier) +} + +func (s *SharedQuerierTestSuite) TestHandlesCacheExpiration() { + s.expectMockConnToReturnParamsWithMultiplierOnce(2000) + + params1, err := s.querier.GetParams(s.ctx) + s.NoError(err) + s.Equal(uint64(2000), params1.ComputeUnitsToTokensMultiplier) + + // Wait for cache to expire + time.Sleep(300 * time.Millisecond) + + // Next query should be a cache miss again. + s.expectMockConnToReturnParamsWithMultiplierOnce(3000) + + params2, err := s.querier.GetParams(s.ctx) + s.NoError(err) + s.Equal(uint64(3000), params2.ComputeUnitsToTokensMultiplier) +} + +// expectMockConnToReturnParamsWithMultiplerOnce registers an expectation on s.clientConnMock +// such that this test will fail if the mock connection doesn't see exactly one params request. +// When it does see the params request, it will respond with a sharedtypes.Params object where +// the ComputeUnitsToTokensMultiplier field is set to the given multiplier. +func (s *SharedQuerierTestSuite) expectMockConnToReturnParamsWithMultiplierOnce(multiplier uint64) { + s.clientConnMock.EXPECT(). + Invoke( + gomock.Any(), + "/poktroll.shared.Query/Params", + gomock.Any(), + gomock.Any(), + gomock.Any(), + ). + DoAndReturn(func(_ context.Context, _ string, _, reply any, _ ...grpc.CallOption) error { + resp := reply.(*sharedtypes.QueryParamsResponse) + params := sharedtypes.DefaultParams() + params.ComputeUnitsToTokensMultiplier = multiplier + + resp.Params = params + return nil + }).Times(1) +} diff --git a/x/proof/types/shared_query_client.go b/x/proof/types/shared_query_client.go index 574735e7e..a47e4898f 100644 --- a/x/proof/types/shared_query_client.go +++ b/x/proof/types/shared_query_client.go @@ -4,6 +4,7 @@ import ( "context" "github.com/pokt-network/poktroll/pkg/client" + "github.com/pokt-network/poktroll/x/shared/keeper" sharedtypes "github.com/pokt-network/poktroll/x/shared/types" ) @@ -13,6 +14,8 @@ var _ client.SharedQueryClient = (*SharedKeeperQueryClient)(nil) // It does not rely on the QueryClient, and therefore does not make any // network requests as in the off-chain implementation. type SharedKeeperQueryClient struct { + client.ParamsQuerier[*sharedtypes.Params] + sharedKeeper SharedKeeper sessionKeeper SessionKeeper } @@ -23,59 +26,57 @@ func NewSharedKeeperQueryClient( sharedKeeper SharedKeeper, sessionKeeper SessionKeeper, ) client.SharedQueryClient { + keeperParamsQuerier := keeper.NewKeeperParamsQuerier[sharedtypes.Params](sharedKeeper) + return &SharedKeeperQueryClient{ + ParamsQuerier: keeperParamsQuerier, sharedKeeper: sharedKeeper, sessionKeeper: sessionKeeper, } } -// GetParams queries & returns the shared module on-chain parameters. -func (sqc *SharedKeeperQueryClient) GetParams( - ctx context.Context, -) (params *sharedtypes.Params, err error) { - sharedParams := sqc.sharedKeeper.GetParams(ctx) - return &sharedParams, nil -} - // GetSessionGracePeriodEndHeight returns the block height at which the grace period // for the session which includes queryHeight elapses. // The grace period is the number of blocks after the session ends during which relays // SHOULD be included in the session which most recently ended. -// -// TODO_MAINNET(@bryanchriswhite, #543): We don't really want to use the current value of the params. -// Instead, we should be using the value that the params had for the session given by blockHeight. func (sqc *SharedKeeperQueryClient) GetSessionGracePeriodEndHeight( ctx context.Context, queryHeight int64, ) (int64, error) { - sharedParams := sqc.sharedKeeper.GetParams(ctx) - return sharedtypes.GetSessionGracePeriodEndHeight(&sharedParams, queryHeight), nil + sharedParams, err := sqc.GetParamsAtHeight(ctx, queryHeight) + if err != nil { + return 0, err + } + + return sharedtypes.GetSessionGracePeriodEndHeight(sharedParams, queryHeight), nil } // GetClaimWindowOpenHeight returns the block height at which the claim window of // the session that includes queryHeight opens. -// -// TODO_MAINNET(@bryanchriswhite, #543): We don't really want to use the current value of the params. -// Instead, we should be using the value that the params had for the session given by blockHeight. func (sqc *SharedKeeperQueryClient) GetClaimWindowOpenHeight( ctx context.Context, queryHeight int64, ) (int64, error) { - sharedParams := sqc.sharedKeeper.GetParams(ctx) - return sharedtypes.GetClaimWindowOpenHeight(&sharedParams, queryHeight), nil + sharedParams, err := sqc.GetParamsAtHeight(ctx, queryHeight) + if err != nil { + return 0, err + } + + return sharedtypes.GetClaimWindowOpenHeight(sharedParams, queryHeight), nil } // GetProofWindowOpenHeight returns the block height at which the proof window of // the session that includes queryHeight opens. -// -// TODO_MAINNET(@bryanchriswhite, #543): We don't really want to use the current value of the params. -// Instead, we should be using the value that the params had for the session given by blockHeight. func (sqc *SharedKeeperQueryClient) GetProofWindowOpenHeight( ctx context.Context, queryHeight int64, ) (int64, error) { - sharedParams := sqc.sharedKeeper.GetParams(ctx) - return sharedtypes.GetProofWindowOpenHeight(&sharedParams, queryHeight), nil + sharedParams, err := sqc.GetParamsAtHeight(ctx, queryHeight) + if err != nil { + return 0, err + } + + return sharedtypes.GetProofWindowOpenHeight(sharedParams, queryHeight), nil } // GetEarliestSupplierClaimCommitHeight returns the earliest block height at which a claim @@ -85,8 +86,12 @@ func (sqc *SharedKeeperQueryClient) GetEarliestSupplierClaimCommitHeight( queryHeight int64, supplierOperatorAddr string, ) (int64, error) { - sharedParams := sqc.sharedKeeper.GetParams(ctx) - claimWindowOpenHeight := sharedtypes.GetClaimWindowOpenHeight(&sharedParams, queryHeight) + sharedParams, err := sqc.GetParamsAtHeight(ctx, queryHeight) + if err != nil { + return 0, err + } + + claimWindowOpenHeight := sharedtypes.GetClaimWindowOpenHeight(sharedParams, queryHeight) // Fetch the claim window open block hash so that it can be used as part of the // pseudo-random seed for generating the claim distribution offset. @@ -95,7 +100,7 @@ func (sqc *SharedKeeperQueryClient) GetEarliestSupplierClaimCommitHeight( // Get the earliest claim commit height for the given supplier. return sharedtypes.GetEarliestSupplierClaimCommitHeight( - &sharedParams, + sharedParams, queryHeight, claimWindowOpenBlockHashBz, supplierOperatorAddr, @@ -109,8 +114,12 @@ func (sqc *SharedKeeperQueryClient) GetEarliestSupplierProofCommitHeight( queryHeight int64, supplierOperatorAddr string, ) (int64, error) { - sharedParams := sqc.sharedKeeper.GetParams(ctx) - proofWindowOpenHeight := sharedtypes.GetProofWindowOpenHeight(&sharedParams, queryHeight) + sharedParams, err := sqc.GetParamsAtHeight(ctx, queryHeight) + if err != nil { + return 0, err + } + + proofWindowOpenHeight := sharedtypes.GetProofWindowOpenHeight(sharedParams, queryHeight) // Fetch the proof window open block hash so that it can be used as part of the // pseudo-random seed for generating the proof distribution offset. @@ -119,20 +128,21 @@ func (sqc *SharedKeeperQueryClient) GetEarliestSupplierProofCommitHeight( // Get the earliest proof commit height for the given supplier. return sharedtypes.GetEarliestSupplierProofCommitHeight( - &sharedParams, + sharedParams, queryHeight, proofWindowOpenBlockHash, supplierOperatorAddr, ), nil } -// GetComputeUnitsToTokensMultiplier returns the multiplier used to convert compute units to tokens. -// -// TODO_POST_MAINNNET: If this changes mid-session, the cost of the relays at the -// end of the session may differ from what was anticipated at the beginning. -// Since this will be a non-frequent occurrence, accounting for this edge case is -// not an immediate blocker. -func (sqc *SharedKeeperQueryClient) GetComputeUnitsToTokensMultiplier(ctx context.Context) (uint64, error) { - sharedParams := sqc.sharedKeeper.GetParams(ctx) +// GetComputeUnitsToTokensMultiplier returns the multiplier used to convert compute +// units to tokens. The caller likely SHOULD pass the session start height for queryHeight +// as to avoid miscalculations in scenarios where the params were changed mid-session. +func (sqc *SharedKeeperQueryClient) GetComputeUnitsToTokensMultiplier(ctx context.Context, queryHeight int64) (uint64, error) { + sharedParams, err := sqc.GetParamsAtHeight(ctx, queryHeight) + if err != nil { + return 0, err + } + return sharedParams.GetComputeUnitsToTokensMultiplier(), nil } diff --git a/x/shared/keeper/params_query_client.go b/x/shared/keeper/params_query_client.go new file mode 100644 index 000000000..bd1c60cfc --- /dev/null +++ b/x/shared/keeper/params_query_client.go @@ -0,0 +1,83 @@ +package keeper + +import ( + "context" + "errors" + "fmt" + + "github.com/pokt-network/poktroll/pkg/client" + "github.com/pokt-network/poktroll/pkg/client/query/cache" + sharedtypes "github.com/pokt-network/poktroll/x/shared/types" +) + +var _ client.ParamsQuerier[*sharedtypes.Params] = (*keeperParamsQuerier[sharedtypes.Params, Keeper])(nil) + +// DEV_NOTE: Can't use cosmostypes.Msg instead of any because P +// would be a pointer but GetParams() returns a value. 🙄 +type paramsKeeperIface[P any] interface { + GetParams(context.Context) P +} + +// keeperParamsQuerier provides a base implementation of ParamsQuerier for keeper-based clients +type keeperParamsQuerier[P any, K paramsKeeperIface[P]] struct { + keeper K + cache client.HistoricalQueryCache[P] +} + +// NewKeeperParamsQuerier creates a new keeperParamsQuerier instance +func NewKeeperParamsQuerier[P any, K paramsKeeperIface[P]]( + keeper K, + opts ...cache.QueryCacheOptionFn, +) *keeperParamsQuerier[P, K] { + // Use sensible defaults for keeper-based params cache + defaultOpts := []cache.QueryCacheOptionFn{ + cache.WithHistoricalMode(100), // Keep history of last 100 blocks + cache.WithEvictionPolicy(cache.FirstInFirstOut), + } + opts = append(defaultOpts, opts...) + + // TODO_IMPROVE: Implement and call a goroutine that subscribes to params updates to keep the cache hot. + + return &keeperParamsQuerier[P, K]{ + keeper: keeper, + cache: cache.NewInMemoryCache[P](opts...), + } +} + +// GetParams retrieves current parameters from the keeper +func (kpq *keeperParamsQuerier[P, K]) GetParams(ctx context.Context) (*P, error) { + // Check cache first + cached, err := kpq.cache.Get("params") + if err == nil { + return &cached, nil + } + if err != nil && !errors.Is(err, cache.ErrCacheMiss) { + return &cached, err + } + + // On cache miss, get from keeper + params := kpq.keeper.GetParams(ctx) + + // Cache the result + if err := kpq.cache.Set("params", params); err != nil { + return ¶ms, fmt.Errorf("failed to cache params: %w", err) + } + + return ¶ms, nil +} + +// GetParamsAtHeight retrieves parameters as they were at a specific height +func (kpq *keeperParamsQuerier[P, K]) GetParamsAtHeight(ctx context.Context, height int64) (*P, error) { + // Try cache first + cached, err := kpq.cache.GetAtHeight("params", height) + if err == nil { + return &cached, nil + } + if err != nil && !errors.Is(err, cache.ErrCacheMiss) { + return &cached, err + } + + // For now, return current params as historical params are not yet implemented + // TODO_MAINNET: Implement historical parameter querying from state + return kpq.GetParams(ctx) +} diff --git a/x/shared/types/query_client.go b/x/shared/types/query_client.go new file mode 100644 index 000000000..763c72aaf --- /dev/null +++ b/x/shared/types/query_client.go @@ -0,0 +1,34 @@ +package types + +import ( + "context" + + gogogrpc "github.com/cosmos/gogoproto/grpc" +) + +// SharedQueryClient is an interface which adapts generated (concrete) shared query client +// to paramsQuerierIface (see: pkg/client/query/paramsquerier.go) such that implementors +// (i.e. the generated shared query client) is compliant with client.ParamsQuerier for the +// shared module's params type. This is required to resolve generic type constraints. +type SharedQueryClient interface { + QueryClient + GetParams(context.Context) (*Params, error) +} + +// NewSharedQueryClient is a wrapper for the shared query client constructor which +// returns a new shared query client as a SharedQueryClient interface type. +func NewSharedQueryClient(conn gogogrpc.ClientConn) SharedQueryClient { + return NewQueryClient(conn).(SharedQueryClient) +} + +// GetParams returns the shared module's params as a pointer, which is critical to +// resolve related generic type constraints between client.ParamsQuerier and it's usages. +func (c *queryClient) GetParams(ctx context.Context) (*Params, error) { + res, err := c.Params(ctx, &QueryParamsRequest{}) + if err != nil { + return nil, err + } + + params := res.GetParams() + return ¶ms, nil +}