diff --git a/src/modules/batcher/batcher.service.ts b/src/modules/batcher/batcher.service.ts index 825cd577..b3120034 100644 --- a/src/modules/batcher/batcher.service.ts +++ b/src/modules/batcher/batcher.service.ts @@ -5,7 +5,7 @@ import { Logger } from "@/core/logger"; import { ExecutorService } from "@/executor"; import { gasEstimatorConfig } from "@/gas-estimator/gas-estimator.config"; import { SimulatorService } from "@/simulator"; -import { StorageService } from "@/storage"; +import { DEFAULT_GLOBAL_EXPIRATION_TIME, StorageService } from "@/storage"; import { type MeeUserOpBatch, getOverrideOrDefault, @@ -144,6 +144,8 @@ export class BatcherService { meeUserOpHash, "error", "Invalid maxGasLimit", + // 15 days expiration + { ttl: DEFAULT_GLOBAL_EXPIRATION_TIME }, ); continue; diff --git a/src/modules/entry-point/entry-point.service.ts b/src/modules/entry-point/entry-point.service.ts index e40c2074..77cff7fc 100644 --- a/src/modules/entry-point/entry-point.service.ts +++ b/src/modules/entry-point/entry-point.service.ts @@ -9,7 +9,7 @@ import { ContractsService } from "@/contracts"; import { Logger } from "@/core/logger"; import { NodeService } from "@/node"; import { RpcManagerService } from "@/rpc-manager"; -import { StorageService } from "@/storage"; +import { DEFAULT_GLOBAL_EXPIRATION_TIME, StorageService } from "@/storage"; import { type EIP7702Auth, type PackedUserOp, @@ -672,9 +672,14 @@ export class EntryPointService { if (options.useStorage) { // Update error in background for improved latency - this.storageService.updateUserOpCustomFields(meeUserOpHash, { - revertReason: sanitizeUrl(errorMessage), - }); + this.storageService.updateUserOpCustomFields( + meeUserOpHash, + { + revertReason: sanitizeUrl(errorMessage), + }, + // 15 days expiration + { ttl: DEFAULT_GLOBAL_EXPIRATION_TIME }, + ); } // If there is no retries left and err exists, it will return true with error diff --git a/src/modules/executor/executor.processor.ts b/src/modules/executor/executor.processor.ts index 4e545322..45938b08 100644 --- a/src/modules/executor/executor.processor.ts +++ b/src/modules/executor/executor.processor.ts @@ -16,7 +16,7 @@ import { NODE_ACCOUNT_TOKEN, type NodeAccount } from "@/node"; import { NonceManagerService } from "@/nonce-manager"; import { RpcManagerService } from "@/rpc-manager"; import { trackSimulationTransactionData } from "@/simulator/utils"; -import { StorageService } from "@/storage"; +import { DEFAULT_GLOBAL_EXPIRATION_TIME, StorageService } from "@/storage"; import { type EIP7702Auth, type SignedPackedMeeUserOp, @@ -196,6 +196,8 @@ export class ExecutorProcessor implements Processor { // meeUserOpHash, // "batchHash", // batchHash, + // // 15 days expiration + // { ttl: DEFAULT_GLOBAL_EXPIRATION_TIME }, // ), // { // chainId, @@ -263,6 +265,8 @@ export class ExecutorProcessor implements Processor { executionStartedAt: currentTime, executionFinishedAt: currentTime, }, + // 15 days expiration + { ttl: DEFAULT_GLOBAL_EXPIRATION_TIME }, ); }), ).catch((error) => { @@ -337,6 +341,8 @@ export class ExecutorProcessor implements Processor { meeUserOpHash, "executionStartedAt", currentTime, + // 15 days expiration + { ttl: DEFAULT_GLOBAL_EXPIRATION_TIME }, ); meeUserOpHashes.push(meeUserOpHash); @@ -585,10 +591,14 @@ export class ExecutorProcessor implements Processor { if (isLastExecutionAttempt) { for (const meeUserOp of meeUserOps) { const { meeUserOpHash } = meeUserOp; - this.storageService.updateUserOpCustomFields(meeUserOpHash, { - error: sanitizeUrl(errorMessage), - executionFinishedAt: Date.now(), - }); + this.storageService.updateUserOpCustomFields( + meeUserOpHash, + { + error: sanitizeUrl(errorMessage), + executionFinishedAt: Date.now(), + }, // 15 days expiration + { ttl: DEFAULT_GLOBAL_EXPIRATION_TIME }, + ); // Tracking in background to improve latency trackSimulationTransactionData(meeUserOp); @@ -1212,14 +1222,18 @@ export class ExecutorProcessor implements Processor { for (const { meeUserOpHash } of meeUserOps) { // Update storage in background to improve latency // TODO: Add redis transaction later - this.storageService.updateUserOpCustomFields(meeUserOpHash, { - txHash, - isConfirmed, - confirmations: isConfirmed - ? BigInt(this.chainsService.chainSettings.waitConfirmations) - : 1n, - executionFinishedAt: Date.now(), - }); + this.storageService.updateUserOpCustomFields( + meeUserOpHash, + { + txHash, + isConfirmed, + confirmations: isConfirmed + ? BigInt(this.chainsService.chainSettings.waitConfirmations) + : 1n, + executionFinishedAt: Date.now(), + }, // 15 days expiration + { ttl: DEFAULT_GLOBAL_EXPIRATION_TIME }, + ); } } else { const events = parseEventLogs({ @@ -1264,6 +1278,8 @@ export class ExecutorProcessor implements Processor { error: "UserOperation reverted", }), }, + // 15 days expiration + { ttl: DEFAULT_GLOBAL_EXPIRATION_TIME }, ); if (!success) { @@ -1310,15 +1326,19 @@ export class ExecutorProcessor implements Processor { ); // Update storage in background to improve latency - this.storageService.updateUserOpCustomFields(meeUserOpHash, { - txHash, - isConfirmed, - confirmations: isConfirmed - ? BigInt(this.chainsService.chainSettings.waitConfirmations) - : 1n, - executionFinishedAt: Date.now(), - error: "Failed to execute userOp", - }); + this.storageService.updateUserOpCustomFields( + meeUserOpHash, + { + txHash, + isConfirmed, + confirmations: isConfirmed + ? BigInt(this.chainsService.chainSettings.waitConfirmations) + : 1n, + executionFinishedAt: Date.now(), + error: "Failed to execute userOp", + }, // 15 days expiration + { ttl: DEFAULT_GLOBAL_EXPIRATION_TIME }, + ); } } @@ -1338,6 +1358,8 @@ export class ExecutorProcessor implements Processor { assetTransfers, }, }, + // 15 days expiration + { ttl: DEFAULT_GLOBAL_EXPIRATION_TIME }, ); } } @@ -1649,10 +1671,14 @@ export class ExecutorProcessor implements Processor { // Reverted userOps is marked as failure for explorer for (const { meeUserOpHash } of invalidMeeUserOps) { // Redis is updated in background to achieve minimal latency - this.storageService.updateUserOpCustomFields(meeUserOpHash, { - error: "UserOperation reverted", - executionFinishedAt: Date.now(), - }); + this.storageService.updateUserOpCustomFields( + meeUserOpHash, + { + error: "UserOperation reverted", + executionFinishedAt: Date.now(), + }, // 15 days expiration + { ttl: DEFAULT_GLOBAL_EXPIRATION_TIME }, + ); } this.logger.trace( diff --git a/src/modules/quotes/quotes.service.ts b/src/modules/quotes/quotes.service.ts index 578c8f4b..715de9c4 100644 --- a/src/modules/quotes/quotes.service.ts +++ b/src/modules/quotes/quotes.service.ts @@ -32,7 +32,7 @@ import { SimulationService, SimulatorService, } from "@/simulator"; -import { StorageService } from "@/storage"; +import { DEFAULT_GLOBAL_EXPIRATION_TIME, StorageService } from "@/storage"; import { type SignedPackedMeeUserOp, UserOpService, @@ -1492,10 +1492,14 @@ export class QuotesService { } await withTrace("exec.createQuote", async () => { - return await this.storageService.createQuote({ - ...options, - userOps: signedPackedMeeUserOps, - }); + return await this.storageService.createQuote( + { + ...options, + userOps: signedPackedMeeUserOps, + }, + // 15 days expiration + { ttl: DEFAULT_GLOBAL_EXPIRATION_TIME }, + ); })(); // grouping the userOps based on chainIds @@ -1560,6 +1564,8 @@ export class QuotesService { isConfirmed: true, isExecutionSkipped: true, }, + // 15 days expiration + { ttl: DEFAULT_GLOBAL_EXPIRATION_TIME }, ); } @@ -1714,6 +1720,8 @@ export class QuotesService { { confirmations, }, + // 15 days expiration + { ttl: DEFAULT_GLOBAL_EXPIRATION_TIME }, ); } catch (error) { txReceiptError.isError = true; @@ -1736,6 +1744,8 @@ export class QuotesService { { error: errorMessage, }, + // 15 days expiration + { ttl: DEFAULT_GLOBAL_EXPIRATION_TIME }, ); } } diff --git a/src/modules/quotes/utils/calculate-orchestration-fee.ts b/src/modules/quotes/utils/calculate-orchestration-fee.ts index ca3ff2cf..2f230380 100644 --- a/src/modules/quotes/utils/calculate-orchestration-fee.ts +++ b/src/modules/quotes/utils/calculate-orchestration-fee.ts @@ -26,7 +26,7 @@ function calculateOrchestrationFee( chainsCount: 0, isComposable: false, totalWindowSize: 0, - totalOrchestrationFee: 0.01, // fixed 1c orch fee; leaving impl below for future reference. + totalOrchestrationFee: 0.0, // Orch fees is set to zero for now }; /** ORIGINAL IMPLEMENTATION */ // try { diff --git a/src/modules/simulator/simulator.processor.ts b/src/modules/simulator/simulator.processor.ts index 977c7561..b68a23f8 100644 --- a/src/modules/simulator/simulator.processor.ts +++ b/src/modules/simulator/simulator.processor.ts @@ -8,7 +8,7 @@ import { import { Logger } from "@/core/logger"; import { type Processor, UnrecoverableError } from "@/core/queue"; import { type AccountValidationData, EntryPointService } from "@/entry-point"; -import { StorageService } from "@/storage"; +import { DEFAULT_GLOBAL_EXPIRATION_TIME, StorageService } from "@/storage"; import { USEROP_SAFE_WINDOW_BEFORE_EXEC_END } from "@/user-ops/userop.config"; import { Service } from "typedi"; import { type SimulatorJob } from "./interfaces"; @@ -41,6 +41,8 @@ export class SimulatorProcessor implements Processor { meeUserOpHash, "simulationStartedAt", Date.now(), + // 15 days expiration + { ttl: DEFAULT_GLOBAL_EXPIRATION_TIME }, ); let accountValidationData: AccountValidationData; @@ -119,6 +121,8 @@ export class SimulatorProcessor implements Processor { sanitizeUrl( (err as Error).message || "UserOp simulation validation failed", ), + // 15 days expiration + { ttl: DEFAULT_GLOBAL_EXPIRATION_TIME }, ); // Tracking in background to improve latency @@ -147,6 +151,8 @@ export class SimulatorProcessor implements Processor { meeUserOpHash, "error", "Invalid signature", + // 15 days expiration + { ttl: DEFAULT_GLOBAL_EXPIRATION_TIME }, ); // Tracking in background to improve latency @@ -199,6 +205,8 @@ export class SimulatorProcessor implements Processor { meeUserOpHash, "error", "Execution deadline limit exceeded", + // 15 days expiration + { ttl: DEFAULT_GLOBAL_EXPIRATION_TIME }, ); // Tracking in background to improve latency @@ -311,6 +319,8 @@ export class SimulatorProcessor implements Processor { meeUserOpHash, "error", "Execution deadline limit exceeded", + // 15 days expiration + { ttl: DEFAULT_GLOBAL_EXPIRATION_TIME }, ); // Tracking in background to improve latency @@ -365,10 +375,14 @@ export class SimulatorProcessor implements Processor { } // Updating userOp in background to improve latency - this.storageService.updateUserOpCustomFields(meeUserOpHash, { - simulationAttempts, - simulationFinishedAt: Date.now(), - }); + this.storageService.updateUserOpCustomFields( + meeUserOpHash, + { + simulationAttempts, + simulationFinishedAt: Date.now(), + }, // 15 days expiration + { ttl: DEFAULT_GLOBAL_EXPIRATION_TIME }, + ); this.logger.trace( { diff --git a/src/modules/simulator/utils/track-simulation-transaction-data.ts b/src/modules/simulator/utils/track-simulation-transaction-data.ts index 1a569509..d55e2ea4 100644 --- a/src/modules/simulator/utils/track-simulation-transaction-data.ts +++ b/src/modules/simulator/utils/track-simulation-transaction-data.ts @@ -1,4 +1,4 @@ -import { StorageService } from "@/storage"; +import { DEFAULT_GLOBAL_EXPIRATION_TIME, StorageService } from "@/storage"; import { SignedPackedMeeUserOp } from "@/user-ops"; import Container from "typedi"; import { getUserOpTransactionData } from "./get-simulation-transaction-data"; @@ -17,5 +17,7 @@ export const trackSimulationTransactionData = async ( meeUserOpHash, "simulationTransactionData", txData, + // 15 days expiration + { ttl: DEFAULT_GLOBAL_EXPIRATION_TIME }, ); }; diff --git a/src/modules/storage/constants.ts b/src/modules/storage/constants.ts new file mode 100644 index 00000000..66e270a7 --- /dev/null +++ b/src/modules/storage/constants.ts @@ -0,0 +1,2 @@ +// 15 days +export const DEFAULT_GLOBAL_EXPIRATION_TIME = 60 * 60 * 24 * 15; diff --git a/src/modules/storage/index.ts b/src/modules/storage/index.ts index 0bda1952..9ef06f4a 100644 --- a/src/modules/storage/index.ts +++ b/src/modules/storage/index.ts @@ -1 +1,2 @@ export * from "./storage.service"; +export * from "./constants"; diff --git a/src/modules/storage/storage.service.ts b/src/modules/storage/storage.service.ts index 99531eda..37c5cf34 100644 --- a/src/modules/storage/storage.service.ts +++ b/src/modules/storage/storage.service.ts @@ -75,7 +75,13 @@ export class StorageService { return quote; } - async createQuote(quote: QuoteEntity) { + async createQuote( + quote: QuoteEntity, + options: { + ttl?: number; + } = {}, + ) { + const { ttl } = options; const { hash } = quote; const { userOps, ...data } = quote; @@ -83,9 +89,13 @@ export class StorageService { return false; // should never happen } - let multi = this.redis - .multi() - .set(buildRedisKey("quote", hash, "data"), SafeJSON.stringify(data)); + const quoteKey = buildRedisKey("quote", hash, "data"); + + let multi = this.redis.multi().set(quoteKey, SafeJSON.stringify(data)); + + if (ttl) { + multi = multi.expire(quoteKey, ttl); + } const userOpHashes: Hex[] = []; @@ -94,16 +104,22 @@ export class StorageService { userOpHashes.push(meeUserOpHash); - multi = multi.set( - buildRedisKey("user-op", meeUserOpHash, "data"), - SafeJSON.stringify(userOp), - ); + const userOpKey = buildRedisKey("user-op", meeUserOpHash, "data"); + + multi = multi.set(userOpKey, SafeJSON.stringify(userOp)); + + if (ttl) { + multi = multi.expire(userOpKey, ttl); + } } - multi = multi.rpush( - buildRedisKey("quote", hash, "user-ops"), - ...userOpHashes, - ); + const quoteUserOpKey = buildRedisKey("quote", hash, "user-ops"); + + multi = multi.rpush(quoteUserOpKey, ...userOpHashes); + + if (ttl) { + multi = multi.expire(quoteUserOpKey, ttl); + } const res = await multi.exec(); @@ -128,30 +144,47 @@ export class StorageService { hash: Hex, name: Name, value: Exclude, + options: { + ttl?: number; + } = {}, ) { - const res = await this.redis.hsetnx( - buildRedisKey("user-op", hash, "custom-fields"), - name, - SafeJSON.stringify(value), - ); + const { ttl } = options; - return !!res; + const key = buildRedisKey("user-op", hash, "custom-fields"); + + let multi = await this.redis + .multi() + .hsetnx(key, name, SafeJSON.stringify(value)); + + if (ttl) { + multi = multi.expire(key, ttl); + } + + const res = await multi.exec(); + + return !!res?.every(([err, res]) => !err && !!res); // should always be true } async setUserOpCustomField( hash: Hex, name: Name, value: Exclude, + options: { + ttl?: number; + } = {}, ) { + const { ttl } = options; const key = buildRedisKey("user-op", hash, "custom-fields"); const raw = SafeJSON.stringify(value); - const res = await this.redis - .multi() - .hsetnx(key, name, raw) - .hget(key, name) - .exec(); + let multi = await this.redis.multi().hsetnx(key, name, raw).hget(key, name); + + if (ttl) { + multi = multi.expire(key, ttl); + } + + const res = await multi.exec(); if (!res) { return false; @@ -182,13 +215,26 @@ export class StorageService { async updateUserOpCustomFields( hash: Hex, customFields: UserOpEntityCustomFields, + options: { + ttl?: number; + } = {}, ) { - const res = await this.redis.hset( - buildRedisKey("user-op", hash, "custom-fields"), + const { ttl } = options; + + const key = buildRedisKey("user-op", hash, "custom-fields"); + + let multi = await this.redis.multi().hset( + key, mapValues(customFields, (value) => SafeJSON.stringify(value)), ); - return !!res; + if (ttl) { + multi = multi.expire(key, ttl); + } + + const res = await multi.exec(); + + return !!res?.every(([err, res]) => !err && !!res); // should always be true } private get redis() { @@ -233,7 +279,7 @@ export class StorageService { const res = await multi.exec(); - return !!res; + return !!res?.every(([err, res]) => !err && !!res); // should always be true } catch (err) { return false; }