diff --git a/backend/src/api/bitcoin/bitcoin.routes.ts b/backend/src/api/bitcoin/bitcoin.routes.ts index 90a31ecae..103158ad5 100644 --- a/backend/src/api/bitcoin/bitcoin.routes.ts +++ b/backend/src/api/bitcoin/bitcoin.routes.ts @@ -6,7 +6,7 @@ import websocketHandler from '../websocket-handler'; import mempool from '../mempool'; import feeApi from '../fee-api'; import mempoolBlocks from '../mempool-blocks'; -import bitcoinApi from './bitcoin-api-factory'; +import bitcoinApi, { bitcoinCoreApi } from './bitcoin-api-factory'; import { Common } from '../common'; import backendInfo from '../backend-info'; import transactionUtils from '../transaction-utils'; @@ -19,6 +19,7 @@ import bitcoinClient from './bitcoin-client'; import difficultyAdjustment from '../difficulty-adjustment'; import transactionRepository from '../../repositories/TransactionRepository'; import rbfCache from '../rbf-cache'; +import BlocksRepository from '../../repositories/BlocksRepository'; class BitcoinRoutes { public initRoutes(app: Application) { @@ -126,8 +127,53 @@ class BitcoinRoutes { .get(config.MEMPOOL.API_URL_PREFIX + 'address-prefix/:prefix', this.getAddressPrefix) ; } + + app.get(config.MEMPOOL.API_URL_PREFIX + 'health', this.generateHealthReport) } + private async generateHealthReport(req: Request, res: Response) { + try { + // Bitcoin Core + const bitcoinCoreBlockHeight = await bitcoinCoreApi.$getBlockHeightTip(); + const bitcoinCoreIndexes = await bitcoinClient.getIndexInfo(); + // Esplora + const esploraBlockHeight = await bitcoinApi.$getBlockHeightTip(); + // Mempool + const mempoolBlockHeight = blocks.getCurrentBlockHeight(); + const indexedBlockCount = await BlocksRepository.$getIndexedBlockCount(); + const indexedBlockWithCpfpCount = await BlocksRepository.$getIndexedCpfpBlockCount(); + const indexedBlockWithCoinStatsCount = await BlocksRepository.$getIndexedCoinStatsBlockCount(); + + const response = { + core: { + height: bitcoinCoreBlockHeight, + }, + esplora: { + height: esploraBlockHeight + }, + mempool: { + height: mempoolBlockHeight, + indexing: { + indexedBlockCount: indexedBlockCount, + indexedBlockCountWithCPFP: indexedBlockWithCpfpCount, + indexedBlockCountWithCoinStats: indexedBlockWithCoinStatsCount, + } + } + }; + + // Bitcoin Core indexes + for (const indexName in bitcoinCoreIndexes) { + response.core[indexName] = bitcoinCoreIndexes[indexName]; + } + + res.json(response); + + } catch (e: any) { + logger.err(`Unable to generate health report. Exception: ${JSON.stringify(e)}`); + logger.err(e.stack); + res.status(500).send(e instanceof Error ? e.message : e); + } + } private getInitData(req: Request, res: Response) { try { diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 0953f9b84..ad1aad44d 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -385,7 +385,7 @@ class BlocksRepository { /** * Get blocks count for a period */ - public async $blockCountBetweenHeight(startHeight: number, endHeight: number): Promise { + public async $blockCountBetweenHeight(startHeight: number, endHeight: number): Promise { const params: any[] = []; let query = `SELECT count(height) as blockCount FROM blocks @@ -721,7 +721,7 @@ class BlocksRepository { /** * Get the historical averaged block fee rate percentiles */ - public async $getHistoricalBlockFeeRates(div: number, interval: string | null): Promise { + public async $getHistoricalBlockFeeRates(div: number, interval: string | null): Promise { try { let query = `SELECT CAST(AVG(height) as INT) as avgHeight, @@ -752,7 +752,7 @@ class BlocksRepository { /** * Get the historical averaged block sizes */ - public async $getHistoricalBlockSizes(div: number, interval: string | null): Promise { + public async $getHistoricalBlockSizes(div: number, interval: string | null): Promise { try { let query = `SELECT CAST(AVG(height) as INT) as avgHeight, @@ -777,7 +777,7 @@ class BlocksRepository { /** * Get the historical averaged block weights */ - public async $getHistoricalBlockWeights(div: number, interval: string | null): Promise { + public async $getHistoricalBlockWeights(div: number, interval: string | null): Promise { try { let query = `SELECT CAST(AVG(height) as INT) as avgHeight, @@ -815,7 +815,7 @@ class BlocksRepository { /** * Get a list of blocks that have not had CPFP data indexed */ - public async $getCPFPUnindexedBlocks(): Promise { + public async $getCPFPUnindexedBlocks(): Promise { try { const blockchainInfo = await bitcoinClient.getBlockchainInfo(); const currentBlockHeight = blockchainInfo.blocks; @@ -885,7 +885,7 @@ class BlocksRepository { /** * Save block price by batch */ - public async $saveBlockPrices(blockPrices: BlockPrice[]): Promise { + public async $saveBlockPrices(blockPrices: BlockPrice[]): Promise { try { let query = `INSERT INTO blocks_prices(height, price_id) VALUES`; for (const price of blockPrices) { @@ -1061,6 +1061,57 @@ class BlocksRepository { blk.extras = extras; return blk; } + + /** + * Count how many blocks are indexed + */ + public async $getIndexedBlockCount(): Promise { + try { + const [res]: any[] = await DB.query(`SELECT COUNT(hash) as count FROM blocks`); + if (!res || !res.length) { + logger.err(`Unable to count indexed blocks in our db`); + return -1; + } + return res[0].count; + } catch (e) { + logger.err(`Unable to count indexed blocks in our db. Exception: ${JSON.stringify(e)}`); + return -1; + } + } + + /** + * Count how many blocks are indexed with CPFP data + */ + public async $getIndexedCpfpBlockCount(): Promise { + try { + const [res]: any[] = await DB.query(`SELECT COUNT(DISTINCT height) as count FROM compact_cpfp_clusters`); + if (!res || !res.length) { + logger.err(`Unable to count indexed blocks with CPFP data in our db`); + return -1; + } + return res[0].count; + } catch (e) { + logger.err(`Unable to count indexed blocks with CPFP data in our db. Exception: ${JSON.stringify(e)}`); + return -1; + } + } + + /** + * Count how many blocks are indexed with coin stats data + */ + public async $getIndexedCoinStatsBlockCount(): Promise { + try { + const [res]: any[] = await DB.query(`SELECT COUNT(hash) as count FROM blocks WHERE utxoset_size IS NOT NULL && total_input_amt IS NOT NULL`); + if (!res || !res.length) { + logger.err(`Unable to count indexed blocks with coin stats data in our db`); + return -1; + } + return res[0].count; + } catch (e) { + logger.err(`Unable to count indexed blocks with coin stats data in our db. Exception: ${JSON.stringify(e)}`); + return -1; + } + } } export default new BlocksRepository();