From 58fda7fd958b1d98a3ed6b9b59a6018250e03a12 Mon Sep 17 00:00:00 2001 From: David A <4429761+daywiss@users.noreply.github.com> Date: Fri, 25 Oct 2024 10:37:13 -0400 Subject: [PATCH] feat(indexer-api): expose hubpool balance endpoint (#81) Signed-off-by: david --- packages/indexer-api/package.json | 2 + .../indexer-api/src/controllers/balances.ts | 22 ++++--- packages/indexer-api/src/dtos/balances.dto.ts | 5 +- packages/indexer-api/src/main.ts | 34 ++++++++++ packages/indexer-api/src/routers/balances.ts | 13 ++++ packages/indexer-api/src/routers/index.ts | 1 + packages/indexer-api/src/services/balances.ts | 25 ++++++++ packages/indexer/src/index.ts | 1 + packages/indexer/src/parseEnv.ts | 2 +- packages/indexer/src/redis/index.ts | 4 ++ pnpm-lock.yaml | 63 +++++-------------- 11 files changed, 109 insertions(+), 63 deletions(-) create mode 100644 packages/indexer-api/src/routers/balances.ts create mode 100644 packages/indexer-api/src/services/balances.ts create mode 100644 packages/indexer/src/redis/index.ts diff --git a/packages/indexer-api/package.json b/packages/indexer-api/package.json index 31dbe680..d1ab956e 100644 --- a/packages/indexer-api/package.json +++ b/packages/indexer-api/package.json @@ -22,11 +22,13 @@ "license": "ISC", "dependencies": { "@repo/indexer-database": "workspace:*", + "@repo/indexer": "workspace:*", "@types/express": "^4.17.21", "@types/supertest": "^6.0.2", "body-parser": "^1.20.2", "cors": "^2.8.5", "express": "^4.19.2", + "ioredis": "^5.4.1", "superstruct": "2.0.3-1", "winston": "^3.13.1" }, diff --git a/packages/indexer-api/src/controllers/balances.ts b/packages/indexer-api/src/controllers/balances.ts index 80ce1534..2699c6d1 100644 --- a/packages/indexer-api/src/controllers/balances.ts +++ b/packages/indexer-api/src/controllers/balances.ts @@ -1,30 +1,32 @@ import { Request, Response, NextFunction } from "express"; import * as s from "superstruct"; -import { DepositsService } from "../services/deposits"; +import { BalancesService } from "../services/balances"; import { - HubPoolBalanceParams, + HubPoolBalanceQueryParams, SpokePoolBalanceParams, } from "../dtos/balances.dto"; export class BalancesController { - constructor(private service: DepositsService) {} + constructor(private service: BalancesService) {} - public getHubPoolBalance = async ( + public getHubPoolBalance = ( req: Request, res: Response, next: NextFunction, ) => { - s.assert(req.query, HubPoolBalanceParams); - return 0; + req.query && s.assert(req.query, HubPoolBalanceQueryParams); + this.service + .hubPoolBalance(req.query) + .then((result) => res.json(result)) + .catch((err) => next(err)); }; - public getSpokePoolBalance = async ( + public getSpokePoolBalance = ( req: Request, res: Response, next: NextFunction, ) => { - s.assert(req.query, SpokePoolBalanceParams); - - return 0; + req.query && s.assert(req.query, SpokePoolBalanceParams); + res.json([]); }; } diff --git a/packages/indexer-api/src/dtos/balances.dto.ts b/packages/indexer-api/src/dtos/balances.dto.ts index ac47a466..3c6efa00 100644 --- a/packages/indexer-api/src/dtos/balances.dto.ts +++ b/packages/indexer-api/src/dtos/balances.dto.ts @@ -1,9 +1,8 @@ import * as s from "superstruct"; // query hub pools by chainId? default to 1 if not specified. will leave option in case of testnets? -export const HubPoolBalanceParams = s.object({ - chainId: s.defaulted(s.number(), 1), - l1Token: s.string(), +export const HubPoolBalanceQueryParams = s.object({ + l1Token: s.optional(s.string()), }); // query spokepools by chainId, must specify diff --git a/packages/indexer-api/src/main.ts b/packages/indexer-api/src/main.ts index 76c6211d..4c0f3ad4 100644 --- a/packages/indexer-api/src/main.ts +++ b/packages/indexer-api/src/main.ts @@ -4,7 +4,38 @@ import { createDataSource, DatabaseConfig } from "@repo/indexer-database"; import * as routers from "./routers"; import winston from "winston"; import { type Router } from "express"; +import Redis from "ioredis"; +import * as s from "superstruct"; +import * as Indexer from "@repo/indexer"; +async function initializeRedis( + config: Indexer.RedisConfig, + logger: winston.Logger, +) { + const redis = new Redis({ + ...config, + }); + + return new Promise((resolve, reject) => { + redis.on("ready", () => { + logger.info({ + at: "Indexer-API", + message: "Redis connection established", + config, + }); + resolve(redis); + }); + + redis.on("error", (err) => { + logger.error({ + at: "Indexer-API", + message: "Redis connection failed", + error: err, + }); + reject(err); + }); + }); +} export async function connectToDatabase( databaseConfig: DatabaseConfig, logger: winston.Logger, @@ -52,9 +83,12 @@ export async function Main( const postgresConfig = getPostgresConfig(env); const postgres = await connectToDatabase(postgresConfig, logger); + const redisConfig = Indexer.parseRedisConfig(env); + const redis = await initializeRedis(redisConfig, logger); const allRouters: Record = { deposits: routers.deposits.getRouter(postgres), + balances: routers.balances.getRouter(redis), }; const app = ExpressApp(allRouters); diff --git a/packages/indexer-api/src/routers/balances.ts b/packages/indexer-api/src/routers/balances.ts new file mode 100644 index 00000000..46fe93d4 --- /dev/null +++ b/packages/indexer-api/src/routers/balances.ts @@ -0,0 +1,13 @@ +import { Router } from "express"; +import Redis from "ioredis"; +import { BalancesController } from "../controllers/balances"; +import { BalancesService } from "../services/balances"; + +export function getRouter(redis: Redis): Router { + const router = Router(); + const service = new BalancesService(redis); + const controller = new BalancesController(service); + router.get("/hubpool-balance", controller.getHubPoolBalance); + router.get("/spokepool-balance", controller.getSpokePoolBalance); + return router; +} diff --git a/packages/indexer-api/src/routers/index.ts b/packages/indexer-api/src/routers/index.ts index 35861618..9c449080 100644 --- a/packages/indexer-api/src/routers/index.ts +++ b/packages/indexer-api/src/routers/index.ts @@ -1 +1,2 @@ export * as deposits from "./deposits"; +export * as balances from "./balances"; diff --git a/packages/indexer-api/src/services/balances.ts b/packages/indexer-api/src/services/balances.ts new file mode 100644 index 00000000..0d0b962c --- /dev/null +++ b/packages/indexer-api/src/services/balances.ts @@ -0,0 +1,25 @@ +import assert from "assert"; +import Redis from "ioredis"; +import * as Indexer from "@repo/indexer"; + +export class BalancesService { + hubBalancesCache: Indexer.redis.hubBalancesCache.HubPoolBalanceCache; + constructor(private redis: Redis) { + this.hubBalancesCache = + new Indexer.redis.hubBalancesCache.HubPoolBalanceCache({ + redis, + prefix: "hubBalanceCache", + }); + } + async hubPoolBalance(params?: { + l1Token?: string; + }): Promise { + if (params?.l1Token) { + const balance = await this.hubBalancesCache.get(params.l1Token); + assert(balance, `No hubpoolBalance found for ${params.l1Token}`); + return [balance]; + } else { + return this.hubBalancesCache.getAllL1Tokens(); + } + } +} diff --git a/packages/indexer/src/index.ts b/packages/indexer/src/index.ts index 025a49c4..9ddc565d 100644 --- a/packages/indexer/src/index.ts +++ b/packages/indexer/src/index.ts @@ -1,2 +1,3 @@ export * from "./main"; export * from "./parseEnv"; +export * as redis from "./redis"; diff --git a/packages/indexer/src/parseEnv.ts b/packages/indexer/src/parseEnv.ts index 48a0f089..62b815cb 100644 --- a/packages/indexer/src/parseEnv.ts +++ b/packages/indexer/src/parseEnv.ts @@ -17,7 +17,7 @@ export type ProviderConfig = [providerUrl: string, chainId: number]; export type Env = Record; -function parseRedisConfig(env: Env): RedisConfig { +export function parseRedisConfig(env: Env): RedisConfig { assert(env.REDIS_HOST, "requires REDIS_HOST"); assert(env.REDIS_PORT, "requires REDIS_PORT"); const port = parseNumber(env.REDIS_PORT); diff --git a/packages/indexer/src/redis/index.ts b/packages/indexer/src/redis/index.ts new file mode 100644 index 00000000..8e20b038 --- /dev/null +++ b/packages/indexer/src/redis/index.ts @@ -0,0 +1,4 @@ +export * as bundleLeavesCache from "./bundleLeavesCache"; +export * as hubBalancesCache from "./hubBalancesCache"; +export * as rangeQueryStore from "./rangeQueryStore"; +export * as redisCache from "./redisCache"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e1044983..fe0a45af 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -166,6 +166,9 @@ importers: packages/indexer-api: dependencies: + '@repo/indexer': + specifier: workspace:* + version: link:../indexer '@repo/indexer-database': specifier: workspace:* version: link:../indexer-database @@ -184,6 +187,9 @@ importers: express: specifier: ^4.19.2 version: 4.19.2 + ioredis: + specifier: ^5.4.1 + version: 5.4.1 superstruct: specifier: 2.0.3-1 version: 2.0.3-1 @@ -3590,10 +3596,6 @@ packages: resolution: {integrity: sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==} engines: {node: '>= 0.10.0'} - express@4.21.0: - resolution: {integrity: sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==} - engines: {node: '>= 0.10.0'} - express@4.21.1: resolution: {integrity: sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==} engines: {node: '>= 0.10.0'} @@ -8704,7 +8706,7 @@ snapshots: '@ethereumjs/tx@3.3.2': dependencies: - '@ethereumjs/common': 2.5.0 + '@ethereumjs/common': 2.6.5 ethereumjs-util: 7.1.5 '@ethereumjs/tx@3.5.2': @@ -9244,7 +9246,7 @@ snapshots: dependencies: '@ledgerhq/cryptoassets': 5.53.0 '@ledgerhq/errors': 5.50.0 - '@ledgerhq/hw-transport': 5.26.0 + '@ledgerhq/hw-transport': 5.51.1 bignumber.js: 9.1.2 rlp: 2.2.7 @@ -9261,7 +9263,7 @@ snapshots: dependencies: '@ledgerhq/devices': 5.51.1 '@ledgerhq/errors': 5.50.0 - '@ledgerhq/hw-transport': 5.26.0 + '@ledgerhq/hw-transport': 5.51.1 '@ledgerhq/hw-transport-node-hid-noevents': 5.51.1 '@ledgerhq/logs': 5.50.0 lodash: 4.17.21 @@ -9272,7 +9274,7 @@ snapshots: '@ledgerhq/hw-transport-u2f@5.26.0': dependencies: '@ledgerhq/errors': 5.50.0 - '@ledgerhq/hw-transport': 5.26.0 + '@ledgerhq/hw-transport': 5.51.1 '@ledgerhq/logs': 5.50.0 u2f-api: 0.2.7 @@ -9287,7 +9289,6 @@ snapshots: '@ledgerhq/devices': 5.51.1 '@ledgerhq/errors': 5.50.0 events: 3.3.0 - optional: true '@ledgerhq/logs@5.50.0': {} @@ -10018,7 +10019,7 @@ snapshots: '@types/mkdirp@0.5.2': dependencies: - '@types/node': 22.7.3 + '@types/node': 16.18.104 '@types/mocha@10.0.7': {} @@ -10065,7 +10066,7 @@ snapshots: '@types/resolve@0.0.8': dependencies: - '@types/node': 22.7.3 + '@types/node': 16.18.104 '@types/responselike@1.0.3': dependencies: @@ -13028,42 +13029,6 @@ snapshots: transitivePeerDependencies: - supports-color - express@4.21.0: - dependencies: - accepts: 1.3.8 - array-flatten: 1.1.1 - body-parser: 1.20.3 - content-disposition: 0.5.4 - content-type: 1.0.5 - cookie: 0.6.0 - cookie-signature: 1.0.6 - debug: 2.6.9 - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 1.3.1 - fresh: 0.5.2 - http-errors: 2.0.0 - merge-descriptors: 1.0.3 - methods: 1.1.2 - on-finished: 2.4.1 - parseurl: 1.3.3 - path-to-regexp: 0.1.10 - proxy-addr: 2.0.7 - qs: 6.13.0 - range-parser: 1.2.1 - safe-buffer: 5.2.1 - send: 0.19.0 - serve-static: 1.16.2 - setprototypeof: 1.2.0 - statuses: 2.0.1 - type-is: 1.6.18 - utils-merge: 1.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - express@4.21.1: dependencies: accepts: 1.3.8 @@ -16737,7 +16702,7 @@ snapshots: dependencies: body-parser: 1.20.3 cors: 2.8.5 - express: 4.21.0 + express: 4.21.1 request: 2.88.2 xhr: 2.6.0 transitivePeerDependencies: @@ -18616,7 +18581,7 @@ snapshots: wide-align@1.1.5: dependencies: - string-width: 1.0.2 + string-width: 4.2.3 optional: true widest-line@3.1.0: