diff --git a/pkg/client/query/accquerier.go b/pkg/client/query/accquerier.go index 932db5836..627e9471b 100644 --- a/pkg/client/query/accquerier.go +++ b/pkg/client/query/accquerier.go @@ -2,7 +2,6 @@ package query import ( "context" - "sync" "cosmossdk.io/depinject" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" @@ -21,11 +20,7 @@ var _ client.AccountQueryClient = (*accQuerier)(nil) type accQuerier struct { clientConn grpc.ClientConn accountQuerier accounttypes.QueryClient - - // accountCache is a cache of accounts that have already been queried. - // TODO_TECHDEBT: Add a size limit to the cache and consider an LRU cache. - accountCache map[string]types.AccountI - accountCacheMu sync.Mutex + accountsCache KeyValueCache[types.AccountI] } // NewAccountQuerier returns a new instance of a client.AccountQueryClient by @@ -34,10 +29,11 @@ type accQuerier struct { // Required dependencies: // - clientCtx func NewAccountQuerier(deps depinject.Config) (client.AccountQueryClient, error) { - aq := &accQuerier{accountCache: make(map[string]types.AccountI)} + aq := &accQuerier{} if err := depinject.Inject( deps, + &aq.accountsCache, &aq.clientConn, ); err != nil { return nil, err @@ -53,11 +49,9 @@ func (aq *accQuerier) GetAccount( ctx context.Context, address string, ) (types.AccountI, error) { - aq.accountCacheMu.Lock() - defer aq.accountCacheMu.Unlock() - - if foundAccount, isAccountFound := aq.accountCache[address]; isAccountFound { - return foundAccount, nil + // Check if the account is present in the cache. + if account, found := aq.accountsCache.Get(address); found { + return account, nil } // Query the blockchain for the account record @@ -81,8 +75,8 @@ func (aq *accQuerier) GetAccount( return nil, ErrQueryPubKeyNotFound } - aq.accountCache[address] = fetchedAccount - + // Cache the fetched account for future queries. + aq.accountsCache.Set(address, fetchedAccount) return fetchedAccount, nil } diff --git a/pkg/client/query/appquerier.go b/pkg/client/query/appquerier.go index 356ce674c..fb2f414c2 100644 --- a/pkg/client/query/appquerier.go +++ b/pkg/client/query/appquerier.go @@ -18,6 +18,8 @@ var _ client.ApplicationQueryClient = (*appQuerier)(nil) type appQuerier struct { clientConn grpc.ClientConn applicationQuerier apptypes.QueryClient + applicationsCache KeyValueCache[apptypes.Application] + paramsCache ParamsCache[apptypes.Params] } // NewApplicationQuerier returns a new instance of a client.ApplicationQueryClient @@ -30,6 +32,8 @@ func NewApplicationQuerier(deps depinject.Config) (client.ApplicationQueryClient if err := depinject.Inject( deps, + &aq.paramsCache, + &aq.applicationsCache, &aq.clientConn, ); err != nil { return nil, err @@ -45,17 +49,28 @@ func (aq *appQuerier) GetApplication( ctx context.Context, appAddress string, ) (apptypes.Application, error) { + // Check if the application is present in the cache. + if app, found := aq.applicationsCache.Get(appAddress); found { + return app, nil + } + req := apptypes.QueryGetApplicationRequest{Address: appAddress} res, err := aq.applicationQuerier.Application(ctx, &req) if err != nil { return apptypes.Application{}, apptypes.ErrAppNotFound.Wrapf("app address: %s [%v]", appAddress, err) } + + // Cache the application. + aq.applicationsCache.Set(appAddress, res.Application) return res.Application, nil } // GetAllApplications returns all staked applications func (aq *appQuerier) GetAllApplications(ctx context.Context) ([]apptypes.Application, error) { req := apptypes.QueryAllApplicationsRequest{} + // TODO_CONSIDERATION: Fill the cache with all applications and mark it as + // having been filled, such that subsequent calls to this function will + // return the cached value. res, err := aq.applicationQuerier.AllApplications(ctx, &req) if err != nil { return []apptypes.Application{}, err @@ -65,10 +80,18 @@ func (aq *appQuerier) GetAllApplications(ctx context.Context) ([]apptypes.Applic // GetParams returns the application module parameters func (aq *appQuerier) GetParams(ctx context.Context) (*apptypes.Params, error) { + // Check if the application module parameters are present in the cache. + if params, found := aq.paramsCache.Get(); found { + return ¶ms, nil + } + req := apptypes.QueryParamsRequest{} res, err := aq.applicationQuerier.Params(ctx, &req) if err != nil { return nil, err } + + // Update the cache with the newly retrieved application module parameters. + aq.paramsCache.Set(res.Params) return &res.Params, nil } diff --git a/pkg/client/query/bankquerier.go b/pkg/client/query/bankquerier.go index ca28a4998..37b346f95 100644 --- a/pkg/client/query/bankquerier.go +++ b/pkg/client/query/bankquerier.go @@ -17,8 +17,9 @@ var _ client.BankQueryClient = (*bankQuerier)(nil) // bankQuerier is a wrapper around the banktypes.QueryClient that enables the // querying of onchain balance information. type bankQuerier struct { - clientConn grpc.ClientConn - bankQuerier banktypes.QueryClient + clientConn grpc.ClientConn + bankQuerier banktypes.QueryClient + balancesCache KeyValueCache[*sdk.Coin] } // NewBankQuerier returns a new instance of a client.BankQueryClient by @@ -31,6 +32,7 @@ func NewBankQuerier(deps depinject.Config) (client.BankQueryClient, error) { if err := depinject.Inject( deps, + &bq.balancesCache, &bq.clientConn, ); err != nil { return nil, err @@ -46,6 +48,11 @@ func (bq *bankQuerier) GetBalance( ctx context.Context, address string, ) (*sdk.Coin, error) { + // Check if the account balance is present in the cache. + if balance, found := bq.balancesCache.Get(address); found { + return balance, nil + } + // Query the blockchain for the balance record req := &banktypes.QueryBalanceRequest{Address: address, Denom: volatile.DenomuPOKT} res, err := bq.bankQuerier.Balance(ctx, req) @@ -53,5 +60,7 @@ func (bq *bankQuerier) GetBalance( return nil, ErrQueryBalanceNotFound.Wrapf("address: %s [%s]", address, err) } + // Cache the balance for future queries + bq.balancesCache.Set(address, res.Balance) return res.Balance, nil } diff --git a/pkg/client/query/cache/kvcache.go b/pkg/client/query/cache/kvcache.go new file mode 100644 index 000000000..0ce19c808 --- /dev/null +++ b/pkg/client/query/cache/kvcache.go @@ -0,0 +1,61 @@ +package cache + +import ( + "sync" + + "github.com/pokt-network/poktroll/pkg/client/query" +) + +var _ query.KeyValueCache[any] = (*keyValueCache[any])(nil) + +// keyValueCache is a simple in-memory key-value cache implementation. +// It is safe for concurrent use. +type keyValueCache[V any] struct { + cacheMu sync.RWMutex + valuesMap map[string]V +} + +// NewKeyValueCache returns a new instance of a KeyValueCache. +func NewKeyValueCache[T any]() query.KeyValueCache[T] { + return &keyValueCache[T]{ + valuesMap: make(map[string]T), + } +} + +// Get returns the value for the given key. +// A boolean is returned as the second value to indicate if the key was found in the cache. +func (c *keyValueCache[V]) Get(key string) (value V, found bool) { + c.cacheMu.RLock() + defer c.cacheMu.RUnlock() + + value, found = c.valuesMap[key] + return value, found +} + +// Set sets the value for the given key. +// TODO_CONSIDERATION: Add a method to set many values and indicate whether it +// is the result of a GetAll operation. This would allow us to know whether the +// cache is populated with all the possible values, so any other GetAll operation +// could be returned from the cache. +func (c *keyValueCache[V]) Set(key string, value V) { + c.cacheMu.Lock() + defer c.cacheMu.Unlock() + + c.valuesMap[key] = value +} + +// Delete deletes the value for the given key. +func (c *keyValueCache[V]) Delete(key string) { + c.cacheMu.Lock() + defer c.cacheMu.Unlock() + + delete(c.valuesMap, key) +} + +// Clear empties the whole cache. +func (c *keyValueCache[V]) Clear() { + c.cacheMu.Lock() + defer c.cacheMu.Unlock() + + c.valuesMap = make(map[string]V) +} diff --git a/pkg/client/query/cache/options.go b/pkg/client/query/cache/options.go new file mode 100644 index 000000000..f4a487e8f --- /dev/null +++ b/pkg/client/query/cache/options.go @@ -0,0 +1,38 @@ +package cache + +import ( + "context" + + "cosmossdk.io/depinject" + + "github.com/pokt-network/poktroll/pkg/client" + "github.com/pokt-network/poktroll/pkg/observable/channel" +) + +// ClearableCache is an interface that defines the common methods for a cache object. +type Cache interface { + Clear() +} + +// CacheOption is a function type for the option functions that can customize +// the cache behavior. +type CacheOption[C Cache] func(context.Context, depinject.Config, C) error + +// WithNewBlockCacheClearing is a cache option that clears the cache every time +// a new block is observed. +func WithNewBlockCacheClearing[C Cache](ctx context.Context, deps depinject.Config, cache C) error { + var blockClient client.BlockClient + if err := depinject.Inject(deps, &blockClient); err != nil { + return err + } + + channel.ForEach( + ctx, + blockClient.CommittedBlocksSequence(ctx), + func(ctx context.Context, block client.Block) { + cache.Clear() + }, + ) + + return nil +} diff --git a/pkg/client/query/cache/paramscache.go b/pkg/client/query/cache/paramscache.go new file mode 100644 index 000000000..c07cab3d4 --- /dev/null +++ b/pkg/client/query/cache/paramscache.go @@ -0,0 +1,54 @@ +package cache + +import ( + "sync" + + "github.com/pokt-network/poktroll/pkg/client/query" +) + +var _ query.ParamsCache[any] = (*paramsCache[any])(nil) + +// paramsCache is a simple in-memory cache implementation for query parameters. +// It does not involve key-value pairs, but only stores a single value. +type paramsCache[T any] struct { + cacheMu sync.RWMutex + found bool + value T +} + +// NewParamsCache returns a new instance of a ParamsCache. +func NewParamsCache[T any]() query.ParamsCache[T] { + return ¶msCache[T]{} +} + +// Get returns the value stored in the cache. +// A boolean is returned as the second value to indicate if the value was found in the cache. +func (c *paramsCache[T]) Get() (value T, found bool) { + c.cacheMu.RLock() + defer c.cacheMu.RUnlock() + + return c.value, c.found +} + +// Set sets the value in the cache. +func (c *paramsCache[T]) Set(value T) { + c.cacheMu.Lock() + defer c.cacheMu.Unlock() + + c.found = true + c.value = value +} + +// Clear empties the cache. +func (c *paramsCache[T]) Clear() { + c.cacheMu.Lock() + defer c.cacheMu.Unlock() + + c.found = false + c.value = zeroValue[T]() +} + +// zeroValue is a generic helper which returns the zero value of the given type. +func zeroValue[T any]() (zero T) { + return zero +} diff --git a/pkg/client/query/interface.go b/pkg/client/query/interface.go new file mode 100644 index 000000000..d4c97448c --- /dev/null +++ b/pkg/client/query/interface.go @@ -0,0 +1,16 @@ +package query + +// ParamsCache is an interface for a simple in-memory cache implementation for query parameters. +type ParamsCache[T any] interface { + Get() (T, bool) + Set(T) + Clear() +} + +// KeyValueCache is an interface for a simple in-memory key-value cache implementation. +type KeyValueCache[V any] interface { + Get(string) (V, bool) + Set(string, V) + Delete(string) + Clear() +} diff --git a/pkg/client/query/proofquerier.go b/pkg/client/query/proofquerier.go index 6751dc995..3c0293a07 100644 --- a/pkg/client/query/proofquerier.go +++ b/pkg/client/query/proofquerier.go @@ -15,6 +15,7 @@ import ( type proofQuerier struct { clientConn grpc.ClientConn proofQuerier prooftypes.QueryClient + paramsCache ParamsCache[prooftypes.Params] } // NewProofQuerier returns a new instance of a client.ProofQueryClient by @@ -27,6 +28,7 @@ func NewProofQuerier(deps depinject.Config) (client.ProofQueryClient, error) { if err := depinject.Inject( deps, + &querier.paramsCache, &querier.clientConn, ); err != nil { return nil, err @@ -41,10 +43,18 @@ func NewProofQuerier(deps depinject.Config) (client.ProofQueryClient, error) { func (pq *proofQuerier) GetParams( ctx context.Context, ) (client.ProofParams, error) { + // Get the params from the cache if they exist. + if params, found := pq.paramsCache.Get(); found { + return ¶ms, nil + } + req := &prooftypes.QueryParamsRequest{} res, err := pq.proofQuerier.Params(ctx, req) if err != nil { return nil, err } + + // Update the cache with the newly retrieved params. + pq.paramsCache.Set(res.Params) return &res.Params, nil } diff --git a/pkg/client/query/servicequerier.go b/pkg/client/query/servicequerier.go index 1f5ef2d2a..8312504a8 100644 --- a/pkg/client/query/servicequerier.go +++ b/pkg/client/query/servicequerier.go @@ -17,8 +17,10 @@ var _ client.ServiceQueryClient = (*serviceQuerier)(nil) // querying of onchain service information through a single exposed method // which returns a sharedtypes.Service struct type serviceQuerier struct { - clientConn grpc.ClientConn - serviceQuerier servicetypes.QueryClient + clientConn grpc.ClientConn + serviceQuerier servicetypes.QueryClient + servicesCache KeyValueCache[sharedtypes.Service] + relayMiningDifficultyCache KeyValueCache[servicetypes.RelayMiningDifficulty] } // NewServiceQuerier returns a new instance of a client.ServiceQueryClient by @@ -31,6 +33,8 @@ func NewServiceQuerier(deps depinject.Config) (client.ServiceQueryClient, error) if err := depinject.Inject( deps, + &servq.servicesCache, + &servq.relayMiningDifficultyCache, &servq.clientConn, ); err != nil { return nil, err @@ -47,6 +51,11 @@ func (servq *serviceQuerier) GetService( ctx context.Context, serviceId string, ) (sharedtypes.Service, error) { + // Check if the service is present in the cache. + if service, found := servq.servicesCache.Get(serviceId); found { + return service, nil + } + req := &servicetypes.QueryGetServiceRequest{ Id: serviceId, } @@ -58,6 +67,9 @@ func (servq *serviceQuerier) GetService( serviceId, err, ) } + + // Cache the service for future use. + servq.servicesCache.Set(serviceId, res.Service) return res.Service, nil } @@ -67,6 +79,11 @@ func (servq *serviceQuerier) GetServiceRelayDifficulty( ctx context.Context, serviceId string, ) (servicetypes.RelayMiningDifficulty, error) { + // Check if the relay mining difficulty is present in the cache. + if relayMiningDifficulty, found := servq.relayMiningDifficultyCache.Get(serviceId); found { + return relayMiningDifficulty, nil + } + req := &servicetypes.QueryGetRelayMiningDifficultyRequest{ ServiceId: serviceId, } @@ -76,5 +93,7 @@ func (servq *serviceQuerier) GetServiceRelayDifficulty( return servicetypes.RelayMiningDifficulty{}, err } + // Cache the relay mining difficulty for future use. + servq.relayMiningDifficultyCache.Set(serviceId, res.RelayMiningDifficulty) return res.RelayMiningDifficulty, nil } diff --git a/pkg/client/query/sessionquerier.go b/pkg/client/query/sessionquerier.go index fdf6c42e9..d766fd52f 100644 --- a/pkg/client/query/sessionquerier.go +++ b/pkg/client/query/sessionquerier.go @@ -2,12 +2,14 @@ package query import ( "context" + "fmt" "cosmossdk.io/depinject" "github.com/cosmos/gogoproto/grpc" "github.com/pokt-network/poktroll/pkg/client" sessiontypes "github.com/pokt-network/poktroll/x/session/types" + sharedtypes "github.com/pokt-network/poktroll/x/shared/types" ) var _ client.SessionQueryClient = (*sessionQuerier)(nil) @@ -16,8 +18,11 @@ var _ client.SessionQueryClient = (*sessionQuerier)(nil) // querying of onchain session information through a single exposed method // which returns an sessiontypes.Session struct type sessionQuerier struct { - clientConn grpc.ClientConn - sessionQuerier sessiontypes.QueryClient + clientConn grpc.ClientConn + sessionQuerier sessiontypes.QueryClient + sharedQueryClient client.SharedQueryClient + sessionsCache KeyValueCache[*sessiontypes.Session] + paramsCache ParamsCache[sessiontypes.Params] } // NewSessionQuerier returns a new instance of a client.SessionQueryClient by @@ -30,6 +35,9 @@ func NewSessionQuerier(deps depinject.Config) (client.SessionQueryClient, error) if err := depinject.Inject( deps, + &sessq.sharedQueryClient, + &sessq.paramsCache, + &sessq.sessionsCache, &sessq.clientConn, ); err != nil { return nil, err @@ -48,6 +56,24 @@ func (sessq *sessionQuerier) GetSession( serviceId string, blockHeight int64, ) (*sessiontypes.Session, error) { + // Get the shared parameters to calculate the session start height. + // Use the session start height as the canonical height to be used in the cache key. + sharedParams, err := sessq.sharedQueryClient.GetParams(ctx) + if err != nil { + return nil, err + } + sessionStartHeight := sharedtypes.GetSessionStartHeight(sharedParams, blockHeight) + // Construct the cache key using the appAddress, serviceId and session start height. + // Using the session start height as the canonical height ensures that the cache + // does not duplicate entries for the same session given different block heights + // of the same session. + sessionKey := fmt.Sprintf("%s/%s/%d", appAddress, serviceId, sessionStartHeight) + + // Check if the session is present in the cache. + if session, found := sessq.sessionsCache.Get(sessionKey); found { + return session, nil + } + req := &sessiontypes.QueryGetSessionRequest{ ApplicationAddress: appAddress, ServiceId: serviceId, @@ -60,15 +86,26 @@ func (sessq *sessionQuerier) GetSession( appAddress, serviceId, blockHeight, err, ) } + + // Cache the session using the session key. + sessq.sessionsCache.Set(sessionKey, res.Session) return res.Session, nil } // GetParams queries & returns the session module onchain parameters. func (sessq *sessionQuerier) GetParams(ctx context.Context) (*sessiontypes.Params, error) { + // Check if the params are present in the cache. + if params, found := sessq.paramsCache.Get(); found { + return ¶ms, nil + } + req := &sessiontypes.QueryParamsRequest{} res, err := sessq.sessionQuerier.Params(ctx, req) if err != nil { return nil, ErrQuerySessionParams.Wrapf("[%v]", err) } + + // Cache the params for future queries. + sessq.paramsCache.Set(res.Params) return &res.Params, nil } diff --git a/pkg/client/query/sharedquerier.go b/pkg/client/query/sharedquerier.go index bbe67b0de..47a13004e 100644 --- a/pkg/client/query/sharedquerier.go +++ b/pkg/client/query/sharedquerier.go @@ -2,6 +2,7 @@ package query import ( "context" + "strconv" "cosmossdk.io/depinject" "github.com/cosmos/gogoproto/grpc" @@ -16,9 +17,11 @@ var _ client.SharedQueryClient = (*sharedQuerier)(nil) // querying of onchain shared information through a single exposed method // which returns an sharedtypes.Session struct type sharedQuerier struct { - clientConn grpc.ClientConn - sharedQuerier sharedtypes.QueryClient - blockQuerier client.BlockQueryClient + clientConn grpc.ClientConn + sharedQuerier sharedtypes.QueryClient + blockQuerier client.BlockQueryClient + blockHashCache KeyValueCache[[]byte] + paramsCache ParamsCache[sharedtypes.Params] } // NewSharedQuerier returns a new instance of a client.SharedQueryClient by @@ -32,6 +35,7 @@ func NewSharedQuerier(deps depinject.Config) (client.SharedQueryClient, error) { if err := depinject.Inject( deps, + &querier.paramsCache, &querier.clientConn, &querier.blockQuerier, ); err != nil { @@ -49,11 +53,19 @@ func NewSharedQuerier(deps depinject.Config) (client.SharedQueryClient, error) { // 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) { + // Get the params from the cache if they exist. + if params, found := sq.paramsCache.Get(); found { + return ¶ms, nil + } + req := &sharedtypes.QueryParamsRequest{} res, err := sq.sharedQuerier.Params(ctx, req) if err != nil { return nil, ErrQuerySessionParams.Wrapf("[%v]", err) } + + // Update the cache with the newly retrieved params. + sq.paramsCache.Set(res.Params) return &res.Params, nil } @@ -127,13 +139,20 @@ func (sq *sharedQuerier) GetEarliestSupplierClaimCommitHeight(ctx context.Contex // Fetch the block at the proof window open height. Its hash is used as part // of the seed to the pseudo-random number generator. claimWindowOpenHeight := sharedtypes.GetClaimWindowOpenHeight(sharedParams, queryHeight) - claimWindowOpenBlock, err := sq.blockQuerier.Block(ctx, &claimWindowOpenHeight) - if err != nil { - return 0, err - } - // NB: Byte slice representation of block hashes don't need to be normalized. - claimWindowOpenBlockHash := claimWindowOpenBlock.BlockID.Hash.Bytes() + // Check if the block hash is already in the cache. + claimWindowOpenHeightStr := strconv.FormatInt(claimWindowOpenHeight, 10) + claimWindowOpenBlockHash, found := sq.blockHashCache.Get(claimWindowOpenHeightStr) + if !found { + claimWindowOpenBlock, err := sq.blockQuerier.Block(ctx, &claimWindowOpenHeight) + if err != nil { + return 0, err + } + + // Cache the block hash for future use. + // NB: Byte slice representation of block hashes don't need to be normalized. + claimWindowOpenBlockHash = claimWindowOpenBlock.BlockID.Hash.Bytes() + } return sharedtypes.GetEarliestSupplierClaimCommitHeight( sharedParams, @@ -157,18 +176,25 @@ func (sq *sharedQuerier) GetEarliestSupplierProofCommitHeight(ctx context.Contex return 0, err } - // Fetch the block at the proof window open height. Its hash is used as part - // of the seed to the pseudo-random number generator. - proofWindowOpenHeight := sharedtypes.GetProofWindowOpenHeight(sharedParams, queryHeight) - proofWindowOpenBlock, err := sq.blockQuerier.Block(ctx, &proofWindowOpenHeight) - if err != nil { - return 0, err + proofWindowOpenBlockHash, found := sq.blockHashCache.Get(strconv.FormatInt(queryHeight, 10)) + + if !found { + // Fetch the block at the proof window open height. Its hash is used as part + // of the seed to the pseudo-random number generator. + proofWindowOpenHeight := sharedtypes.GetProofWindowOpenHeight(sharedParams, queryHeight) + proofWindowOpenBlock, err := sq.blockQuerier.Block(ctx, &proofWindowOpenHeight) + if err != nil { + return 0, err + } + + // Cache the block hash for future use. + proofWindowOpenBlockHash = proofWindowOpenBlock.BlockID.Hash.Bytes() } return sharedtypes.GetEarliestSupplierProofCommitHeight( sharedParams, queryHeight, - proofWindowOpenBlock.BlockID.Hash, + proofWindowOpenBlockHash, supplierOperatorAddr, ), nil } diff --git a/pkg/client/query/supplierquerier.go b/pkg/client/query/supplierquerier.go index 927f2b335..f409a4669 100644 --- a/pkg/client/query/supplierquerier.go +++ b/pkg/client/query/supplierquerier.go @@ -17,6 +17,7 @@ import ( type supplierQuerier struct { clientConn grpc.ClientConn supplierQuerier suppliertypes.QueryClient + suppliersCache KeyValueCache[sharedtypes.Supplier] } // NewSupplierQuerier returns a new instance of a client.SupplierQueryClient by @@ -29,6 +30,7 @@ func NewSupplierQuerier(deps depinject.Config) (client.SupplierQueryClient, erro if err := depinject.Inject( deps, + &supq.suppliersCache, &supq.clientConn, ); err != nil { return nil, err @@ -44,6 +46,11 @@ func (supq *supplierQuerier) GetSupplier( ctx context.Context, operatorAddress string, ) (sharedtypes.Supplier, error) { + // Check if the supplier is present in the cache. + if supplier, found := supq.suppliersCache.Get(operatorAddress); found { + return supplier, nil + } + req := &suppliertypes.QueryGetSupplierRequest{OperatorAddress: operatorAddress} res, err := supq.supplierQuerier.Supplier(ctx, req) if err != nil { @@ -52,5 +59,8 @@ func (supq *supplierQuerier) GetSupplier( operatorAddress, err, ) } + + // Cache the supplier for future use. + supq.suppliersCache.Set(operatorAddress, res.Supplier) return res.Supplier, nil } diff --git a/pkg/deps/config/suppliers.go b/pkg/deps/config/suppliers.go index 26f04043e..59c0ba68d 100644 --- a/pkg/deps/config/suppliers.go +++ b/pkg/deps/config/suppliers.go @@ -17,6 +17,7 @@ import ( "github.com/pokt-network/poktroll/pkg/client/delegation" "github.com/pokt-network/poktroll/pkg/client/events" "github.com/pokt-network/poktroll/pkg/client/query" + "github.com/pokt-network/poktroll/pkg/client/query/cache" querytypes "github.com/pokt-network/poktroll/pkg/client/query/types" "github.com/pokt-network/poktroll/pkg/client/supplier" "github.com/pokt-network/poktroll/pkg/client/tx" @@ -507,3 +508,45 @@ func newSupplyTxClientsFn( return depinject.Configs(deps, depinject.Supply(txClient)), nil } + +// NewSupplyTxClientFn returns a function which constructs a KeyValueCache of type T. +// It take a list of cache options that can be used to configure the cache. +func NewSupplyKeyValueCacheFn[T any](opts ...cache.CacheOption[query.KeyValueCache[T]]) SupplierFn { + return func( + ctx context.Context, + deps depinject.Config, + _ *cobra.Command, + ) (depinject.Config, error) { + kvCache := cache.NewKeyValueCache[T]() + + // Apply the cache options + for _, opt := range opts { + if err := opt(ctx, deps, kvCache); err != nil { + return nil, err + } + } + + return depinject.Configs(deps, depinject.Supply(kvCache)), nil + } +} + +// NewSupplyParamsCacheFn returns a function which constructs a ParamsCache of type T. +// It take a list of cache options that can be used to configure the cache. +func NewSupplyParamsCacheFn[T any](opts ...cache.CacheOption[query.ParamsCache[T]]) SupplierFn { + return func( + ctx context.Context, + deps depinject.Config, + _ *cobra.Command, + ) (depinject.Config, error) { + paramsCache := cache.NewParamsCache[T]() + + // Apply the cache options + for _, opt := range opts { + if err := opt(ctx, deps, paramsCache); err != nil { + return nil, err + } + } + + return depinject.Configs(deps, depinject.Supply(paramsCache)), nil + } +} diff --git a/pkg/relayer/cmd/cmd.go b/pkg/relayer/cmd/cmd.go index 574f405b4..06971afca 100644 --- a/pkg/relayer/cmd/cmd.go +++ b/pkg/relayer/cmd/cmd.go @@ -12,9 +12,11 @@ import ( cosmosclient "github.com/cosmos/cosmos-sdk/client" cosmosflags "github.com/cosmos/cosmos-sdk/client/flags" cosmostx "github.com/cosmos/cosmos-sdk/client/tx" + cosmostypes "github.com/cosmos/cosmos-sdk/types" "github.com/spf13/cobra" "github.com/pokt-network/poktroll/cmd/signals" + "github.com/pokt-network/poktroll/pkg/client/query/cache" "github.com/pokt-network/poktroll/pkg/client/tx" txtypes "github.com/pokt-network/poktroll/pkg/client/tx/types" "github.com/pokt-network/poktroll/pkg/deps/config" @@ -25,6 +27,11 @@ import ( "github.com/pokt-network/poktroll/pkg/relayer/miner" "github.com/pokt-network/poktroll/pkg/relayer/proxy" "github.com/pokt-network/poktroll/pkg/relayer/session" + apptypes "github.com/pokt-network/poktroll/x/application/types" + prooftypes "github.com/pokt-network/poktroll/x/proof/types" + servicetypes "github.com/pokt-network/poktroll/x/service/types" + sessiontypes "github.com/pokt-network/poktroll/x/session/types" + sharedtypes "github.com/pokt-network/poktroll/x/shared/types" ) // We're `explicitly omitting default` so the relayer crashes if these aren't specified. @@ -198,7 +205,23 @@ func setupRelayerDependencies( config.NewSupplyQueryClientContextFn(queryNodeGRPCUrl), // leaf config.NewSupplyTxClientContextFn(queryNodeGRPCUrl, txNodeRPCUrl), // leaf config.NewSupplyDelegationClientFn(), // leaf - config.NewSupplySharedQueryClientFn(), // leaf + + // Setup the params caches and configure them to clear on new blocks. + config.NewSupplyParamsCacheFn[sharedtypes.Params](cache.WithNewBlockCacheClearing), + config.NewSupplyParamsCacheFn[apptypes.Params](cache.WithNewBlockCacheClearing), + config.NewSupplyParamsCacheFn[sessiontypes.Params](cache.WithNewBlockCacheClearing), + config.NewSupplyParamsCacheFn[prooftypes.Params](cache.WithNewBlockCacheClearing), + + // Setup the key-value caches and configure them to clear on new blocks. + config.NewSupplyKeyValueCacheFn[sharedtypes.Service](cache.WithNewBlockCacheClearing), + config.NewSupplyKeyValueCacheFn[servicetypes.RelayMiningDifficulty](cache.WithNewBlockCacheClearing), + config.NewSupplyKeyValueCacheFn[apptypes.Application](cache.WithNewBlockCacheClearing), + config.NewSupplyKeyValueCacheFn[cosmostypes.AccountI](cache.WithNewBlockCacheClearing), + config.NewSupplyKeyValueCacheFn[sharedtypes.Supplier](cache.WithNewBlockCacheClearing), + config.NewSupplyKeyValueCacheFn[*sessiontypes.Session](cache.WithNewBlockCacheClearing), + config.NewSupplyKeyValueCacheFn[*cosmostypes.Coin](cache.WithNewBlockCacheClearing), + + config.NewSupplySharedQueryClientFn(), // leaf config.NewSupplyServiceQueryClientFn(), config.NewSupplyApplicationQuerierFn(), config.NewSupplySessionQuerierFn(), diff --git a/testutil/integration/suites/application.go b/testutil/integration/suites/application.go index 86b22fccf..a05af606a 100644 --- a/testutil/integration/suites/application.go +++ b/testutil/integration/suites/application.go @@ -10,6 +10,7 @@ import ( "github.com/pokt-network/poktroll/app/volatile" "github.com/pokt-network/poktroll/pkg/client" "github.com/pokt-network/poktroll/pkg/client/query" + "github.com/pokt-network/poktroll/pkg/client/query/cache" apptypes "github.com/pokt-network/poktroll/x/application/types" sharedtypes "github.com/pokt-network/poktroll/x/shared/types" ) @@ -25,7 +26,9 @@ type ApplicationModuleSuite struct { // GetAppQueryClient constructs and returns a query client for the application // module of the integration app. func (s *ApplicationModuleSuite) GetAppQueryClient() client.ApplicationQueryClient { - deps := depinject.Supply(s.GetApp().QueryHelper()) + appCache := cache.NewKeyValueCache[apptypes.Application]() + appParamsCache := cache.NewParamsCache[apptypes.Params]() + deps := depinject.Supply(s.GetApp().QueryHelper(), appCache, appParamsCache) appQueryClient, err := query.NewApplicationQuerier(deps) require.NoError(s.T(), err)