diff --git a/pkg/client/interface.go b/pkg/client/interface.go index 03d7f7923..f5ee598f7 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/testutil/integration/app.go b/testutil/integration/app.go index 715c0135b..641d0b71c 100644 --- a/testutil/integration/app.go +++ b/testutil/integration/app.go @@ -605,13 +605,16 @@ func NewCompleteIntegrationApp(t *testing.T, opts ...IntegrationAppOptionFn) *Ap preGeneratedAccts := testkeyring.PreGeneratedAccounts() integrationApp.preGeneratedAccts = preGeneratedAccts + sharedKeeperQueryClient, err := prooftypes.NewSharedKeeperQueryClient(sharedKeeper, sessionKeeper) + require.NoError(t, err) + // Construct a ringClient to get the application's ring & verify the relay // request signature. ringClient, err := rings.NewRingClient(depinject.Supply( polyzero.NewLogger(), prooftypes.NewAppKeeperQueryClient(applicationKeeper), prooftypes.NewAccountKeeperQueryClient(accountKeeper), - prooftypes.NewSharedKeeperQueryClient(sharedKeeper, sessionKeeper), + sharedKeeperQueryClient, )) require.NoError(t, err) integrationApp.ringClient = ringClient diff --git a/x/proof/keeper/keeper.go b/x/proof/keeper/keeper.go index 5c67ca8d4..579231a02 100644 --- a/x/proof/keeper/keeper.go +++ b/x/proof/keeper/keeper.go @@ -62,7 +62,10 @@ func NewKeeper( polylogger := polylog.Ctx(context.Background()) applicationQuerier := types.NewAppKeeperQueryClient(applicationKeeper) accountQuerier := types.NewAccountKeeperQueryClient(accountKeeper) - sharedQuerier := types.NewSharedKeeperQueryClient(sharedKeeper, sessionKeeper) + sharedQuerier, err := types.NewSharedKeeperQueryClient(sharedKeeper, sessionKeeper) + if err != nil { + panic(err) + } // RingKeeperClient holds the logic of verifying RelayRequests ring signatures // for both on-chain and off-chain actors. diff --git a/x/proof/keeper/msg_server_submit_proof_test.go b/x/proof/keeper/msg_server_submit_proof_test.go index 35185f798..e8dcaf48c 100644 --- a/x/proof/keeper/msg_server_submit_proof_test.go +++ b/x/proof/keeper/msg_server_submit_proof_test.go @@ -144,12 +144,15 @@ func TestMsgServer_SubmitProof_Success(t *testing.T) { // Construct a proof message server from the proof keeper. srv := keeper.NewMsgServerImpl(*keepers.Keeper) + sharedKeeperQueryClient, err := prooftypes.NewSharedKeeperQueryClient(keepers.SharedKeeper, keepers.SessionKeeper) + require.NoError(t, err) + // Prepare a ring client to sign & validate relays. ringClient, err := rings.NewRingClient(depinject.Supply( polyzero.NewLogger(), prooftypes.NewAppKeeperQueryClient(keepers.ApplicationKeeper), prooftypes.NewAccountKeeperQueryClient(keepers.AccountKeeper), - prooftypes.NewSharedKeeperQueryClient(keepers.SharedKeeper, keepers.SessionKeeper), + sharedKeeperQueryClient, )) require.NoError(t, err) @@ -319,12 +322,15 @@ func TestMsgServer_SubmitProof_Error_OutsideOfWindow(t *testing.T) { // Construct a proof message server from the proof keeper. srv := keeper.NewMsgServerImpl(*keepers.Keeper) + sharedKeeperQueryClient, err := prooftypes.NewSharedKeeperQueryClient(keepers.SharedKeeper, keepers.SessionKeeper) + require.NoError(t, err) + // Prepare a ring client to sign & validate relays. ringClient, err := rings.NewRingClient(depinject.Supply( polyzero.NewLogger(), prooftypes.NewAppKeeperQueryClient(keepers.ApplicationKeeper), prooftypes.NewAccountKeeperQueryClient(keepers.AccountKeeper), - prooftypes.NewSharedKeeperQueryClient(keepers.SharedKeeper, keepers.SessionKeeper), + sharedKeeperQueryClient, )) require.NoError(t, err) @@ -516,13 +522,16 @@ func TestMsgServer_SubmitProof_Error(t *testing.T) { // Construct a proof message server from the proof keeper. srv := keeper.NewMsgServerImpl(*keepers.Keeper) + sharedKeeperQueryClient, err := prooftypes.NewSharedKeeperQueryClient(keepers.SharedKeeper, keepers.SessionKeeper) + require.NoError(t, err) + // Construct a ringClient to get the application's ring & verify the relay // request signature. ringClient, err := rings.NewRingClient(depinject.Supply( polyzero.NewLogger(), prooftypes.NewAppKeeperQueryClient(keepers.ApplicationKeeper), prooftypes.NewAccountKeeperQueryClient(keepers.AccountKeeper), - prooftypes.NewSharedKeeperQueryClient(keepers.SharedKeeper, keepers.SessionKeeper), + sharedKeeperQueryClient, )) require.NoError(t, err) @@ -765,12 +774,15 @@ func TestMsgServer_SubmitProof_FailSubmittingNonRequiredProof(t *testing.T) { // Construct a proof message server from the proof keeper. srv := keeper.NewMsgServerImpl(*keepers.Keeper) + sharedKeeperQueryClient, err := prooftypes.NewSharedKeeperQueryClient(keepers.SharedKeeper, keepers.SessionKeeper) + require.NoError(t, err) + // Prepare a ring client to sign & validate relays. ringClient, err := rings.NewRingClient(depinject.Supply( polyzero.NewLogger(), prooftypes.NewAppKeeperQueryClient(keepers.ApplicationKeeper), prooftypes.NewAccountKeeperQueryClient(keepers.AccountKeeper), - prooftypes.NewSharedKeeperQueryClient(keepers.SharedKeeper, keepers.SessionKeeper), + sharedKeeperQueryClient, )) require.NoError(t, err) diff --git a/x/proof/keeper/proof_validation_test.go b/x/proof/keeper/proof_validation_test.go index a3e6133ab..cd7afbebc 100644 --- a/x/proof/keeper/proof_validation_test.go +++ b/x/proof/keeper/proof_validation_test.go @@ -109,13 +109,16 @@ func TestEnsureValidProof_Error(t *testing.T) { // TODO_TECHDEBT: add a test case such that we can distinguish between early // & late session end block heights. + sharedKeeperQueryClient, err := prooftypes.NewSharedKeeperQueryClient(keepers.SharedKeeper, keepers.SessionKeeper) + require.NoError(t, err) + // Construct a ringClient to get the application's ring & verify the relay // request signature. ringClient, err := rings.NewRingClient(depinject.Supply( polyzero.NewLogger(), prooftypes.NewAppKeeperQueryClient(keepers.ApplicationKeeper), prooftypes.NewAccountKeeperQueryClient(keepers.AccountKeeper), - prooftypes.NewSharedKeeperQueryClient(keepers.SharedKeeper, keepers.SessionKeeper), + sharedKeeperQueryClient, )) require.NoError(t, err) diff --git a/x/proof/types/shared_query_client.go b/x/proof/types/shared_query_client.go index 574735e7e..2ce9cad06 100644 --- a/x/proof/types/shared_query_client.go +++ b/x/proof/types/shared_query_client.go @@ -4,15 +4,18 @@ 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" ) -var _ client.SharedQueryClient = (*SharedKeeperQueryClient)(nil) +var _ client.SharedQueryClient = (*sharedKeeperQueryClient)(nil) -// SharedKeeperQueryClient is a thin wrapper around the SharedKeeper. +// sharedKeeperQueryClient is a thin wrapper around the SharedKeeper. // It does not rely on the QueryClient, and therefore does not make any // network requests as in the off-chain implementation. -type SharedKeeperQueryClient struct { +type sharedKeeperQueryClient struct { + client.ParamsQuerier[*sharedtypes.Params] + sharedKeeper SharedKeeper sessionKeeper SessionKeeper } @@ -22,19 +25,17 @@ type SharedKeeperQueryClient struct { func NewSharedKeeperQueryClient( sharedKeeper SharedKeeper, sessionKeeper SessionKeeper, -) client.SharedQueryClient { - return &SharedKeeperQueryClient{ - sharedKeeper: sharedKeeper, - sessionKeeper: sessionKeeper, +) (client.SharedQueryClient, error) { + keeperParamsQuerier, err := keeper.NewKeeperParamsQuerier[sharedtypes.Params](sharedKeeper) + if err != nil { + return nil, err } -} -// 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 + return &sharedKeeperQueryClient{ + ParamsQuerier: keeperParamsQuerier, + sharedKeeper: sharedKeeper, + sessionKeeper: sessionKeeper, + }, nil } // GetSessionGracePeriodEndHeight returns the block height at which the grace period @@ -42,51 +43,74 @@ func (sqc *SharedKeeperQueryClient) GetParams( // 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( +// TODO_MAINNET(@bryanchriswhite, #931): 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 queryHeight. +func (sqc *sharedKeeperQueryClient) GetSessionGracePeriodEndHeight( ctx context.Context, queryHeight int64, ) (int64, error) { - sharedParams := sqc.sharedKeeper.GetParams(ctx) - return sharedtypes.GetSessionGracePeriodEndHeight(&sharedParams, queryHeight), nil + // TODO_MAINNET(#931): sqc.GetParamsAtHeight(ctx, queryHeight) + sharedParams, err := sqc.GetParams(ctx) + 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( +// TODO_MAINNET(@bryanchriswhite, #931): 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 queryHeight. +func (sqc *sharedKeeperQueryClient) GetClaimWindowOpenHeight( ctx context.Context, queryHeight int64, ) (int64, error) { - sharedParams := sqc.sharedKeeper.GetParams(ctx) - return sharedtypes.GetClaimWindowOpenHeight(&sharedParams, queryHeight), nil + // TODO_MAINNET(#931): sqc.GetParamsAtHeight(ctx, queryHeight) + sharedParams, err := sqc.GetParams(ctx) + 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( +// TODO_MAINNET(@bryanchriswhite, #931): 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 queryHeight. +func (sqc *sharedKeeperQueryClient) GetProofWindowOpenHeight( ctx context.Context, queryHeight int64, ) (int64, error) { - sharedParams := sqc.sharedKeeper.GetParams(ctx) - return sharedtypes.GetProofWindowOpenHeight(&sharedParams, queryHeight), nil + // TODO_MAINNET(#931): sqc.GetParamsAtHeight(ctx, queryHeight) + sharedParams, err := sqc.GetParams(ctx) + if err != nil { + return 0, err + } + + return sharedtypes.GetProofWindowOpenHeight(sharedParams, queryHeight), nil } // GetEarliestSupplierClaimCommitHeight returns the earliest block height at which a claim // for the session that includes queryHeight can be committed for a given supplier. -func (sqc *SharedKeeperQueryClient) GetEarliestSupplierClaimCommitHeight( +// +// TODO_MAINNET(@bryanchriswhite, #931): 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 queryHeight. +func (sqc *sharedKeeperQueryClient) GetEarliestSupplierClaimCommitHeight( ctx context.Context, queryHeight int64, supplierOperatorAddr string, ) (int64, error) { - sharedParams := sqc.sharedKeeper.GetParams(ctx) - claimWindowOpenHeight := sharedtypes.GetClaimWindowOpenHeight(&sharedParams, queryHeight) + // TODO_MAINNET(#931): sqc.GetParamsAtHeight(ctx, queryHeight) + sharedParams, err := sqc.GetParams(ctx) + 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 +119,7 @@ func (sqc *SharedKeeperQueryClient) GetEarliestSupplierClaimCommitHeight( // Get the earliest claim commit height for the given supplier. return sharedtypes.GetEarliestSupplierClaimCommitHeight( - &sharedParams, + sharedParams, queryHeight, claimWindowOpenBlockHashBz, supplierOperatorAddr, @@ -104,13 +128,21 @@ func (sqc *SharedKeeperQueryClient) GetEarliestSupplierClaimCommitHeight( // GetEarliestSupplierProofCommitHeight returns the earliest block height at which a proof // for the session that includes queryHeight can be committed for a given supplier. -func (sqc *SharedKeeperQueryClient) GetEarliestSupplierProofCommitHeight( +// +// TODO_MAINNET(@bryanchriswhite, #931): 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 queryHeight. +func (sqc *sharedKeeperQueryClient) GetEarliestSupplierProofCommitHeight( ctx context.Context, queryHeight int64, supplierOperatorAddr string, ) (int64, error) { - sharedParams := sqc.sharedKeeper.GetParams(ctx) - proofWindowOpenHeight := sharedtypes.GetProofWindowOpenHeight(&sharedParams, queryHeight) + // TODO_MAINNET(#931): sqc.GetParamsAtHeight(ctx, queryHeight) + sharedParams, err := sqc.GetParams(ctx) + 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 +151,25 @@ 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. +// 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. // -// 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) +// TODO_MAINNET(@bryanchriswhite, #931): 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 queryHeight. +func (sqc *sharedKeeperQueryClient) GetComputeUnitsToTokensMultiplier(ctx context.Context, queryHeight int64) (uint64, error) { + // TODO_MAINNET(#931): sqc.GetParamsAtHeight(ctx, queryHeight) + sharedParams, err := sqc.GetParams(ctx) + 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..6124652fa --- /dev/null +++ b/x/shared/keeper/params_query_client.go @@ -0,0 +1,48 @@ +package keeper + +import ( + "context" + "fmt" + + "github.com/pokt-network/poktroll/pkg/client" + 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 +} + +// NewKeeperParamsQuerier creates a new keeperParamsQuerier instance +func NewKeeperParamsQuerier[P any, K paramsKeeperIface[P]]( + keeper K, +) (*keeperParamsQuerier[P, K], error) { + return &keeperParamsQuerier[P, K]{ + keeper: keeper, + }, nil +} + +// GetParams retrieves current parameters from the keeper +func (kpq *keeperParamsQuerier[P, K]) GetParams(ctx context.Context) (*P, error) { + params := kpq.keeper.GetParams(ctx) + return ¶ms, nil +} + +// GetParamsAtHeight retrieves parameters as they were at a specific height +// +// TODO_MAINNET(@bryanchriswhite, #931): Integrate with indexer module/mixin once available. +// Currently, this method is (and MUST) NEVER called on-chain and only exists to satisfy the +// client.ParamsQuerier interface. However, it will be needed as part of #931 to support +// querying for params at historical heights, so it's short-circuited for now to always +// return an error. +func (kpq *keeperParamsQuerier[P, K]) GetParamsAtHeight(_ context.Context, _ int64) (*P, error) { + return nil, fmt.Errorf("TODO(#931): Support on-chain historical queries") +} 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 +} diff --git a/x/tokenomics/keeper/keeper.go b/x/tokenomics/keeper/keeper.go index 1c5c5fd11..4e7515c38 100644 --- a/x/tokenomics/keeper/keeper.go +++ b/x/tokenomics/keeper/keeper.go @@ -59,8 +59,12 @@ func NewKeeper( panic(fmt.Sprintf("invalid authority address: %s", authority)) } - sharedQuerier := prooftypes.NewSharedKeeperQueryClient(sharedKeeper, sessionKeeper) - if err := tlm.ValidateTLMConfig(tokenLogicModules); err != nil { + sharedQuerier, err := prooftypes.NewSharedKeeperQueryClient(sharedKeeper, sessionKeeper) + if err != nil { + panic(err) + } + + if err = tlm.ValidateTLMConfig(tokenLogicModules); err != nil { panic(err) } diff --git a/x/tokenomics/keeper/keeper_settle_pending_claims_test.go b/x/tokenomics/keeper/keeper_settle_pending_claims_test.go index 076663cce..a4e8dc3e6 100644 --- a/x/tokenomics/keeper/keeper_settle_pending_claims_test.go +++ b/x/tokenomics/keeper/keeper_settle_pending_claims_test.go @@ -143,13 +143,16 @@ func (s *TestSuite) SetupTest() { require.NoError(t, err) sessionHeader := sessionRes.Session.Header + sharedKeeperQueryClient, err := prooftypes.NewSharedKeeperQueryClient(s.keepers.SharedKeeper, s.keepers.SessionKeeper) + require.NoError(t, err) + // Construct a ringClient to get the application's ring & verify the relay // request signature. ringClient, err := rings.NewRingClient(depinject.Supply( polyzero.NewLogger(), prooftypes.NewAppKeeperQueryClient(s.keepers.ApplicationKeeper), prooftypes.NewAccountKeeperQueryClient(s.keepers.AccountKeeper), - prooftypes.NewSharedKeeperQueryClient(s.keepers.SharedKeeper, s.keepers.SessionKeeper), + sharedKeeperQueryClient, )) require.NoError(t, err)