diff --git a/docs/testing-guide.md b/docs/testing-guide.md index c88efe845a..35bf564771 100644 --- a/docs/testing-guide.md +++ b/docs/testing-guide.md @@ -156,6 +156,7 @@ import chaiAsPromised from 'chai-as-promised'; import sinon from 'sinon'; import { MyClass } from './my-class'; import { CacheService } from './cache-service'; +import { CacheClientFactory } from './cacheClientFactory'; chai.use(chaiAsPromised); @@ -165,7 +166,7 @@ describe('MyClass', function() { beforeEach(function() { // Common setup for all tests - cacheService = new CacheService(); + cacheService = CacheClientFactory.create(); myClass = new MyClass(cacheService); }); @@ -229,7 +230,7 @@ describe('MyClass', function() { }); }); }); - + describe('anotherMethod', () => { // Tests for anotherMethod // Use analogous formatting to the tests for myMethod @@ -268,6 +269,9 @@ import sinon from 'sinon'; import pino from 'pino'; import { overrideEnvsInMochaDescribe, useInMemoryRedisServer, withOverriddenEnvsInMochaTest } from './helpers'; +import { CacheService } from './cache-service'; +import { CacheClientFactory } from './cacheClientFactory'; + chai.use(chaiAsPromised); describe('MyClass', function() { @@ -289,7 +293,7 @@ describe('MyClass', function() { beforeEach(function() { // Common setup for all tests serviceThatDependsOnEnv = new ServiceThatDependsOnEnv(); - cacheService = new CacheService(); + cacheService = CacheClientFactory.create(); myClass = new MyClass(serviceThatDependsOnEnv, cacheService); }); diff --git a/packages/relay/src/lib/clients/cache/ICacheClient.ts b/packages/relay/src/lib/clients/cache/ICacheClient.ts index a8028bc41b..505071b980 100644 --- a/packages/relay/src/lib/clients/cache/ICacheClient.ts +++ b/packages/relay/src/lib/clients/cache/ICacheClient.ts @@ -11,4 +11,9 @@ export interface ICacheClient { incrBy(key: string, amount: number, callingMethod: string): Promise; rPush(key: string, value: any, callingMethod: string): Promise; lRange(key: string, start: number, end: number, callingMethod: string): Promise; + + /** + * @deprecated Alias of `get`; consider removing. Left in place to avoid modifying the CacheService interface. + */ + getAsync(key: string, callingMethod: string): Promise; } diff --git a/packages/relay/src/lib/clients/cache/localLRUCache.ts b/packages/relay/src/lib/clients/cache/localLRUCache.ts index b58f6917af..6bb6787d9c 100644 --- a/packages/relay/src/lib/clients/cache/localLRUCache.ts +++ b/packages/relay/src/lib/clients/cache/localLRUCache.ts @@ -110,6 +110,19 @@ export class LocalLRUCache implements ICacheClient { return `${LocalLRUCache.CACHE_KEY_PREFIX}${key}`; } + /** + * Alias for the `get` method. + * + * @param key - The key associated with the cached value. + * @param callingMethod - The name of the method calling the cache. + * @returns The cached value if found, otherwise null. + * + * @deprecated use `get` instead. + */ + public getAsync(key: string, callingMethod: string): Promise { + return this.get(key, callingMethod); + } + /** * Retrieves a cached value associated with the given key. * If the value exists in the cache, updates metrics and logs the retrieval. diff --git a/packages/relay/src/lib/clients/cache/measurableCache.ts b/packages/relay/src/lib/clients/cache/measurableCache.ts new file mode 100644 index 0000000000..364f0586e3 --- /dev/null +++ b/packages/relay/src/lib/clients/cache/measurableCache.ts @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: Apache-2.0 + +import { Counter } from 'prom-client'; + +import { ICacheClient } from './ICacheClient'; + +/** + * Represents a cache client that performs the caching operations and tracks and counts all processed events. + * + * @implements {ICacheClient} + */ +export class MeasurableCache implements ICacheClient { + private decorated: ICacheClient; + private readonly cacheMethodsCounter: Counter; + + public static readonly methods = { + GET: 'get', + SET: 'set', + DELETE: 'delete', + MSET: 'mSet', + PIPELINE: 'pipeline', + INCR_BY: 'incrBy', + RPUSH: 'rpush', + LRANGE: 'lrange', + }; + + private cacheType: string; + + public constructor(decorated: ICacheClient, cacheMethodsCounter: Counter, cacheType: string) { + this.decorated = decorated; + this.cacheMethodsCounter = cacheMethodsCounter; + this.cacheType = cacheType; + } + + /** + * Alias for the `get` method. + * + * @param key - The key associated with the cached value. + * @param callingMethod - The name of the method calling the cache. + * @returns The cached value if found, otherwise null. + * + * @deprecated use `get` instead. + */ + public getAsync(key: string, callingMethod: string): Promise { + return this.decorated.get(key, callingMethod); + } + + /** + * Calls the method that retrieves a cached value associated with the given key + * and tracks how many times this event occurs. + * + * @param key - The key associated with the cached value. + * @param callingMethod - The name of the method calling the cache. + * @returns The cached value if found, otherwise null. + */ + public async get(key: string, callingMethod: string): Promise { + this.cacheMethodsCounter.labels(callingMethod, this.cacheType, MeasurableCache.methods.GET).inc(1); + return await this.decorated.get(key, callingMethod); + } + + /** + * Calls the method that sets a value in the cache for the given key + * and tracks how many times this event occurs. + * + * @param key - The key to associate with the value. + * @param value - The value to cache. + * @param callingMethod - The name of the method calling the cache. + * @param ttl - Time to live for the cached value in milliseconds (optional). + */ + public async set(key: string, value: any, callingMethod: string, ttl?: number): Promise { + this.cacheMethodsCounter.labels(callingMethod, this.cacheType, MeasurableCache.methods.SET).inc(1); + return await this.decorated.set(key, value, callingMethod, ttl); + } + + /** + * Calls the method that stores multiple key–value pairs in the cache + * and tracks how many times this event occurs. + * + * @param keyValuePairs - An object where each property is a key and its value is the value to be cached. + * @param callingMethod - The name of the calling method. + * @param ttl - Time to live on the set values + * @returns A Promise that resolves when the values are cached. + */ + public async multiSet(keyValuePairs: Record, callingMethod: string, ttl?: number): Promise { + this.cacheMethodsCounter.labels(callingMethod, this.cacheType, MeasurableCache.methods.MSET).inc(1); + await this.decorated.multiSet(keyValuePairs, callingMethod, ttl); + } + + /** + * Calls the pipelineSet method that stores multiple key–value pairs in the cache + * and tracks how many times this event occurs. + * + * @param keyValuePairs - An object where each property is a key and its value is the value to be cached. + * @param callingMethod - The name of the calling method. + * @param ttl - Time to live on the set values + * @returns A Promise that resolves when the values are cached. + */ + public async pipelineSet(keyValuePairs: Record, callingMethod: string, ttl?: number): Promise { + this.cacheMethodsCounter.labels(callingMethod, this.cacheType, MeasurableCache.methods.PIPELINE).inc(1); + await this.decorated.pipelineSet(keyValuePairs, callingMethod, ttl); + } + + /** + * Calls the method that deletes the cached value associated with the given key + * and tracks how many times this event occurs. + * + * @param key - The key associated with the cached value to delete. + * @param callingMethod - The name of the method calling the cache. + */ + public async delete(key: string, callingMethod: string): Promise { + this.cacheMethodsCounter.labels(callingMethod, this.cacheType, MeasurableCache.methods.DELETE).inc(1); + await this.decorated.delete(key, callingMethod); + } + + /** + * Calls the method that clears the entire cache, removing all entries. + */ + public async clear(): Promise { + await this.decorated.clear(); + } + + /** + * Call the method that retrieves all keys in the cache that match the given pattern. + * + * @param pattern - The pattern to match keys against. + * @param callingMethod - The name of the method calling the cache. + * @returns An array of keys that match the pattern (without the cache prefix). + */ + public async keys(pattern: string, callingMethod: string): Promise { + return await this.decorated.keys(pattern, callingMethod); + } + + /** + * Calls the method that increments a cached value and tracks how many times this event occurs. + * + * @param key The key to increment + * @param amount The amount to increment by + * @param callingMethod The name of the calling method + * @returns The value of the key after incrementing + */ + public async incrBy(key: string, amount: number, callingMethod: string): Promise { + this.cacheMethodsCounter.labels(callingMethod, this.cacheType, MeasurableCache.methods.INCR_BY).inc(1); + return await this.decorated.incrBy(key, amount, callingMethod); + } + + /** + * Calls the method that retrieves a range of elements from a list in the cache + * and tracks how many times this event occurs. + * + * @param key The key of the list + * @param start The start index + * @param end The end index + * @param callingMethod The name of the calling method + * @returns The list of elements in the range + */ + public async lRange(key: string, start: number, end: number, callingMethod: string): Promise { + this.cacheMethodsCounter.labels(callingMethod, this.cacheType, MeasurableCache.methods.LRANGE).inc(1); + return await this.decorated.lRange(key, start, end, callingMethod); + } + + /** + * Calls the method that pushes a value to the end of a list in the cache + * and tracks how many times this event occurs. + * + * @param key The key of the list + * @param value The value to push + * @param callingMethod The name of the calling method + * @returns The length of the list after pushing + */ + public async rPush(key: string, value: any, callingMethod: string): Promise { + this.cacheMethodsCounter.labels(callingMethod, this.cacheType, MeasurableCache.methods.RPUSH).inc(1); + return await this.decorated.rPush(key, value, callingMethod); + } +} diff --git a/packages/relay/src/lib/clients/cache/redisCache/redisCache.ts b/packages/relay/src/lib/clients/cache/redisCache/redisCache.ts index 1f88a97774..fb25a6c297 100644 --- a/packages/relay/src/lib/clients/cache/redisCache/redisCache.ts +++ b/packages/relay/src/lib/clients/cache/redisCache/redisCache.ts @@ -65,6 +65,19 @@ export class RedisCache implements ICacheClient { return `${RedisCache.CACHE_KEY_PREFIX}${key}`; } + /** + * Alias for the `get` method. + * + * @param key - The key associated with the cached value. + * @param callingMethod - The name of the method calling the cache. + * @returns The cached value if found, otherwise null. + * + * @deprecated use `get` instead. + */ + public getAsync(key: string, callingMethod: string): Promise { + return this.get(key, callingMethod); + } + /** * Retrieves a value from the cache. * diff --git a/packages/relay/src/lib/clients/cache/redisCache/safeRedisCache.ts b/packages/relay/src/lib/clients/cache/redisCache/safeRedisCache.ts index db948af7be..aa08e8d32c 100644 --- a/packages/relay/src/lib/clients/cache/redisCache/safeRedisCache.ts +++ b/packages/relay/src/lib/clients/cache/redisCache/safeRedisCache.ts @@ -12,6 +12,19 @@ import { RedisCache } from './redisCache'; * Thanks to that our application will be able to continue functioning even with Redis being down... */ export class SafeRedisCache extends RedisCache { + /** + * Alias for the `get` method. + * + * @param key - The key associated with the cached value. + * @param callingMethod - The name of the method calling the cache. + * @returns The cached value if found, otherwise null. + * + * @deprecated use `get` instead. + */ + public getAsync(key: string, callingMethod: string): Promise { + return this.get(key, callingMethod); + } + /** * Retrieves a value from the cache. * diff --git a/packages/relay/src/lib/clients/index.ts b/packages/relay/src/lib/clients/index.ts index 8c398c4c7e..2b1b25c400 100644 --- a/packages/relay/src/lib/clients/index.ts +++ b/packages/relay/src/lib/clients/index.ts @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 export * from './cache/localLRUCache'; +export * from './cache/measurableCache'; export * from './cache/redisCache/index'; export * from './mirrorNodeClient'; export * from './sdkClient'; diff --git a/packages/relay/src/lib/factories/cacheClientFactory.ts b/packages/relay/src/lib/factories/cacheClientFactory.ts index 8cf93ea4b2..10887daf9c 100644 --- a/packages/relay/src/lib/factories/cacheClientFactory.ts +++ b/packages/relay/src/lib/factories/cacheClientFactory.ts @@ -2,12 +2,31 @@ import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services'; import type { Logger } from 'pino'; -import { Registry } from 'prom-client'; +import { Counter, Registry } from 'prom-client'; import type { RedisClientType } from 'redis'; -import { LocalLRUCache, RedisCache } from '../clients'; +import { LocalLRUCache, MeasurableCache, RedisCache } from '../clients'; import type { ICacheClient } from '../clients/cache/ICacheClient'; +const measurable = (client: ICacheClient, register: Registry, configType: 'lru' | 'redis') => { + /** + * Labels: + * callingMethod - The method initiating the cache operation + * cacheType - redis/lru + * method - The CacheService method being called + */ + const metricName = 'rpc_cache_service_methods_counter'; + register.removeSingleMetric(metricName); + const methodsCounter = new Counter({ + name: metricName, + help: 'Counter for calls to methods of CacheService separated by CallingMethod and CacheType', + registers: [register], + labelNames: ['callingMethod', 'cacheType', 'method'], + }); + + return new MeasurableCache(client, methodsCounter, configType); +}; + export class CacheClientFactory { static create( logger: Logger, @@ -16,7 +35,7 @@ export class CacheClientFactory { redisClient?: RedisClientType, ): ICacheClient { return !ConfigService.get('TEST') && redisClient !== undefined - ? new RedisCache(logger.child({ name: 'redisCache' }), redisClient) - : new LocalLRUCache(logger.child({ name: 'localLRUCache' }), register, reservedKeys); + ? measurable(new RedisCache(logger.child({ name: 'redisCache' }), redisClient), register, 'redis') + : measurable(new LocalLRUCache(logger.child({ name: 'localLRUCache' }), register, reservedKeys), register, 'lru'); } } diff --git a/packages/relay/src/lib/relay.ts b/packages/relay/src/lib/relay.ts index b48d9f72ab..de06da335c 100644 --- a/packages/relay/src/lib/relay.ts +++ b/packages/relay/src/lib/relay.ts @@ -284,14 +284,11 @@ export class Relay { const reservedKeys = HbarSpendingPlanConfigService.getPreconfiguredSpendingPlanKeys(this.logger); // Create CacheService with the connected Redis client (or undefined for LRU-only) - this.cacheService = new CacheService( - CacheClientFactory.create( - this.logger.child({ name: 'cache-service' }), - this.register, - reservedKeys, - this.redisClient, - ), + this.cacheService = CacheClientFactory.create( + this.logger.child({ name: 'cache-service' }), this.register, + reservedKeys, + this.redisClient, ); // Create spending plan repositories diff --git a/packages/relay/src/lib/services/cacheService/cacheService.ts b/packages/relay/src/lib/services/cacheService/cacheService.ts index 56ccce8b4d..265fa0372c 100644 --- a/packages/relay/src/lib/services/cacheService/cacheService.ts +++ b/packages/relay/src/lib/services/cacheService/cacheService.ts @@ -1,241 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 -import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services'; -import { Counter, Registry } from 'prom-client'; - -import { RedisCache } from '../../clients'; -import { ICacheClient } from '../../clients/cache/ICacheClient'; - /** * A service that manages caching using different cache implementations based on configuration. + * Client can be used directly, instead of this service, but it is left as an alias + * (and entrypoint for potential rewrites). */ -export class CacheService { - /** - * The cache used for caching items from requests. - * - * @private - */ - private readonly client: ICacheClient; - - /** - * Used for reference to the state of REDIS_ENABLED and REDIS_URL env. variables. - */ - private readonly isSharedCacheEnabled: boolean; - - /** - * Used for setting what type of multiSet method should be used to save new values. - */ - private readonly shouldMultiSet: boolean; - - /** - * Represents a caching manager that utilizes various cache implementations based on configuration. - * @param {Logger} logger - The logger used for logging all output from this class. - * @param {Registry} register - The metrics register used for metrics tracking. - */ - - private static readonly cacheTypes = { - REDIS: 'redis', - LRU: 'lru', - }; - - private static readonly methods = { - GET: 'get', - GET_ASYNC: 'getAsync', - SET: 'set', - DELETE: 'delete', - MSET: 'mSet', - PIPELINE: 'pipeline', - INCR_BY: 'incrBy', - RPUSH: 'rpush', - LRANGE: 'lrange', - }; - - private readonly cacheMethodsCounter: Counter; - - public constructor(client: ICacheClient, register: Registry = new Registry()) { - this.client = client; - this.isSharedCacheEnabled = client instanceof RedisCache; // TODO measurements will be moved out of here in the next PR. - this.shouldMultiSet = ConfigService.get('MULTI_SET'); // TODO measurements will be moved out of here in the next PR. - /** - * Labels: - * callingMethod - The method initiating the cache operation - * cacheType - redis/lru - * method - The CacheService method being called - */ - const metricName = 'rpc_cache_service_methods_counter'; - register.removeSingleMetric(metricName); - this.cacheMethodsCounter = new Counter({ - name: metricName, - help: 'Counter for calls to methods of CacheService separated by CallingMethod and CacheType', - registers: [register], - labelNames: ['callingMethod', 'cacheType', 'method'], - }); - } - - /** - * Retrieves a value from the cache asynchronously. - * - * @param key - The cache key. - * @param callingMethod - The name of the calling method. - * @returns A Promise that resolves with the cached value or null if not found. - */ - private async getFromSharedCache(key: string, callingMethod: string): Promise { - this.cacheMethodsCounter - .labels(callingMethod, CacheService.cacheTypes.REDIS, CacheService.methods.GET_ASYNC) - .inc(1); - - return await this.client.get(key, callingMethod); - } - - /** - * If SharedCacheEnabled will use shared, otherwise will fallback to internal cache. - * @param key - The cache key. - * @param callingMethod - The name of the calling method. - * @returns A Promise that resolves with the cached value or null if not found. - * @template T - The type of the cached value. - */ - public async getAsync(key: string, callingMethod: string): Promise { - if (this.isSharedCacheEnabled) { - return await this.getFromSharedCache(key, callingMethod); - } else { - return await this.getFromInternalCache(key, callingMethod); - } - } - - /** - * Retrieves a value from the internal cache. - * - * @param key - The cache key. - * @param callingMethod - The name of the calling method. - * @returns A Promise that resolves with the cached value or null if not found. - */ - private async getFromInternalCache(key: string, callingMethod: string): Promise { - this.cacheMethodsCounter.labels(callingMethod, CacheService.cacheTypes.LRU, CacheService.methods.GET).inc(1); - - return await this.client.get(key, callingMethod); - } - - /** - * Sets a value in the cache associated with the given key. - * - * @param key - The key to associate with the value. - * @param value - The value to cache. - * @param callingMethod - The name of the method calling the cache. - * @param ttl - Time to live for the cached value in milliseconds (optional). - */ - public async set(key: string, value: any, callingMethod: string, ttl?: number): Promise { - if (this.isSharedCacheEnabled) { - this.cacheMethodsCounter.labels(callingMethod, CacheService.cacheTypes.REDIS, CacheService.methods.SET).inc(1); - } else { - this.cacheMethodsCounter.labels(callingMethod, CacheService.cacheTypes.LRU, CacheService.methods.SET).inc(1); - } - await this.client.set(key, value, callingMethod, ttl); - } - - /** - * Sets multiple values in the cache, each associated with its respective key. - * @param entries - An object containing key-value pairs to cache. - * @param callingMethod - The name of the method calling the cache. - * @param ttl - Time to live for the cached value in milliseconds (optional). - */ - public async multiSet(entries: Record, callingMethod: string, ttl?: number): Promise { - await this.client.multiSet(entries, callingMethod, ttl); - if (this.isSharedCacheEnabled) { - const metricsMethod = this.shouldMultiSet ? CacheService.methods.MSET : CacheService.methods.PIPELINE; - this.cacheMethodsCounter.labels(callingMethod, CacheService.cacheTypes.REDIS, metricsMethod).inc(1); - } else { - this.cacheMethodsCounter.labels(callingMethod, CacheService.cacheTypes.LRU, CacheService.methods.SET).inc(1); - } - } - - /** - * Deletes a cached value associated with the given key. - * If the shared cache is enabled and an error occurs while deleting from it, just logs the error. - * Else the internal cache deletion is attempted. - * @param key - The key associated with the cached value to delete. - * @param callingMethod - The name of the method calling the cache. - */ - public async delete(key: string, callingMethod: string): Promise { - if (this.isSharedCacheEnabled) { - this.cacheMethodsCounter.labels(callingMethod, CacheService.cacheTypes.REDIS, CacheService.methods.DELETE).inc(1); - } else { - this.cacheMethodsCounter.labels(callingMethod, CacheService.cacheTypes.LRU, CacheService.methods.DELETE).inc(1); - } - await this.client.delete(key, callingMethod); - } - - /** - * Clears the cache. - * If the shared cache is enabled and an error occurs while clearing it, just logs the error. - * Else the internal cache clearing is attempted. - */ - public async clear(): Promise { - await this.client.clear(); - } - - /** - * Increments the value of a key in the cache by the specified amount. - * @param key - The key to increment. - * @param amount - The amount to increment by. - * @param callingMethod - The name of the calling method. - * @returns A Promise that resolves with the new value of the key after incrementing. - */ - public async incrBy(key: string, amount: number, callingMethod: string): Promise { - if (this.isSharedCacheEnabled) { - this.cacheMethodsCounter - .labels(callingMethod, CacheService.cacheTypes.REDIS, CacheService.methods.INCR_BY) - .inc(1); - } else { - this.cacheMethodsCounter.labels(callingMethod, CacheService.cacheTypes.LRU, CacheService.methods.GET).inc(1); - this.cacheMethodsCounter.labels(callingMethod, CacheService.cacheTypes.LRU, CacheService.methods.SET).inc(1); - } - - return await this.client.incrBy(key, amount, callingMethod); - } - - /** - * Pushes a value to the end of a list in the cache. - * @param key - The key of the list. - * @param value - The value to push. - * @param callingMethod - The name of the calling method. - * @returns A Promise that resolves with the new length of the list after pushing. - */ - public async rPush(key: string, value: any, callingMethod: string): Promise { - if (this.isSharedCacheEnabled) { - this.cacheMethodsCounter.labels(callingMethod, CacheService.cacheTypes.REDIS, CacheService.methods.RPUSH).inc(1); - } else { - this.cacheMethodsCounter.labels(callingMethod, CacheService.cacheTypes.LRU, CacheService.methods.GET).inc(1); - this.cacheMethodsCounter.labels(callingMethod, CacheService.cacheTypes.LRU, CacheService.methods.SET).inc(1); - } - - return await this.client.rPush(key, value, callingMethod); - } - - /** - * Retrieves a range of values from a list in the cache. - * @param key - The key of the list. - * @param start - The start index of the range. - * @param end - The end index of the range. - * @param callingMethod - The name of the calling method. - * @returns A Promise that resolves with the values in the range. - * @template T - The type of the values in the list. - */ - public async lRange(key: string, start: number, end: number, callingMethod: string): Promise { - if (this.isSharedCacheEnabled) { - this.cacheMethodsCounter.labels(callingMethod, CacheService.cacheTypes.REDIS, CacheService.methods.LRANGE).inc(1); - } else { - this.cacheMethodsCounter.labels(callingMethod, CacheService.cacheTypes.LRU, CacheService.methods.GET).inc(1); - } - return await this.client.lRange(key, start, end, callingMethod); - } - - /** - * Retrieves all keys matching the given pattern. - * @param pattern - The pattern to match keys against. - * @param callingMethod - The name of the calling method. - * @returns A Promise that resolves with an array of keys that match the pattern. - */ - async keys(pattern: string, callingMethod: string): Promise { - return await this.client.keys(pattern, callingMethod); - } -} +export { ICacheClient as CacheService } from '../../clients/cache/ICacheClient'; diff --git a/packages/relay/tests/lib/config/hbarSpendingPlanConfigService.spec.ts b/packages/relay/tests/lib/config/hbarSpendingPlanConfigService.spec.ts index 30763607c2..e560225be5 100644 --- a/packages/relay/tests/lib/config/hbarSpendingPlanConfigService.spec.ts +++ b/packages/relay/tests/lib/config/hbarSpendingPlanConfigService.spec.ts @@ -168,9 +168,11 @@ describe('HbarSpendingPlanConfigService', function () { } else { redisClient = undefined; } - cacheService = new CacheService( - CacheClientFactory.create(logger.child({ name: 'cache-service' }), registry, reservedKeys, redisClient as any), + cacheService = CacheClientFactory.create( + logger.child({ name: 'cache-service' }), registry, + reservedKeys, + redisClient as any, ); hbarSpendingPlanRepository = new HbarSpendingPlanRepository( cacheService, diff --git a/packages/relay/tests/lib/eth/eth-helpers.ts b/packages/relay/tests/lib/eth/eth-helpers.ts index 0f12d4fe56..55b26ed3c6 100644 --- a/packages/relay/tests/lib/eth/eth-helpers.ts +++ b/packages/relay/tests/lib/eth/eth-helpers.ts @@ -14,7 +14,6 @@ import { IPAddressHbarSpendingPlanRepository } from '../../../src/lib/db/reposit import { EthImpl } from '../../../src/lib/eth'; import { CacheClientFactory } from '../../../src/lib/factories/cacheClientFactory'; import { CommonService } from '../../../src/lib/services'; -import { CacheService } from '../../../src/lib/services/cacheService/cacheService'; import HAPIService from '../../../src/lib/services/hapiService/hapiService'; import { HbarLimitService } from '../../../src/lib/services/hbarLimitService'; @@ -35,7 +34,7 @@ export function generateEthTestEnv(fixedFeeHistory = false) { ConfigServiceTestHelper.dynamicOverride('ETH_FEE_HISTORY_FIXED', fixedFeeHistory); const logger = pino({ level: 'silent' }); const registry = new Registry(); - const cacheService = new CacheService(CacheClientFactory.create(logger, registry), registry); + const cacheService = CacheClientFactory.create(logger, registry); const mirrorNodeInstance = new MirrorNodeClient( ConfigService.get('MIRROR_NODE_URL'), logger.child({ name: `mirror-node` }), diff --git a/packages/relay/tests/lib/ethGetBlockBy.spec.ts b/packages/relay/tests/lib/ethGetBlockBy.spec.ts index 42d942643e..6536efd0dd 100644 --- a/packages/relay/tests/lib/ethGetBlockBy.spec.ts +++ b/packages/relay/tests/lib/ethGetBlockBy.spec.ts @@ -109,10 +109,7 @@ describe('eth_getBlockBy', async function () { eval: sinon.stub(), quit: sinon.stub().resolves(true), } as any; - cacheService = new CacheService( - CacheClientFactory.create(logger, registry, new Set(), redisClientMock as any), - registry, - ); + cacheService = CacheClientFactory.create(logger, registry, new Set(), redisClientMock as any); // @ts-ignore mirrorNodeInstance = new MirrorNodeClient( diff --git a/packages/relay/tests/lib/hapiService.spec.ts b/packages/relay/tests/lib/hapiService.spec.ts index 354d95100c..14d3321f12 100644 --- a/packages/relay/tests/lib/hapiService.spec.ts +++ b/packages/relay/tests/lib/hapiService.spec.ts @@ -39,7 +39,7 @@ describe('HAPI Service', async function () { this.beforeAll(() => { const duration = constants.HBAR_RATE_LIMIT_DURATION; - cacheService = new CacheService(CacheClientFactory.create(logger, registry), registry); + cacheService = CacheClientFactory.create(logger, registry); const hbarSpendingPlanRepository = new HbarSpendingPlanRepository(cacheService, logger); const evmAddressHbarSpendingPlanRepository = new EvmAddressHbarSpendingPlanRepository(cacheService, logger); diff --git a/packages/relay/tests/lib/mirrorNodeClient.spec.ts b/packages/relay/tests/lib/mirrorNodeClient.spec.ts index 4498fdef7f..80c8b80d44 100644 --- a/packages/relay/tests/lib/mirrorNodeClient.spec.ts +++ b/packages/relay/tests/lib/mirrorNodeClient.spec.ts @@ -40,7 +40,7 @@ describe('MirrorNodeClient', async function () { }, timeout: 20 * 1000, }); - cacheService = new CacheService(CacheClientFactory.create(logger, registry), registry); + cacheService = CacheClientFactory.create(logger, registry); mirrorNodeInstance = new MirrorNodeClient( ConfigService.get('MIRROR_NODE_URL'), logger.child({ name: `mirror-node` }), diff --git a/packages/relay/tests/lib/openrpc.spec.ts b/packages/relay/tests/lib/openrpc.spec.ts index 8d4577d64e..af797d7fec 100644 --- a/packages/relay/tests/lib/openrpc.spec.ts +++ b/packages/relay/tests/lib/openrpc.spec.ts @@ -26,7 +26,6 @@ import { IPAddressHbarSpendingPlanRepository } from '../../src/lib/db/repositori import { EthImpl } from '../../src/lib/eth'; import { CacheClientFactory } from '../../src/lib/factories/cacheClientFactory'; import { NetImpl } from '../../src/lib/net'; -import { CacheService } from '../../src/lib/services/cacheService/cacheService'; import ClientService from '../../src/lib/services/hapiService/hapiService'; import { HbarLimitService } from '../../src/lib/services/hbarLimitService'; import { TxPoolImpl } from '../../src/lib/txpool'; @@ -107,7 +106,7 @@ describe('Open RPC Specification', function () { }); mock = new MockAdapter(instance, { onNoMatch: 'throwException' }); - const cacheService = new CacheService(CacheClientFactory.create(logger, registry), registry); + const cacheService = CacheClientFactory.create(logger, registry); mirrorNodeInstance = new MirrorNodeClient( ConfigService.get('MIRROR_NODE_URL'), logger.child({ name: `mirror-node` }), diff --git a/packages/relay/tests/lib/precheck.spec.ts b/packages/relay/tests/lib/precheck.spec.ts index 53fddd40f3..e3aba892e2 100644 --- a/packages/relay/tests/lib/precheck.spec.ts +++ b/packages/relay/tests/lib/precheck.spec.ts @@ -13,7 +13,6 @@ import { JsonRpcError, predefined } from '../../src'; import { MirrorNodeClient } from '../../src/lib/clients'; import constants from '../../src/lib/constants'; import { Precheck } from '../../src/lib/precheck'; -import { CacheService } from '../../src/lib/services/cacheService/cacheService'; import { blobVersionedHash, contractAddress1, @@ -95,7 +94,7 @@ describe('Precheck', async function () { ConfigService.get('MIRROR_NODE_URL')!, logger.child({ name: `mirror-node` }), registry, - new CacheService(CacheClientFactory.create(logger, registry), registry), + CacheClientFactory.create(logger, registry), instance, ); const transactionPoolService = sinon.createStubInstance(TransactionPoolService); diff --git a/packages/relay/tests/lib/relay.spec.ts b/packages/relay/tests/lib/relay.spec.ts index e4149cf08e..509ae391ce 100644 --- a/packages/relay/tests/lib/relay.spec.ts +++ b/packages/relay/tests/lib/relay.spec.ts @@ -9,7 +9,6 @@ import sinon from 'sinon'; import { Relay } from '../../src'; import { MirrorNodeClient } from '../../src/lib/clients/mirrorNodeClient'; -import { CacheService } from '../../src/lib/services/cacheService/cacheService'; import { overrideEnvsInMochaDescribe, withOverriddenEnvsInMochaTest } from '../helpers'; chai.use(chaiAsPromised); @@ -52,8 +51,6 @@ describe('Relay', () => { let populatePreconfiguredSpendingPlansSpy: sinon.SinonSpy; beforeEach(() => { - // @ts-ignore - CacheService.instances = []; loggerSpy = sinon.spy(logger); populatePreconfiguredSpendingPlansSpy = sinon.spy(Relay.prototype, 'populatePreconfiguredSpendingPlans'); }); diff --git a/packages/relay/tests/lib/repositories/hbarLimiter/evmAddressHbarSpendingPlanRepository.spec.ts b/packages/relay/tests/lib/repositories/hbarLimiter/evmAddressHbarSpendingPlanRepository.spec.ts index 85c89d75e9..35787dc283 100644 --- a/packages/relay/tests/lib/repositories/hbarLimiter/evmAddressHbarSpendingPlanRepository.spec.ts +++ b/packages/relay/tests/lib/repositories/hbarLimiter/evmAddressHbarSpendingPlanRepository.spec.ts @@ -42,7 +42,7 @@ describe('@evmAddressHbarSpendingPlanRepository EvmAddressHbarSpendingPlanReposi } else { redisClient = undefined; } - cacheService = new CacheService(CacheClientFactory.create(logger, registry, new Set(), redisClient), registry); + cacheService = CacheClientFactory.create(logger, registry, new Set(), redisClient); cacheServiceSpy = sinon.spy(cacheService); repository = new EvmAddressHbarSpendingPlanRepository( cacheService, diff --git a/packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts b/packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts index 9c4f470162..4f9803df98 100644 --- a/packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts +++ b/packages/relay/tests/lib/repositories/hbarLimiter/hbarSpendingPlanRepository.spec.ts @@ -45,7 +45,7 @@ describe('HbarSpendingPlanRepository', function () { } else { redisClient = undefined; } - cacheService = new CacheService(CacheClientFactory.create(logger, registry, new Set(), redisClient), registry); + cacheService = CacheClientFactory.create(logger, registry, new Set(), redisClient); cacheServiceSpy = sinon.spy(cacheService); repository = new HbarSpendingPlanRepository(cacheService, logger.child({ name: `HbarSpendingPlanRepository` })); }); diff --git a/packages/relay/tests/lib/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.spec.ts b/packages/relay/tests/lib/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.spec.ts index 49e9d05b28..c143a18f53 100644 --- a/packages/relay/tests/lib/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.spec.ts +++ b/packages/relay/tests/lib/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository.spec.ts @@ -39,7 +39,7 @@ describe('IPAddressHbarSpendingPlanRepository', function () { if (isSharedCacheEnabled) { await RedisClientManager.getClient(logger); } - cacheService = new CacheService(CacheClientFactory.create(logger, registry), registry); + cacheService = CacheClientFactory.create(logger, registry); cacheServiceSpy = sinon.spy(cacheService); repository = new IPAddressHbarSpendingPlanRepository( cacheService, diff --git a/packages/relay/tests/lib/sdkClient.spec.ts b/packages/relay/tests/lib/sdkClient.spec.ts index 673001cce3..e627bde6d7 100644 --- a/packages/relay/tests/lib/sdkClient.spec.ts +++ b/packages/relay/tests/lib/sdkClient.spec.ts @@ -87,7 +87,7 @@ describe('SdkClient', async function () { const hederaNetwork = ConfigService.get('HEDERA_NETWORK')!; const duration = constants.HBAR_RATE_LIMIT_DURATION; - cacheService = new CacheService(CacheClientFactory.create(logger, registry), registry); + cacheService = CacheClientFactory.create(logger, registry); const hbarSpendingPlanRepository = new HbarSpendingPlanRepository(cacheService, logger); const evmAddressHbarSpendingPlanRepository = new EvmAddressHbarSpendingPlanRepository(cacheService, logger); const ipAddressHbarSpendingPlanRepository = new IPAddressHbarSpendingPlanRepository(cacheService, logger); diff --git a/packages/relay/tests/lib/services/cacheService/cacheService.spec.ts b/packages/relay/tests/lib/services/cacheService/cacheService.spec.ts index 5ac375ebc2..94c97e7940 100644 --- a/packages/relay/tests/lib/services/cacheService/cacheService.spec.ts +++ b/packages/relay/tests/lib/services/cacheService/cacheService.spec.ts @@ -27,7 +27,7 @@ describe('CacheService Test Suite', async function () { describe('keys', async function () { let internalCacheSpy: sinon.SinonSpiedInstance; before(async () => { - internalCacheSpy = sinon.spy(cacheService['client']); + internalCacheSpy = sinon.spy(cacheService); }); it('should retrieve all keys', async function () { @@ -134,7 +134,7 @@ describe('CacheService Test Suite', async function () { overrideEnvsInMochaDescribe({ REDIS_ENABLED: false }); this.beforeAll(() => { - cacheService = new CacheService(CacheClientFactory.create(logger, registry), registry); + cacheService = CacheClientFactory.create(logger, registry); }); this.afterEach(async () => { @@ -236,7 +236,7 @@ describe('CacheService Test Suite', async function () { describe('should not initialize redis cache if shared cache is not enabled', async function () { it('should not initialize redis cache if shared cache is not enabled', async function () { - expect(cacheService['client']).to.be.an.instanceOf(LocalLRUCache); + expect(cacheService['decorated']).to.be.an.instanceOf(LocalLRUCache); }); }); }); @@ -252,10 +252,7 @@ describe('CacheService Test Suite', async function () { overrideEnvsInMochaDescribe({ MULTI_SET: true }); before(async () => { - cacheService = new CacheService( - CacheClientFactory.create(logger, registry, new Set(), await RedisClientManager.getClient(logger)), - registry, - ); + cacheService = CacheClientFactory.create(logger, registry, new Set(), await RedisClientManager.getClient(logger)); }); this.beforeEach(async () => { @@ -356,7 +353,6 @@ describe('CacheService Test Suite', async function () { it('should be able to ignore clear failure in case of Redis error', async function () { await RedisClientManager.disconnect(); - await expect(cacheService.clear()).to.eventually.not.be.rejected; }); @@ -401,7 +397,6 @@ describe('CacheService Test Suite', async function () { it('should be able to ignore increment failure in case of Redis error', async function () { const key = 'counter'; - const amount = 5; await cacheService.set(key, 10, callingMethod); await RedisClientManager.disconnect(); await expect(cacheService.incrBy(key, 5, callingMethod)).to.eventually.not.be.rejected; diff --git a/packages/relay/tests/lib/services/eth/filter.spec.ts b/packages/relay/tests/lib/services/eth/filter.spec.ts index 09b418de3a..3c42b8a68c 100644 --- a/packages/relay/tests/lib/services/eth/filter.spec.ts +++ b/packages/relay/tests/lib/services/eth/filter.spec.ts @@ -63,7 +63,7 @@ describe('Filter API Test Suite', async function () { }; this.beforeAll(() => { - cacheService = new CacheService(CacheClientFactory.create(logger, registry)); + cacheService = CacheClientFactory.create(logger, registry); mirrorNodeInstance = new MirrorNodeClient( ConfigService.get('MIRROR_NODE_URL'), logger.child({ name: `mirror-node` }), diff --git a/packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts b/packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts index 9855260839..12c50b08e0 100644 --- a/packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts +++ b/packages/relay/tests/lib/services/hbarLimitService/hbarLimitService.spec.ts @@ -59,7 +59,7 @@ describe('HBAR Rate Limit Service', function () { let loggerSpy: sinon.SinonSpiedInstance; beforeEach(async function () { - cacheService = new CacheService(CacheClientFactory.create(logger, register)); + cacheService = CacheClientFactory.create(logger, register); loggerSpy = sinon.spy(logger); hbarSpendingPlanRepository = new HbarSpendingPlanRepository( cacheService, diff --git a/packages/relay/tests/lib/services/metricService/metricService.spec.ts b/packages/relay/tests/lib/services/metricService/metricService.spec.ts index bedadd9cb5..819b1a1f5e 100644 --- a/packages/relay/tests/lib/services/metricService/metricService.spec.ts +++ b/packages/relay/tests/lib/services/metricService/metricService.spec.ts @@ -18,7 +18,6 @@ import { EvmAddressHbarSpendingPlanRepository } from '../../../../src/lib/db/rep import { HbarSpendingPlanRepository } from '../../../../src/lib/db/repositories/hbarLimiter/hbarSpendingPlanRepository'; import { IPAddressHbarSpendingPlanRepository } from '../../../../src/lib/db/repositories/hbarLimiter/ipAddressHbarSpendingPlanRepository'; import { CacheClientFactory } from '../../../../src/lib/factories/cacheClientFactory'; -import { CacheService } from '../../../../src/lib/services/cacheService/cacheService'; import { HbarLimitService } from '../../../../src/lib/services/hbarLimitService'; import MetricService from '../../../../src/lib/services/metricService/metricService'; import { @@ -136,7 +135,7 @@ describe('Metric Service', function () { ConfigService.get('MIRROR_NODE_URL'), logger.child({ name: `mirror-node` }), registry, - new CacheService(CacheClientFactory.create(logger, registry)), + CacheClientFactory.create(logger, registry), instance, ); }); @@ -148,7 +147,7 @@ describe('Metric Service', function () { eventEmitter = new EventEmitter(); - const cacheService = new CacheService(CacheClientFactory.create(logger, registry)); + const cacheService = CacheClientFactory.create(logger, registry); const hbarSpendingPlanRepository = new HbarSpendingPlanRepository(cacheService, logger); const evmAddressHbarSpendingPlanRepository = new EvmAddressHbarSpendingPlanRepository(cacheService, logger); const ipAddressHbarSpendingPlanRepository = new IPAddressHbarSpendingPlanRepository(cacheService, logger); diff --git a/packages/server/tests/acceptance/cacheService.spec.ts b/packages/server/tests/acceptance/cacheService.spec.ts index 5acd6d5fce..dcfe20ead0 100644 --- a/packages/server/tests/acceptance/cacheService.spec.ts +++ b/packages/server/tests/acceptance/cacheService.spec.ts @@ -27,24 +27,23 @@ describe('@cache-service Acceptance Tests for shared cache', function () { logger = pino({ level: 'silent' }); redisClient = await RedisClientManager.getClient(logger); - cacheService = new CacheService(CacheClientFactory.create(logger, undefined, undefined, redisClient)); + cacheService = CacheClientFactory.create(logger, undefined, undefined, redisClient); await new Promise((r) => setTimeout(r, 1000)); }); it('Correctly performs set, get and delete operations', async () => { const dataLabel = `${DATA_LABEL_PREFIX}1`; - const setSharedCacheSpy = sinon.spy(cacheService['client'], 'set'); - const getSharedCacheSpy = sinon.spy(cacheService['client'], 'get'); - const deleteSharedCacheSpy = sinon.spy(cacheService['client'], 'delete'); + const setSharedCacheSpy = sinon.spy(cacheService, 'set'); + const getSharedCacheSpy = sinon.spy(cacheService, 'getAsync'); + const deleteSharedCacheSpy = sinon.spy(cacheService, 'delete'); await cacheService.set(dataLabel, DATA, CALLING_METHOD); await new Promise((r) => setTimeout(r, 200)); const cache = await cacheService.getAsync(dataLabel, CALLING_METHOD); expect(cache).to.deep.eq(DATA, 'set method saves to shared cache'); - expect(cacheService['isSharedCacheEnabled']).to.be.true; - expect(cacheService['client']).to.be.instanceOf(RedisCache); + expect(cacheService['decorated']).to.be.instanceOf(RedisCache); const cacheFromService = await cacheService.getAsync(dataLabel, CALLING_METHOD); expect(cacheFromService).to.deep.eq(DATA, 'getAsync method reads correctly from shared cache'); @@ -86,7 +85,7 @@ describe('@cache-service Acceptance Tests for shared cache', function () { it('Falls back to local cache for REDIS_ENABLED !== true', async () => { const dataLabel = `${DATA_LABEL_PREFIX}3`; - const serviceWithDisabledRedis = new CacheService(CacheClientFactory.create(logger)); + const serviceWithDisabledRedis = CacheClientFactory.create(logger); const isRedisEnabled = ConfigService.get('REDIS_ENABLED') && !!ConfigService.get('REDIS_URL'); await new Promise((r) => setTimeout(r, 1000)); expect(isRedisEnabled).to.eq(false); @@ -100,7 +99,7 @@ describe('@cache-service Acceptance Tests for shared cache', function () { it('Cache set by one instance can be accessed by another', async () => { const dataLabel = `${DATA_LABEL_PREFIX}4`; - const otherServiceInstance = new CacheService(CacheClientFactory.create(logger, undefined, undefined, redisClient)); + const otherServiceInstance = CacheClientFactory.create(logger, undefined, undefined, redisClient); await cacheService.set(dataLabel, DATA, CALLING_METHOD); await new Promise((r) => setTimeout(r, 200)); diff --git a/packages/server/tests/acceptance/hbarLimiter.spec.ts b/packages/server/tests/acceptance/hbarLimiter.spec.ts index c643605571..25d8dc3c2e 100644 --- a/packages/server/tests/acceptance/hbarLimiter.spec.ts +++ b/packages/server/tests/acceptance/hbarLimiter.spec.ts @@ -87,9 +87,11 @@ describe('@hbarlimiter HBAR Limiter Acceptance Tests', function () { const register = new Registry(); const reservedKeys = HbarSpendingPlanConfigService.getPreconfiguredSpendingPlanKeys(logger); - cacheService = new CacheService( - CacheClientFactory.create(logger.child({ name: 'cache-service' }), register, reservedKeys, redisClient), + cacheService = CacheClientFactory.create( + logger.child({ name: 'cache-service' }), register, + reservedKeys, + redisClient, ); evmAddressSpendingPlanRepository = new EvmAddressHbarSpendingPlanRepository(cacheService, logger); diff --git a/packages/server/tests/integration/server.spec.ts b/packages/server/tests/integration/server.spec.ts index 66840c5fc1..1d3df028d8 100644 --- a/packages/server/tests/integration/server.spec.ts +++ b/packages/server/tests/integration/server.spec.ts @@ -8,7 +8,6 @@ import { predefined, Relay } from '@hashgraph/json-rpc-relay'; import { MirrorNodeClient } from '@hashgraph/json-rpc-relay/dist/lib/clients'; import { TracerType } from '@hashgraph/json-rpc-relay/dist/lib/constants'; import { DebugImpl } from '@hashgraph/json-rpc-relay/dist/lib/debug'; -import { CacheService } from '@hashgraph/json-rpc-relay/dist/lib/services/cacheService/cacheService'; import { Constants, TYPES } from '@hashgraph/json-rpc-relay/dist/lib/validators'; import serverTestConstants from '../helpers/constants'; @@ -24,6 +23,8 @@ import { GCProfiler } from 'v8'; chai.use(chaiAsPromised); +import { MeasurableCache } from '@hashgraph/json-rpc-relay/dist/lib/clients/cache/measurableCache'; + import { contractAddress1, contractAddress2, @@ -3048,8 +3049,8 @@ describe('RPC Server', function () { }); callTracer = sinon.stub(DebugImpl.prototype, 'callTracer').resolves(callTracerResult); prestateTracer = sinon.stub(DebugImpl.prototype, 'prestateTracer').resolves(prestateTracerResult); - cacheGetAsync = sinon.stub(CacheService.prototype, 'getAsync').resolves(null); - cacheSet = sinon.stub(CacheService.prototype, 'set').resolves(); + cacheGetAsync = sinon.stub(MeasurableCache.prototype, 'getAsync').resolves(null); + cacheSet = sinon.stub(MeasurableCache.prototype, 'set').resolves(); requireDebugAPIEnabled = sinon.stub(DebugImpl, 'requireDebugAPIEnabled').returns(); });