From bd0de745e2e1b18bdd56eaf41b5ee306de6baf5c Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 28 Feb 2024 20:43:32 +0000 Subject: [PATCH] Add local acceleration info APIs --- backend/src/api/mining/mining-routes.ts | 51 ++++++++++++++ .../repositories/AccelerationRepository.ts | 66 +++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/backend/src/api/mining/mining-routes.ts b/backend/src/api/mining/mining-routes.ts index bb78de44a..6b9ef7b9f 100644 --- a/backend/src/api/mining/mining-routes.ts +++ b/backend/src/api/mining/mining-routes.ts @@ -8,6 +8,7 @@ import HashratesRepository from '../../repositories/HashratesRepository'; import bitcoinClient from '../bitcoin/bitcoin-client'; import mining from "./mining"; import PricesRepository from '../../repositories/PricesRepository'; +import AccelerationRepository from '../../repositories/AccelerationRepository'; class MiningRoutes { public initRoutes(app: Application) { @@ -34,6 +35,10 @@ class MiningRoutes { .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/:hash', this.$getBlockAudit) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/timestamp/:timestamp', this.$getHeightFromTimestamp) .get(config.MEMPOOL.API_URL_PREFIX + 'historical-price', this.$getHistoricalPrice) + + .get(config.MEMPOOL.API_URL_PREFIX + 'accelerations/pool/:slug', this.$getAccelerationsByPool) + .get(config.MEMPOOL.API_URL_PREFIX + 'accelerations/block/:height', this.$getAccelerationsByHeight) + .get(config.MEMPOOL.API_URL_PREFIX + 'accelerations/recent/:interval', this.$getRecentAccelerations) ; } @@ -352,6 +357,52 @@ class MiningRoutes { res.status(500).send(e instanceof Error ? e.message : e); } } + + private async $getAccelerationsByPool(req: Request, res: Response): Promise { + try { + res.header('Pragma', 'public'); + res.header('Cache-control', 'public'); + res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); + if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid'].includes(config.MEMPOOL.NETWORK)) { + res.status(400).send('Acceleration data is not available.'); + return; + } + res.status(200).send(await AccelerationRepository.$getAccelerationInfo(req.params.slug)); + } catch (e) { + res.status(500).send(e instanceof Error ? e.message : e); + } + } + + private async $getAccelerationsByHeight(req: Request, res: Response): Promise { + try { + res.header('Pragma', 'public'); + res.header('Cache-control', 'public'); + res.setHeader('Expires', new Date(Date.now() + 1000 * 3600 * 24).toUTCString()); + if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid'].includes(config.MEMPOOL.NETWORK)) { + res.status(400).send('Acceleration data is not available.'); + return; + } + const height = req.params.height === undefined ? undefined : parseInt(req.params.height, 10); + res.status(200).send(await AccelerationRepository.$getAccelerationInfo(null, height)); + } catch (e) { + res.status(500).send(e instanceof Error ? e.message : e); + } + } + + private async $getRecentAccelerations(req: Request, res: Response): Promise { + try { + res.header('Pragma', 'public'); + res.header('Cache-control', 'public'); + res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); + if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid'].includes(config.MEMPOOL.NETWORK)) { + res.status(400).send('Acceleration data is not available.'); + return; + } + res.status(200).send(await AccelerationRepository.$getAccelerationInfo(null, null, req.params.interval)); + } catch (e) { + res.status(500).send(e instanceof Error ? e.message : e); + } + } } export default new MiningRoutes(); diff --git a/backend/src/repositories/AccelerationRepository.ts b/backend/src/repositories/AccelerationRepository.ts index 9fbed4673..868f8526f 100644 --- a/backend/src/repositories/AccelerationRepository.ts +++ b/backend/src/repositories/AccelerationRepository.ts @@ -3,6 +3,22 @@ import { ResultSetHeader, RowDataPacket } from 'mysql2'; import DB from '../database'; import logger from '../logger'; import { IEsploraApi } from '../api/bitcoin/esplora-api.interface'; +import { Common } from '../api/common'; +import config from '../config'; + +export interface PublicAcceleration { + txid: string, + height: number, + pool: { + id: number, + slug: string, + name: string, + }, + effective_vsize: number, + effective_fee: number, + boost_rate: number, + boost_cost: number, +} class AccelerationRepository { public async $saveAcceleration(acceleration: AccelerationInfo, block: IEsploraApi.Block, pool_id: number): Promise { @@ -38,6 +54,56 @@ class AccelerationRepository { // We don't throw, not a critical issue if we miss some accelerations } } + + public async $getAccelerationInfo(poolSlug: string | null = null, height: number | null = null, interval: string | null = null): Promise { + interval = Common.getSqlInterval(interval); + + if (!config.MEMPOOL_SERVICES.ACCELERATIONS || (interval == null && poolSlug == null && height == null)) { + return []; + } + + let query = ` + SELECT * FROM accelerations + JOIN pools on pools.unique_id = accelerations.pool + `; + let params: any[] = []; + + if (interval) { + query += ` WHERE accelerations.added BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW() `; + } else if (height != null) { + query += ` WHERE accelerations.height = ? `; + params.push(height); + } else if (poolSlug != null) { + query += ` WHERE pools.slug = ? `; + params.push(poolSlug); + } + + query += ` ORDER BY accelerations.added DESC `; + + try { + const [rows] = await DB.query(query, params) as RowDataPacket[][]; + if (rows?.length) { + return rows.map(row => ({ + txid: row.txid, + height: row.height, + pool: { + id: row.id, + slug: row.slug, + name: row.name, + }, + effective_vsize: row.effective_vsize, + effective_fee: row.effective_fee, + boost_rate: row.boost_rate, + boost_cost: row.boost_cost, + })); + } else { + return []; + } + } catch (e) { + logger.err(`Cannot query acceleration info. Reason: ` + (e instanceof Error ? e.message : e)); + throw e; + } + } } export default new AccelerationRepository();