From b854c071d00e2ffcb9b92eb4a8afa6934b8aebc6 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 8 Feb 2022 18:28:53 +0900 Subject: [PATCH 01/16] Added `mining/pool/:poolId` and `mining/pool/:poolId/:interval` APIs --- backend/src/api/mining.ts | 47 ++++++++++++++------ backend/src/index.ts | 11 ++++- backend/src/repositories/BlocksRepository.ts | 19 +++++++- backend/src/repositories/PoolsRepository.ts | 17 +++++++ backend/src/routes.ts | 15 +++++++ 5 files changed, 94 insertions(+), 15 deletions(-) diff --git a/backend/src/api/mining.ts b/backend/src/api/mining.ts index c89ea9324..7431dc0b3 100644 --- a/backend/src/api/mining.ts +++ b/backend/src/api/mining.ts @@ -7,23 +7,26 @@ class Mining { constructor() { } + private getSqlInterval(interval: string | null): string | null { + switch (interval) { + case '24h': return '1 DAY'; + case '3d': return '3 DAY'; + case '1w': return '1 WEEK'; + case '1m': return '1 MONTH'; + case '3m': return '3 MONTH'; + case '6m': return '6 MONTH'; + case '1y': return '1 YEAR'; + case '2y': return '2 YEAR'; + case '3y': return '3 YEAR'; + default: return null; + } + } + /** * Generate high level overview of the pool ranks and general stats */ public async $getPoolsStats(interval: string | null) : Promise { - let sqlInterval: string | null = null; - switch (interval) { - case '24h': sqlInterval = '1 DAY'; break; - case '3d': sqlInterval = '3 DAY'; break; - case '1w': sqlInterval = '1 WEEK'; break; - case '1m': sqlInterval = '1 MONTH'; break; - case '3m': sqlInterval = '3 MONTH'; break; - case '6m': sqlInterval = '6 MONTH'; break; - case '1y': sqlInterval = '1 YEAR'; break; - case '2y': sqlInterval = '2 YEAR'; break; - case '3y': sqlInterval = '3 YEAR'; break; - default: sqlInterval = null; break; - } + const sqlInterval = this.getSqlInterval(interval); const poolsStatistics = {}; @@ -64,6 +67,24 @@ class Mining { return poolsStatistics; } + + /** + * Get all mining pool stats for a pool + */ + public async $getPoolStat(interval: string | null, poolId: number): Promise { + const pool = await PoolsRepository.$getPool(poolId); + if (!pool) { + throw new Error("This mining pool does not exist"); + } + + const sqlInterval = this.getSqlInterval(interval); + const blocks = await BlocksRepository.$getBlocksByPool(sqlInterval, poolId); + + return { + pool: pool, + blocks: blocks, + }; + } } export default new Mining(); diff --git a/backend/src/index.ts b/backend/src/index.ts index 07808c98a..b8ca55339 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -256,6 +256,14 @@ class Server { .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/1y', routes.$getStatisticsByTime.bind(routes, '1y')) .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/2y', routes.$getStatisticsByTime.bind(routes, '2y')) .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/3y', routes.$getStatisticsByTime.bind(routes, '3y')) + ; + } + + const indexingAvailable = + ['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) && + config.DATABASE.ENABLED === true; + if (indexingAvailable) { + this.app .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/24h', routes.$getPools.bind(routes, '24h')) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/3d', routes.$getPools.bind(routes, '3d')) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/1w', routes.$getPools.bind(routes, '1w')) @@ -266,7 +274,8 @@ class Server { .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/2y', routes.$getPools.bind(routes, '2y')) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/3y', routes.$getPools.bind(routes, '3y')) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/all', routes.$getPools.bind(routes, 'all')) - ; + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId', routes.$getPool) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/:interval', routes.$getPool); } if (config.BISQ.ENABLED) { diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 18023760f..03545f730 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -135,6 +135,23 @@ class BlocksRepository { return rows[0].blockTimestamp; } + /** + * Get blocks mined by a specific mining pool + */ + public async $getBlocksByPool(interval: string | null, poolId: number): Promise { + const query = ` + SELECT * + FROM blocks + WHERE pool_id = ${poolId}` + + (interval != null ? ` AND blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()` : ``); + + const connection = await DB.pool.getConnection(); + const [rows] = await connection.query(query); + connection.release(); + + return rows; + } + /** * Get one block by height */ @@ -156,4 +173,4 @@ class BlocksRepository { } } -export default new BlocksRepository(); \ No newline at end of file +export default new BlocksRepository(); diff --git a/backend/src/repositories/PoolsRepository.ts b/backend/src/repositories/PoolsRepository.ts index b89725452..50f5268a7 100644 --- a/backend/src/repositories/PoolsRepository.ts +++ b/backend/src/repositories/PoolsRepository.ts @@ -41,6 +41,23 @@ class PoolsRepository { return rows; } + + /** + * Get mining pool statistics for one pool + */ + public async $getPool(poolId: number) : Promise { + const query = ` + SELECT * + FROM pools + WHERE pools.id = ${poolId} + `; + + const connection = await DB.pool.getConnection(); + const [rows] = await connection.query(query); + connection.release(); + + return rows[0]; + } } export default new PoolsRepository(); diff --git a/backend/src/routes.ts b/backend/src/routes.ts index e06177ddd..6811f9190 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -22,6 +22,8 @@ import elementsParser from './api/liquid/elements-parser'; import icons from './api/liquid/icons'; import miningStats from './api/mining'; import axios from 'axios'; +import PoolsRepository from './repositories/PoolsRepository'; +import mining from './api/mining'; class Routes { constructor() {} @@ -533,6 +535,19 @@ class Routes { } } + public async $getPool(req: Request, res: Response) { + try { + const poolId = parseInt(req.params.poolId); + const stats = await mining.$getPoolStat(req.params.interval ?? null, poolId); + res.header('Pragma', 'public'); + res.header('Cache-control', 'public'); + res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); + res.json(stats); + } catch (e) { + res.status(500).send(e instanceof Error ? e.message : e); + } + } + public async $getPools(interval: string, req: Request, res: Response) { try { let stats = await miningStats.$getPoolsStats(interval); From fbda0d8186236eb39783eb22f22e678426635e9b Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 8 Feb 2022 18:56:51 +0900 Subject: [PATCH 02/16] Added /mining/pool/:poolId empty page --- frontend/server.ts | 1 + frontend/src/app/app-routing.module.ts | 13 +++++++++++++ frontend/src/app/app.module.ts | 2 ++ .../src/app/components/pool/pool.component.html | 5 +++++ .../src/app/components/pool/pool.component.scss | 0 frontend/src/app/components/pool/pool.component.ts | 14 ++++++++++++++ 6 files changed, 35 insertions(+) create mode 100644 frontend/src/app/components/pool/pool.component.html create mode 100644 frontend/src/app/components/pool/pool.component.scss create mode 100644 frontend/src/app/components/pool/pool.component.ts diff --git a/frontend/server.ts b/frontend/server.ts index df4ab1294..b6c765588 100644 --- a/frontend/server.ts +++ b/frontend/server.ts @@ -66,6 +66,7 @@ export function app(locale: string): express.Express { server.get('/address/*', getLocalizedSSR(indexHtml)); server.get('/blocks', getLocalizedSSR(indexHtml)); server.get('/mining/pools', getLocalizedSSR(indexHtml)); + server.get('/mining/pool/*', getLocalizedSSR(indexHtml)); server.get('/graphs', getLocalizedSSR(indexHtml)); server.get('/liquid', getLocalizedSSR(indexHtml)); server.get('/liquid/tx/*', getLocalizedSSR(indexHtml)); diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index aaf545206..4018ed64e 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -26,6 +26,7 @@ import { PoolRankingComponent } from './components/pool-ranking/pool-ranking.com import { AssetGroupComponent } from './components/assets/asset-group/asset-group.component'; import { AssetsFeaturedComponent } from './components/assets/assets-featured/assets-featured.component'; import { AssetsComponent } from './components/assets/assets.component'; +import { PoolComponent } from './components/pool/pool.component'; let routes: Routes = [ { @@ -66,6 +67,10 @@ let routes: Routes = [ path: 'mining/pools', component: PoolRankingComponent, }, + { + path: 'mining/pool/:poolId', + component: PoolComponent, + }, { path: 'graphs', component: StatisticsComponent, @@ -154,6 +159,10 @@ let routes: Routes = [ path: 'mining/pools', component: PoolRankingComponent, }, + { + path: 'mining/pool/:poolId', + component: PoolComponent, + }, { path: 'graphs', component: StatisticsComponent, @@ -236,6 +245,10 @@ let routes: Routes = [ path: 'mining/pools', component: PoolRankingComponent, }, + { + path: 'mining/pool/:poolId', + component: PoolComponent, + }, { path: 'graphs', component: StatisticsComponent, diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 97fc16204..20eb2ea03 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -38,6 +38,7 @@ import { TimeSpanComponent } from './components/time-span/time-span.component'; import { SeoService } from './services/seo.service'; import { MempoolGraphComponent } from './components/mempool-graph/mempool-graph.component'; import { PoolRankingComponent } from './components/pool-ranking/pool-ranking.component'; +import { PoolComponent } from './components/pool/pool.component'; import { LbtcPegsGraphComponent } from './components/lbtc-pegs-graph/lbtc-pegs-graph.component'; import { AssetComponent } from './components/asset/asset.component'; import { AssetsComponent } from './components/assets/assets.component'; @@ -96,6 +97,7 @@ import { AssetGroupComponent } from './components/assets/asset-group/asset-group IncomingTransactionsGraphComponent, MempoolGraphComponent, PoolRankingComponent, + PoolComponent, LbtcPegsGraphComponent, AssetComponent, AssetsComponent, diff --git a/frontend/src/app/components/pool/pool.component.html b/frontend/src/app/components/pool/pool.component.html new file mode 100644 index 000000000..62c259ccf --- /dev/null +++ b/frontend/src/app/components/pool/pool.component.html @@ -0,0 +1,5 @@ +
+ + Pool + +
\ No newline at end of file diff --git a/frontend/src/app/components/pool/pool.component.scss b/frontend/src/app/components/pool/pool.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts new file mode 100644 index 000000000..907dcf0fd --- /dev/null +++ b/frontend/src/app/components/pool/pool.component.ts @@ -0,0 +1,14 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-pool', + templateUrl: './pool.component.html', + styleUrls: ['./pool.component.scss'] +}) +export class PoolComponent implements OnInit { + constructor( + ) { } + + ngOnInit(): void { + } +} From a168a223601a44e6063da7a96666a6068d7ebe1e Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 8 Feb 2022 19:04:53 +0900 Subject: [PATCH 03/16] Link PoolRanking page with new pool page --- .../src/app/components/pool-ranking/pool-ranking.component.html | 2 +- frontend/src/app/interfaces/node-api.interface.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/pool-ranking/pool-ranking.component.html b/frontend/src/app/components/pool-ranking/pool-ranking.component.html index 8deb8597a..45707d328 100644 --- a/frontend/src/app/components/pool-ranking/pool-ranking.component.html +++ b/frontend/src/app/components/pool-ranking/pool-ranking.component.html @@ -58,7 +58,7 @@ {{ pool.rank }} - {{ pool.name }} + {{ pool.name }} {{ pool.lastEstimatedHashrate }} {{ miningStats.miningUnits.hashrateUnit }} {{ pool['blockText'] }} {{ pool.emptyBlocks }} ({{ pool.emptyBlockRatio }}%) diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index 373385422..b923d25b7 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -55,7 +55,7 @@ export interface LiquidPegs { export interface ITranslators { [language: string]: string; } export interface SinglePoolStats { - pooldId: number; + poolId: number; name: string; link: string; blockCount: number; From 3f55aabc53e98945272c9e5be82b9d7c05aae707 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Wed, 9 Feb 2022 19:41:05 +0900 Subject: [PATCH 04/16] Mining pool detail page draft PoC --- backend/src/api/common.ts | 15 +++ backend/src/api/mining.ts | 33 ++---- backend/src/index.ts | 4 +- backend/src/mempool.interfaces.ts | 22 ++-- backend/src/repositories/BlocksRepository.ts | 109 +++++++++++++----- backend/src/repositories/PoolsRepository.ts | 33 +++--- backend/src/routes.ts | 21 +++- frontend/src/app/app-routing.module.ts | 12 ++ .../app/components/pool/pool.component.html | 63 +++++++++- .../src/app/components/pool/pool.component.ts | 52 +++++++++ .../src/app/interfaces/node-api.interface.ts | 32 +++-- frontend/src/app/services/api.service.ts | 10 +- 12 files changed, 313 insertions(+), 93 deletions(-) diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index 5e99e870c..c470f6fe7 100644 --- a/backend/src/api/common.ts +++ b/backend/src/api/common.ts @@ -154,4 +154,19 @@ export class Common { }); return parents; } + + static getSqlInterval(interval: string | null): string | null { + switch (interval) { + case '24h': return '1 DAY'; + case '3d': return '3 DAY'; + case '1w': return '1 WEEK'; + case '1m': return '1 MONTH'; + case '3m': return '3 MONTH'; + case '6m': return '6 MONTH'; + case '1y': return '1 YEAR'; + case '2y': return '2 YEAR'; + case '3y': return '3 YEAR'; + default: return null; + } + } } diff --git a/backend/src/api/mining.ts b/backend/src/api/mining.ts index 7431dc0b3..bf8c6b340 100644 --- a/backend/src/api/mining.ts +++ b/backend/src/api/mining.ts @@ -2,36 +2,20 @@ import { PoolInfo, PoolStats } from '../mempool.interfaces'; import BlocksRepository, { EmptyBlocks } from '../repositories/BlocksRepository'; import PoolsRepository from '../repositories/PoolsRepository'; import bitcoinClient from './bitcoin/bitcoin-client'; +import { Common } from './common'; class Mining { constructor() { } - private getSqlInterval(interval: string | null): string | null { - switch (interval) { - case '24h': return '1 DAY'; - case '3d': return '3 DAY'; - case '1w': return '1 WEEK'; - case '1m': return '1 MONTH'; - case '3m': return '3 MONTH'; - case '6m': return '6 MONTH'; - case '1y': return '1 YEAR'; - case '2y': return '2 YEAR'; - case '3y': return '3 YEAR'; - default: return null; - } - } - /** * Generate high level overview of the pool ranks and general stats */ public async $getPoolsStats(interval: string | null) : Promise { - const sqlInterval = this.getSqlInterval(interval); - const poolsStatistics = {}; - const poolsInfo: PoolInfo[] = await PoolsRepository.$getPoolsInfo(sqlInterval); - const emptyBlocks: EmptyBlocks[] = await BlocksRepository.$countEmptyBlocks(sqlInterval); + const poolsInfo: PoolInfo[] = await PoolsRepository.$getPoolsInfo(interval); + const emptyBlocks: EmptyBlocks[] = await BlocksRepository.$getEmptyBlocks(null, interval); const poolsStats: PoolStats[] = []; let rank = 1; @@ -58,7 +42,7 @@ class Mining { const oldestBlock = new Date(await BlocksRepository.$oldestBlockTimestamp()); poolsStatistics['oldestIndexedBlockTimestamp'] = oldestBlock.getTime(); - const blockCount: number = await BlocksRepository.$blockCount(sqlInterval); + const blockCount: number = await BlocksRepository.$blockCount(null, interval); poolsStatistics['blockCount'] = blockCount; const blockHeightTip = await bitcoinClient.getBlockCount(); @@ -74,15 +58,16 @@ class Mining { public async $getPoolStat(interval: string | null, poolId: number): Promise { const pool = await PoolsRepository.$getPool(poolId); if (!pool) { - throw new Error("This mining pool does not exist"); + throw new Error(`This mining pool does not exist`); } - const sqlInterval = this.getSqlInterval(interval); - const blocks = await BlocksRepository.$getBlocksByPool(sqlInterval, poolId); + const blockCount: number = await BlocksRepository.$blockCount(poolId, interval); + const emptyBlocks: EmptyBlocks[] = await BlocksRepository.$getEmptyBlocks(poolId, interval); return { pool: pool, - blocks: blocks, + blockCount: blockCount, + emptyBlocks: emptyBlocks, }; } } diff --git a/backend/src/index.ts b/backend/src/index.ts index b8ca55339..4b0466bf6 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -275,7 +275,9 @@ class Server { .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/3y', routes.$getPools.bind(routes, '3y')) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/all', routes.$getPools.bind(routes, 'all')) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId', routes.$getPool) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/:interval', routes.$getPool); + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/:interval', routes.$getPool) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool-blocks/:poolId', routes.$getPoolBlocks) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool-blocks/:poolId/:height', routes.$getPoolBlocks); } if (config.BISQ.ENABLED) { diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index 2d5092145..4869561c2 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -1,23 +1,23 @@ import { IEsploraApi } from './api/bitcoin/esplora-api.interface'; export interface PoolTag { - id: number, // mysql row id - name: string, - link: string, - regexes: string, // JSON array - addresses: string, // JSON array + id: number; // mysql row id + name: string; + link: string; + regexes: string; // JSON array + addresses: string; // JSON array } export interface PoolInfo { - poolId: number, // mysql row id - name: string, - link: string, - blockCount: number, + poolId: number; // mysql row id + name: string; + link: string; + blockCount: number; } export interface PoolStats extends PoolInfo { - rank: number, - emptyBlocks: number, + rank: number; + emptyBlocks: number; } export interface MempoolBlock { diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 03545f730..03b9fe5bf 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -1,6 +1,7 @@ import { BlockExtended, PoolTag } from '../mempool.interfaces'; import { DB } from '../database'; import logger from '../logger'; +import { Common } from '../api/common'; export interface EmptyBlocks { emptyBlocks: number; @@ -43,6 +44,7 @@ class BlocksRepository { block.extras?.reward ?? 0, ]; + logger.debug(query); await connection.query(query, params); } catch (e: any) { if (e.errno === 1062) { // ER_DUP_ENTRY @@ -64,35 +66,45 @@ class BlocksRepository { } const connection = await DB.pool.getConnection(); - const [rows] : any[] = await connection.query(` + const [rows]: any[] = await connection.query(` SELECT height FROM blocks - WHERE height <= ${startHeight} AND height >= ${endHeight} + WHERE height <= ? AND height >= ? ORDER BY height DESC; - `); + `, [startHeight, endHeight]); connection.release(); const indexedBlockHeights: number[] = []; rows.forEach((row: any) => { indexedBlockHeights.push(row.height); }); const seekedBlocks: number[] = Array.from(Array(startHeight - endHeight + 1).keys(), n => n + endHeight).reverse(); - const missingBlocksHeights = seekedBlocks.filter(x => indexedBlockHeights.indexOf(x) === -1); + const missingBlocksHeights = seekedBlocks.filter(x => indexedBlockHeights.indexOf(x) === -1); return missingBlocksHeights; } /** - * Count empty blocks for all pools + * Get empty blocks for one or all pools */ - public async $countEmptyBlocks(interval: string | null): Promise { - const query = ` - SELECT pool_id as poolId - FROM blocks - WHERE tx_count = 1` + - (interval != null ? ` AND blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()` : ``) - ; + public async $getEmptyBlocks(poolId: number | null, interval: string | null = null): Promise { + interval = Common.getSqlInterval(interval); + const params: any[] = []; + let query = `SELECT height, hash, tx_count, size, pool_id, weight, UNIX_TIMESTAMP(blockTimestamp) as timestamp + FROM blocks + WHERE tx_count = 1`; + + if (poolId) { + query += ` AND pool_id = ?`; + params.push(poolId); + } + + if (interval) { + query += ` AND blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; + } + + logger.debug(query); const connection = await DB.pool.getConnection(); - const [rows] = await connection.query(query); + const [rows] = await connection.query(query, params); connection.release(); return rows; @@ -101,15 +113,30 @@ class BlocksRepository { /** * Get blocks count for a period */ - public async $blockCount(interval: string | null): Promise { - const query = ` - SELECT count(height) as blockCount - FROM blocks` + - (interval != null ? ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()` : ``) - ; + public async $blockCount(poolId: number | null, interval: string | null): Promise { + interval = Common.getSqlInterval(interval); + const params: any[] = []; + let query = `SELECT count(height) as blockCount + FROM blocks`; + + if (poolId) { + query += ` WHERE pool_id = ?`; + params.push(poolId); + } + + if (interval) { + if (poolId) { + query += ` AND`; + } else { + query += ` WHERE`; + } + query += ` blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; + } + + logger.debug(query); const connection = await DB.pool.getConnection(); - const [rows] = await connection.query(query); + const [rows] = await connection.query(query, params); connection.release(); return rows[0].blockCount; @@ -119,13 +146,15 @@ class BlocksRepository { * Get the oldest indexed block */ public async $oldestBlockTimestamp(): Promise { - const connection = await DB.pool.getConnection(); - const [rows]: any[] = await connection.query(` - SELECT blockTimestamp + const query = `SELECT blockTimestamp FROM blocks ORDER BY height - LIMIT 1; - `); + LIMIT 1;`; + + + logger.debug(query); + const connection = await DB.pool.getConnection(); + const [rows]: any[] = await connection.query(query); connection.release(); if (rows.length <= 0) { @@ -138,18 +167,34 @@ class BlocksRepository { /** * Get blocks mined by a specific mining pool */ - public async $getBlocksByPool(interval: string | null, poolId: number): Promise { - const query = ` - SELECT * + public async $getBlocksByPool( + poolId: number, + startHeight: number | null = null + ): Promise { + const params: any[] = []; + let query = `SELECT height, hash, tx_count, size, weight, pool_id, UNIX_TIMESTAMP(blockTimestamp) as timestamp FROM blocks - WHERE pool_id = ${poolId}` - + (interval != null ? ` AND blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()` : ``); + WHERE pool_id = ?`; + params.push(poolId); + if (startHeight) { + query += ` AND height < ?`; + params.push(startHeight); + } + + query += ` ORDER BY height DESC + LIMIT 10`; + + logger.debug(query); const connection = await DB.pool.getConnection(); - const [rows] = await connection.query(query); + const [rows] = await connection.query(query, params); connection.release(); - return rows; + for (const block of rows) { + delete block['blockTimestamp']; + } + + return rows; } /** diff --git a/backend/src/repositories/PoolsRepository.ts b/backend/src/repositories/PoolsRepository.ts index 50f5268a7..b94f3d36d 100644 --- a/backend/src/repositories/PoolsRepository.ts +++ b/backend/src/repositories/PoolsRepository.ts @@ -1,4 +1,6 @@ +import { Common } from '../api/common'; import { DB } from '../database'; +import logger from '../logger'; import { PoolInfo, PoolTag } from '../mempool.interfaces'; class PoolsRepository { @@ -25,16 +27,21 @@ class PoolsRepository { /** * Get basic pool info and block count */ - public async $getPoolsInfo(interval: string | null): Promise { - const query = ` - SELECT COUNT(height) as blockCount, pool_id as poolId, pools.name as name, pools.link as link - FROM blocks - JOIN pools on pools.id = pool_id` + - (interval != null ? ` WHERE blocks.blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()` : ``) + - ` GROUP BY pool_id - ORDER BY COUNT(height) DESC - `; + public async $getPoolsInfo(interval: string | null = null): Promise { + interval = Common.getSqlInterval(interval); + let query = `SELECT COUNT(height) as blockCount, pool_id as poolId, pools.name as name, pools.link as link + FROM blocks + JOIN pools on pools.id = pool_id`; + + if (interval) { + query += ` WHERE blocks.blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; + } + + query += ` GROUP BY pool_id + ORDER BY COUNT(height) DESC`; + + logger.debug(query); const connection = await DB.pool.getConnection(); const [rows] = await connection.query(query); connection.release(); @@ -45,15 +52,15 @@ class PoolsRepository { /** * Get mining pool statistics for one pool */ - public async $getPool(poolId: number) : Promise { + public async $getPool(poolId: any): Promise { const query = ` SELECT * FROM pools - WHERE pools.id = ${poolId} - `; + WHERE pools.id = ?`; + logger.debug(query); const connection = await DB.pool.getConnection(); - const [rows] = await connection.query(query); + const [rows] = await connection.query(query, [poolId]); connection.release(); return rows[0]; diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 6811f9190..4e59bef3a 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -24,6 +24,7 @@ import miningStats from './api/mining'; import axios from 'axios'; import PoolsRepository from './repositories/PoolsRepository'; import mining from './api/mining'; +import BlocksRepository from './repositories/BlocksRepository'; class Routes { constructor() {} @@ -537,8 +538,7 @@ class Routes { public async $getPool(req: Request, res: Response) { try { - const poolId = parseInt(req.params.poolId); - const stats = await mining.$getPoolStat(req.params.interval ?? null, poolId); + const stats = await mining.$getPoolStat(req.params.interval ?? null, parseInt(req.params.poolId, 10)); res.header('Pragma', 'public'); res.header('Cache-control', 'public'); res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); @@ -548,9 +548,24 @@ class Routes { } } + public async $getPoolBlocks(req: Request, res: Response) { + try { + const poolBlocks = await BlocksRepository.$getBlocksByPool( + parseInt(req.params.poolId, 10), + parseInt(req.params.height, 10) ?? null, + ); + res.header('Pragma', 'public'); + res.header('Cache-control', 'public'); + res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); + res.json(poolBlocks); + } catch (e) { + res.status(500).send(e instanceof Error ? e.message : e); + } + } + public async $getPools(interval: string, req: Request, res: Response) { try { - let stats = await miningStats.$getPoolsStats(interval); + const stats = await miningStats.$getPoolsStats(interval); res.header('Pragma', 'public'); res.header('Cache-control', 'public'); res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 4018ed64e..a13bc1e54 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -71,6 +71,10 @@ let routes: Routes = [ path: 'mining/pool/:poolId', component: PoolComponent, }, + { + path: 'mining/pool/:poolId/:interval', + component: PoolComponent, + }, { path: 'graphs', component: StatisticsComponent, @@ -163,6 +167,10 @@ let routes: Routes = [ path: 'mining/pool/:poolId', component: PoolComponent, }, + { + path: 'mining/pool/:poolId/:interval', + component: PoolComponent, + }, { path: 'graphs', component: StatisticsComponent, @@ -249,6 +257,10 @@ let routes: Routes = [ path: 'mining/pool/:poolId', component: PoolComponent, }, + { + path: 'mining/pool/:poolId/:interval', + component: PoolComponent, + }, { path: 'graphs', component: StatisticsComponent, diff --git a/frontend/src/app/components/pool/pool.component.html b/frontend/src/app/components/pool/pool.component.html index 62c259ccf..9040e4553 100644 --- a/frontend/src/app/components/pool/pool.component.html +++ b/frontend/src/app/components/pool/pool.component.html @@ -1,5 +1,66 @@
- Pool +
+

+ {{ poolStats.pool.name }} +

+ +
+
+
+ + + + + + + + + + + +
Address{{ poolStats.pool.addresses }}
Coinbase Tag{{ poolStats.pool.regexes }}
+
+
+ + + + + + + + + + + +
Mined Blocks{{ poolStats.blockCount }}
Empty Blocks{{ poolStats.emptyBlocks.length }}
+
+
+
+
+ + + + + + + + + + + + + + + + + + +
HeightTimestampMinedTransactionsSize
{{ block.height }}‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}{{ block.tx_count | number }} +
+
+
+
+
\ No newline at end of file diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index 907dcf0fd..8296f7520 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -1,4 +1,10 @@ import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Observable } from 'rxjs'; +import { map, switchMap, tap } from 'rxjs/operators'; +import { BlockExtended, PoolStat } from 'src/app/interfaces/node-api.interface'; +import { ApiService } from 'src/app/services/api.service'; +import { StateService } from 'src/app/services/state.service'; @Component({ selector: 'app-pool', @@ -6,9 +12,55 @@ import { Component, OnInit } from '@angular/core'; styleUrls: ['./pool.component.scss'] }) export class PoolComponent implements OnInit { + poolStats$: Observable; + isLoading = false; + + poolId: number; + interval: string; + + blocks: any[] = []; + constructor( + private apiService: ApiService, + private route: ActivatedRoute, + public stateService: StateService, ) { } ngOnInit(): void { + this.poolStats$ = this.route.params + .pipe( + switchMap((params) => { + this.poolId = params.poolId; + this.interval = params.interval; + this.loadMore(2); + return this.apiService.getPoolStats$(params.poolId, params.interval ?? 'all'); + }), + ); + } + + loadMore(chunks = 0) { + let fromHeight: number | undefined; + if (this.blocks.length > 0) { + fromHeight = this.blocks[this.blocks.length - 1].height - 1; + } + + this.apiService.getPoolBlocks$(this.poolId, fromHeight) + .subscribe((blocks) => { + this.blocks = this.blocks.concat(blocks); + + const chunksLeft = chunks - 1; + if (chunksLeft > 0) { + this.loadMore(chunksLeft); + } + // this.cd.markForCheck(); + }, + (error) => { + console.log(error); + // this.cd.markForCheck(); + }); + } + + trackByBlock(index: number, block: BlockExtended) { + return block.height; } } diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index b923d25b7..dfcb5836e 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -54,6 +54,9 @@ export interface LiquidPegs { export interface ITranslators { [language: string]: string; } +/** + * PoolRanking component + */ export interface SinglePoolStats { poolId: number; name: string; @@ -66,20 +69,35 @@ export interface SinglePoolStats { emptyBlockRatio: string; logo: string; } - export interface PoolsStats { blockCount: number; lastEstimatedHashrate: number; oldestIndexedBlockTimestamp: number; pools: SinglePoolStats[]; } - export interface MiningStats { - lastEstimatedHashrate: string, - blockCount: number, - totalEmptyBlock: number, - totalEmptyBlockRatio: string, - pools: SinglePoolStats[], + lastEstimatedHashrate: string; + blockCount: number; + totalEmptyBlock: number; + totalEmptyBlockRatio: string; + pools: SinglePoolStats[]; +} + +/** + * Pool component + */ +export interface PoolInfo { + id: number | null; // mysql row id + name: string; + link: string; + regexes: string; // JSON array + addresses: string; // JSON array + emptyBlocks: number; +} +export interface PoolStat { + pool: PoolInfo; + blockCount: number; + emptyBlocks: BlockExtended[]; } export interface BlockExtension { diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index c19bf5a41..ec14d8a5d 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http'; -import { CpfpInfo, OptimizedMempoolStats, DifficultyAdjustment, AddressInformation, LiquidPegs, ITranslators, PoolsStats } from '../interfaces/node-api.interface'; +import { CpfpInfo, OptimizedMempoolStats, DifficultyAdjustment, AddressInformation, LiquidPegs, ITranslators, PoolsStats, PoolStat, BlockExtended, BlockExtension } from '../interfaces/node-api.interface'; import { Observable } from 'rxjs'; import { StateService } from './state.service'; import { WebsocketResponse } from '../interfaces/websocket.interface'; @@ -132,4 +132,12 @@ export class ApiService { listPools$(interval: string | null) : Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pools/${interval}`); } + + getPoolStats$(poolId: number, interval: string | null): Observable { + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${poolId}/${interval}`); + } + + getPoolBlocks$(poolId: number, fromHeight: number): Observable { + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool-blocks/${poolId}/${fromHeight}`); + } } From f2abedfbaa60b2fc95e3bb5eca509318cc316c27 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Thu, 10 Feb 2022 19:16:00 +0900 Subject: [PATCH 05/16] Add timespan switch for pool stats and load more for pool's blocks --- frontend/src/app/app-routing.module.ts | 12 --- .../app/components/pool/pool.component.html | 44 +++++++++- .../src/app/components/pool/pool.component.ts | 81 ++++++++++--------- frontend/src/app/services/api.service.ts | 6 +- 4 files changed, 91 insertions(+), 52 deletions(-) diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index a13bc1e54..4018ed64e 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -71,10 +71,6 @@ let routes: Routes = [ path: 'mining/pool/:poolId', component: PoolComponent, }, - { - path: 'mining/pool/:poolId/:interval', - component: PoolComponent, - }, { path: 'graphs', component: StatisticsComponent, @@ -167,10 +163,6 @@ let routes: Routes = [ path: 'mining/pool/:poolId', component: PoolComponent, }, - { - path: 'mining/pool/:poolId/:interval', - component: PoolComponent, - }, { path: 'graphs', component: StatisticsComponent, @@ -257,10 +249,6 @@ let routes: Routes = [ path: 'mining/pool/:poolId', component: PoolComponent, }, - { - path: 'mining/pool/:poolId/:interval', - component: PoolComponent, - }, { path: 'graphs', component: StatisticsComponent, diff --git a/frontend/src/app/components/pool/pool.component.html b/frontend/src/app/components/pool/pool.component.html index 9040e4553..14f8fc924 100644 --- a/frontend/src/app/components/pool/pool.component.html +++ b/frontend/src/app/components/pool/pool.component.html @@ -6,6 +6,46 @@
+
+
+
+
+
+ + + + + + + + + + +
+
+
+
+
@@ -47,9 +87,9 @@ - + - + diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index 8296f7520..a89c9a7b4 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -1,7 +1,9 @@ import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; -import { Observable } from 'rxjs'; -import { map, switchMap, tap } from 'rxjs/operators'; +import { once } from 'process'; +import { BehaviorSubject, combineLatest, from, merge, Observable } from 'rxjs'; +import { delay, distinctUntilChanged, map, scan, startWith, switchMap, tap } from 'rxjs/operators'; import { BlockExtended, PoolStat } from 'src/app/interfaces/node-api.interface'; import { ApiService } from 'src/app/services/api.service'; import { StateService } from 'src/app/services/state.service'; @@ -13,51 +15,56 @@ import { StateService } from 'src/app/services/state.service'; }) export class PoolComponent implements OnInit { poolStats$: Observable; + blocks$: Observable; + + fromHeight: number = -1; + fromHeightSubject: BehaviorSubject = new BehaviorSubject(this.fromHeight); + + blocks: BlockExtended[] = []; + poolId: number = undefined; isLoading = false; - - poolId: number; - interval: string; - - blocks: any[] = []; + radioGroupForm: FormGroup; constructor( private apiService: ApiService, private route: ActivatedRoute, public stateService: StateService, - ) { } - - ngOnInit(): void { - this.poolStats$ = this.route.params - .pipe( - switchMap((params) => { - this.poolId = params.poolId; - this.interval = params.interval; - this.loadMore(2); - return this.apiService.getPoolStats$(params.poolId, params.interval ?? 'all'); - }), - ); + private formBuilder: FormBuilder, + ) { + this.radioGroupForm = this.formBuilder.group({ dateSpan: '1w' }); + this.radioGroupForm.controls.dateSpan.setValue('1w'); } - loadMore(chunks = 0) { - let fromHeight: number | undefined; - if (this.blocks.length > 0) { - fromHeight = this.blocks[this.blocks.length - 1].height - 1; - } + ngOnInit(): void { + this.poolStats$ = combineLatest([ + this.route.params.pipe(map((params) => params.poolId)), + this.radioGroupForm.get('dateSpan').valueChanges.pipe(startWith('1w')), + ]) + .pipe( + switchMap((params: any) => { + this.poolId = params[0]; + if (this.blocks.length === 0) { + this.fromHeightSubject.next(undefined); + } + return this.apiService.getPoolStats$(this.poolId, params[1] ?? '1w'); + }), + ); - this.apiService.getPoolBlocks$(this.poolId, fromHeight) - .subscribe((blocks) => { - this.blocks = this.blocks.concat(blocks); + this.blocks$ = this.fromHeightSubject + .pipe( + distinctUntilChanged(), + switchMap((fromHeight) => { + return this.apiService.getPoolBlocks$(this.poolId, fromHeight); + }), + tap((newBlocks) => { + this.blocks = this.blocks.concat(newBlocks); + }), + map(() => this.blocks) + ) + } - const chunksLeft = chunks - 1; - if (chunksLeft > 0) { - this.loadMore(chunksLeft); - } - // this.cd.markForCheck(); - }, - (error) => { - console.log(error); - // this.cd.markForCheck(); - }); + loadMore() { + this.fromHeightSubject.next(this.blocks[this.blocks.length - 1]?.height); } trackByBlock(index: number, block: BlockExtended) { diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index ec14d8a5d..c1d42fd50 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -138,6 +138,10 @@ export class ApiService { } getPoolBlocks$(poolId: number, fromHeight: number): Observable { - return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool-blocks/${poolId}/${fromHeight}`); + if (fromHeight !== undefined) { + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath +`/api/v1/mining/pool-blocks/${poolId}/${fromHeight}`); + } else { + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath +`/api/v1/mining/pool-blocks/${poolId}`); + } } } From e1f3c662b2e8881358fa8ccaf5c176bac048c5dc Mon Sep 17 00:00:00 2001 From: nymkappa Date: Fri, 11 Feb 2022 17:55:38 +0900 Subject: [PATCH 06/16] Show block reward in the pool stat page --- backend/src/repositories/BlocksRepository.ts | 2 +- .../app/components/pool/pool.component.html | 89 ++++++++++--------- .../app/components/pool/pool.component.scss | 32 +++++++ 3 files changed, 78 insertions(+), 45 deletions(-) diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 03b9fe5bf..4b480a0a7 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -172,7 +172,7 @@ class BlocksRepository { startHeight: number | null = null ): Promise { const params: any[] = []; - let query = `SELECT height, hash, tx_count, size, weight, pool_id, UNIX_TIMESTAMP(blockTimestamp) as timestamp + let query = `SELECT height, hash, tx_count, size, weight, pool_id, UNIX_TIMESTAMP(blockTimestamp) as timestamp, reward FROM blocks WHERE pool_id = ?`; params.push(poolId); diff --git a/frontend/src/app/components/pool/pool.component.html b/frontend/src/app/components/pool/pool.component.html index 14f8fc924..08e742eb9 100644 --- a/frontend/src/app/components/pool/pool.component.html +++ b/frontend/src/app/components/pool/pool.component.html @@ -1,62 +1,61 @@ -
+

{{ poolStats.pool.name }}

+
+
+
+
+ + + + + + + + + + +
+ +
+
+
-
-
-
- - - - - - - - - - -
- -
-
-
-
-
-
Transactions Size
{{ block.height }}{{ block.height }} ‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }} {{ block.tx_count | number }}
+
- + - +
Address{{ poolStats.pool.addresses }}{{ poolStats.pool.addresses }}
Coinbase Tag{{ poolStats.pool.regexes }}{{ poolStats.pool.regexes }}
@@ -84,6 +83,7 @@ Height Timestamp Mined + Reward Transactions Size @@ -92,6 +92,7 @@ {{ block.height }} ‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }} + {{ block.tx_count | number }}
diff --git a/frontend/src/app/components/pool/pool.component.scss b/frontend/src/app/components/pool/pool.component.scss index e69de29bb..a1078adc0 100644 --- a/frontend/src/app/components/pool/pool.component.scss +++ b/frontend/src/app/components/pool/pool.component.scss @@ -0,0 +1,32 @@ +.progress { + background-color: #2d3348; +} + +@media (min-width: 768px) { + .d-md-block { + display: table-cell !important; + } +} +@media (min-width: 992px) { + .d-lg-block { + display: table-cell !important; + } +} + +.formRadioGroup { + margin-top: 6px; + display: flex; + flex-direction: column; + @media (min-width: 830px) { + margin-left: 2%; + flex-direction: row; + float: left; + margin-top: 0px; + } + .btn-sm { + font-size: 9px; + @media (min-width: 830px) { + font-size: 14px; + } + } +} From 9e64592acaea9d004832508573a5bfb0f5ecfad7 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Fri, 11 Feb 2022 18:29:38 +0900 Subject: [PATCH 07/16] Add mining pool logo in the pool stats page --- frontend/src/app/components/pool/pool.component.html | 3 ++- frontend/src/app/components/pool/pool.component.ts | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/components/pool/pool.component.html b/frontend/src/app/components/pool/pool.component.html index 08e742eb9..90bbc0ffc 100644 --- a/frontend/src/app/components/pool/pool.component.html +++ b/frontend/src/app/components/pool/pool.component.html @@ -1,7 +1,8 @@
-

+

+ {{ poolStats.pool.name }}

diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index a89c9a7b4..69b3ac761 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -48,6 +48,11 @@ export class PoolComponent implements OnInit { } return this.apiService.getPoolStats$(this.poolId, params[1] ?? '1w'); }), + map((poolStats) => { + return Object.assign({ + logo: `./resources/mining-pools/` + poolStats.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg' + }, poolStats); + }) ); this.blocks$ = this.fromHeightSubject From 763ea0ce6fba3b00ccd9b5f006b95201b4d40778 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Fri, 11 Feb 2022 19:10:29 +0900 Subject: [PATCH 08/16] Show pool addresses in a scrollable div with href --- .../src/app/components/pool/pool.component.html | 15 ++++++++++----- .../src/app/components/pool/pool.component.scss | 9 +++++++++ .../src/app/components/pool/pool.component.ts | 7 +++++++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/components/pool/pool.component.html b/frontend/src/app/components/pool/pool.component.html index 90bbc0ffc..471cd1449 100644 --- a/frontend/src/app/components/pool/pool.component.html +++ b/frontend/src/app/components/pool/pool.component.html @@ -47,21 +47,26 @@
-
+
- - + + + - +
Address{{ poolStats.pool.addresses }}Addresses + + ~
Coinbase TagCoinbase Tags {{ poolStats.pool.regexes }}
-
+
diff --git a/frontend/src/app/components/pool/pool.component.scss b/frontend/src/app/components/pool/pool.component.scss index a1078adc0..271696a39 100644 --- a/frontend/src/app/components/pool/pool.component.scss +++ b/frontend/src/app/components/pool/pool.component.scss @@ -30,3 +30,12 @@ } } } + +div.scrollable { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + overflow: auto; + max-height: 100px; +} \ No newline at end of file diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index 69b3ac761..8b0d2fcaf 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -49,6 +49,13 @@ export class PoolComponent implements OnInit { return this.apiService.getPoolStats$(this.poolId, params[1] ?? '1w'); }), map((poolStats) => { + let regexes = '"'; + for (const regex of JSON.parse(poolStats.pool.regexes)) { + regexes += regex + '", "'; + } + poolStats.pool.regexes = regexes.slice(0, -3); + poolStats.pool.addresses = JSON.parse(poolStats.pool.addresses); + return Object.assign({ logo: `./resources/mining-pools/` + poolStats.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg' }, poolStats); From c28f3fd4b6395fd2b4125f98f136c589be1070d7 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Fri, 11 Feb 2022 19:12:52 +0900 Subject: [PATCH 09/16] Disable query logger spam --- backend/src/repositories/BlocksRepository.ts | 10 +++++----- backend/src/repositories/PoolsRepository.ts | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 4b480a0a7..17e29fc47 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -44,7 +44,7 @@ class BlocksRepository { block.extras?.reward ?? 0, ]; - logger.debug(query); + // logger.debug(query); await connection.query(query, params); } catch (e: any) { if (e.errno === 1062) { // ER_DUP_ENTRY @@ -102,7 +102,7 @@ class BlocksRepository { query += ` AND blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; } - logger.debug(query); + // logger.debug(query); const connection = await DB.pool.getConnection(); const [rows] = await connection.query(query, params); connection.release(); @@ -134,7 +134,7 @@ class BlocksRepository { query += ` blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; } - logger.debug(query); + // logger.debug(query); const connection = await DB.pool.getConnection(); const [rows] = await connection.query(query, params); connection.release(); @@ -152,7 +152,7 @@ class BlocksRepository { LIMIT 1;`; - logger.debug(query); + // logger.debug(query); const connection = await DB.pool.getConnection(); const [rows]: any[] = await connection.query(query); connection.release(); @@ -185,7 +185,7 @@ class BlocksRepository { query += ` ORDER BY height DESC LIMIT 10`; - logger.debug(query); + // logger.debug(query); const connection = await DB.pool.getConnection(); const [rows] = await connection.query(query, params); connection.release(); diff --git a/backend/src/repositories/PoolsRepository.ts b/backend/src/repositories/PoolsRepository.ts index b94f3d36d..7970f6ff1 100644 --- a/backend/src/repositories/PoolsRepository.ts +++ b/backend/src/repositories/PoolsRepository.ts @@ -41,7 +41,7 @@ class PoolsRepository { query += ` GROUP BY pool_id ORDER BY COUNT(height) DESC`; - logger.debug(query); + // logger.debug(query); const connection = await DB.pool.getConnection(); const [rows] = await connection.query(query); connection.release(); @@ -58,7 +58,7 @@ class PoolsRepository { FROM pools WHERE pools.id = ?`; - logger.debug(query); + // logger.debug(query); const connection = await DB.pool.getConnection(); const [rows] = await connection.query(query, [poolId]); connection.release(); From d8e58ee622b57b817c10703bc05dec31b9e14c65 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Fri, 11 Feb 2022 19:27:11 +0900 Subject: [PATCH 10/16] Set reward to 0 by default until reward indexing is available --- backend/src/repositories/BlocksRepository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 17e29fc47..d8465ea71 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -172,7 +172,7 @@ class BlocksRepository { startHeight: number | null = null ): Promise { const params: any[] = []; - let query = `SELECT height, hash, tx_count, size, weight, pool_id, UNIX_TIMESTAMP(blockTimestamp) as timestamp, reward + let query = `SELECT height, hash, tx_count, size, weight, pool_id, UNIX_TIMESTAMP(blockTimestamp) as timestamp, 0 as reward FROM blocks WHERE pool_id = ?`; params.push(poolId); From 4f02efd7fee3ae31c97751a97061daf1139f8636 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Mon, 14 Feb 2022 13:21:35 +0900 Subject: [PATCH 11/16] Fix block link in pool page - Click on chart slice open pool page --- backend/src/api/mining.ts | 1 - backend/src/repositories/BlocksRepository.ts | 2 +- .../pool-ranking/pool-ranking.component.html | 2 +- .../pool-ranking/pool-ranking.component.ts | 21 ++++++++++++++++--- .../app/components/pool/pool.component.html | 16 +++++++------- .../src/app/interfaces/node-api.interface.ts | 4 ++++ 6 files changed, 32 insertions(+), 14 deletions(-) diff --git a/backend/src/api/mining.ts b/backend/src/api/mining.ts index bf8c6b340..2a978868f 100644 --- a/backend/src/api/mining.ts +++ b/backend/src/api/mining.ts @@ -2,7 +2,6 @@ import { PoolInfo, PoolStats } from '../mempool.interfaces'; import BlocksRepository, { EmptyBlocks } from '../repositories/BlocksRepository'; import PoolsRepository from '../repositories/PoolsRepository'; import bitcoinClient from './bitcoin/bitcoin-client'; -import { Common } from './common'; class Mining { constructor() { diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index d8465ea71..644c6a277 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -172,7 +172,7 @@ class BlocksRepository { startHeight: number | null = null ): Promise { const params: any[] = []; - let query = `SELECT height, hash, tx_count, size, weight, pool_id, UNIX_TIMESTAMP(blockTimestamp) as timestamp, 0 as reward + let query = `SELECT height, hash as id, tx_count, size, weight, pool_id, UNIX_TIMESTAMP(blockTimestamp) as timestamp, 0 as reward FROM blocks WHERE pool_id = ?`; params.push(poolId); diff --git a/frontend/src/app/components/pool-ranking/pool-ranking.component.html b/frontend/src/app/components/pool-ranking/pool-ranking.component.html index 45707d328..1f6fdbc0e 100644 --- a/frontend/src/app/components/pool-ranking/pool-ranking.component.html +++ b/frontend/src/app/components/pool-ranking/pool-ranking.component.html @@ -1,7 +1,7 @@
-
+
diff --git a/frontend/src/app/components/pool-ranking/pool-ranking.component.ts b/frontend/src/app/components/pool-ranking/pool-ranking.component.ts index 27515d503..d1b64f190 100644 --- a/frontend/src/app/components/pool-ranking/pool-ranking.component.ts +++ b/frontend/src/app/components/pool-ranking/pool-ranking.component.ts @@ -1,6 +1,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; -import { EChartsOption } from 'echarts'; +import { Router } from '@angular/router'; +import { EChartsOption, PieSeriesOption } from 'echarts'; import { combineLatest, Observable, of } from 'rxjs'; import { catchError, map, share, skip, startWith, switchMap, tap } from 'rxjs/operators'; import { SinglePoolStats } from 'src/app/interfaces/node-api.interface'; @@ -31,6 +32,7 @@ export class PoolRankingComponent implements OnInit, OnDestroy { chartInitOptions = { renderer: 'svg' }; + chartInstance: any = undefined; miningStatsObservable$: Observable; @@ -40,6 +42,7 @@ export class PoolRankingComponent implements OnInit, OnDestroy { private formBuilder: FormBuilder, private miningService: MiningService, private seoService: SeoService, + private router: Router, ) { this.seoService.setTitle($localize`:@@mining.mining-pools:Mining Pools`); this.poolsWindowPreference = this.storageService.getValue('poolsWindowPreference') ? this.storageService.getValue('poolsWindowPreference') : '1w'; @@ -107,7 +110,7 @@ export class PoolRankingComponent implements OnInit, OnDestroy { if (parseFloat(pool.share) < poolShareThreshold) { return; } - data.push({ + data.push({ value: pool.share, name: pool.name + (this.isMobile() ? `` : ` (${pool.share}%)`), label: { @@ -129,7 +132,8 @@ export class PoolRankingComponent implements OnInit, OnDestroy { pool.blockCount.toString() + ` blocks`; } } - } + }, + data: pool.poolId, }); }); return data; @@ -197,6 +201,17 @@ export class PoolRankingComponent implements OnInit, OnDestroy { }; } + onChartInit(ec) { + if (this.chartInstance !== undefined) { + return; + } + + this.chartInstance = ec; + this.chartInstance.on('click', (e) => { + this.router.navigate(['/mining/pool/', e.data.data]); + }) + } + /** * Default mining stats if something goes wrong */ diff --git a/frontend/src/app/components/pool/pool.component.html b/frontend/src/app/components/pool/pool.component.html index 471cd1449..fff474564 100644 --- a/frontend/src/app/components/pool/pool.component.html +++ b/frontend/src/app/components/pool/pool.component.html @@ -47,11 +47,11 @@
-
+
- + - +
AddressesAddresses
{{ address }}
@@ -60,22 +60,22 @@
~
Coinbase TagsCoinbase Tags {{ poolStats.pool.regexes }}
-
+
- - + + - - + +
Mined Blocks{{ poolStats.blockCount }}Mined Blocks{{ poolStats.blockCount }}
Empty Blocks{{ poolStats.emptyBlocks.length }}Empty Blocks{{ poolStats.emptyBlocks.length }}
diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index dfcb5836e..472df0088 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -106,6 +106,10 @@ export interface BlockExtension { reward?: number; coinbaseTx?: Transaction; matchRate?: number; + pool?: { + id: number; + name: string; + } stage?: number; // Frontend only } From 09180c4f9195efb6374ba8fed18308d5be7a8bde Mon Sep 17 00:00:00 2001 From: nymkappa Date: Mon, 14 Feb 2022 13:58:40 +0900 Subject: [PATCH 12/16] Renamed /mining/pool-blocks/xxx -> /mining/pool/:poolId/blocks --- backend/src/index.ts | 6 +++--- frontend/src/app/services/api.service.ts | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 4b0466bf6..1f8575294 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -274,10 +274,10 @@ class Server { .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/2y', routes.$getPools.bind(routes, '2y')) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/3y', routes.$getPools.bind(routes, '3y')) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/all', routes.$getPools.bind(routes, 'all')) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/blocks', routes.$getPoolBlocks) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/blocks/:height', routes.$getPoolBlocks) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId', routes.$getPool) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/:interval', routes.$getPool) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool-blocks/:poolId', routes.$getPoolBlocks) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool-blocks/:poolId/:height', routes.$getPoolBlocks); + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/:interval', routes.$getPool); } if (config.BISQ.ENABLED) { diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index c1d42fd50..ec01a2a5c 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -139,9 +139,11 @@ export class ApiService { getPoolBlocks$(poolId: number, fromHeight: number): Observable { if (fromHeight !== undefined) { - return this.httpClient.get(this.apiBaseUrl + this.apiBasePath +`/api/v1/mining/pool-blocks/${poolId}/${fromHeight}`); + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + + `/api/v1/mining/pool/${poolId}/blocks/${fromHeight}`); } else { - return this.httpClient.get(this.apiBaseUrl + this.apiBasePath +`/api/v1/mining/pool-blocks/${poolId}`); + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + + `/api/v1/mining/pool/${poolId}/blocks`); } } } From a436d3a173899306eea3106716a1e00a8ceedd71 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Mon, 14 Feb 2022 14:03:05 +0900 Subject: [PATCH 13/16] Fix label width being too small on mobile --- frontend/src/app/components/pool/pool.component.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/components/pool/pool.component.html b/frontend/src/app/components/pool/pool.component.html index fff474564..e10b8e6e4 100644 --- a/frontend/src/app/components/pool/pool.component.html +++ b/frontend/src/app/components/pool/pool.component.html @@ -51,7 +51,7 @@ - + - + @@ -70,11 +70,11 @@
AddressesAddresses
{{ address }}
@@ -60,7 +60,7 @@
~
Coinbase TagsCoinbase Tags {{ poolStats.pool.regexes }}
- + - + From f381da0f78d4fa873b6ed3ee54a6fb2cb06a5f19 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Mon, 14 Feb 2022 14:11:55 +0900 Subject: [PATCH 14/16] Show correct reward in pool stat page --- backend/src/repositories/BlocksRepository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 644c6a277..6e74e9435 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -172,7 +172,7 @@ class BlocksRepository { startHeight: number | null = null ): Promise { const params: any[] = []; - let query = `SELECT height, hash as id, tx_count, size, weight, pool_id, UNIX_TIMESTAMP(blockTimestamp) as timestamp, 0 as reward + let query = `SELECT height, hash as id, tx_count, size, weight, pool_id, UNIX_TIMESTAMP(blockTimestamp) as timestamp, reward FROM blocks WHERE pool_id = ?`; params.push(poolId); From 1e96c93557dafe2eef5460ceb23e5c1b09f3e741 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 15 Feb 2022 18:36:58 +0900 Subject: [PATCH 15/16] Fix rendering issue when clicking on block link from pool page --- frontend/src/app/components/pool/pool.component.html | 2 +- frontend/src/app/components/pool/pool.component.ts | 11 +++++------ frontend/src/app/services/api.service.ts | 11 ++++------- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/frontend/src/app/components/pool/pool.component.html b/frontend/src/app/components/pool/pool.component.html index e10b8e6e4..43bc647e8 100644 --- a/frontend/src/app/components/pool/pool.component.html +++ b/frontend/src/app/components/pool/pool.component.html @@ -95,7 +95,7 @@ - + diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index 8b0d2fcaf..230b9a0f8 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -1,9 +1,8 @@ -import { Component, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; -import { once } from 'process'; -import { BehaviorSubject, combineLatest, from, merge, Observable } from 'rxjs'; -import { delay, distinctUntilChanged, map, scan, startWith, switchMap, tap } from 'rxjs/operators'; +import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; +import { distinctUntilChanged, map, startWith, switchMap, tap } from 'rxjs/operators'; import { BlockExtended, PoolStat } from 'src/app/interfaces/node-api.interface'; import { ApiService } from 'src/app/services/api.service'; import { StateService } from 'src/app/services/state.service'; @@ -11,7 +10,8 @@ import { StateService } from 'src/app/services/state.service'; @Component({ selector: 'app-pool', templateUrl: './pool.component.html', - styleUrls: ['./pool.component.scss'] + styleUrls: ['./pool.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush }) export class PoolComponent implements OnInit { poolStats$: Observable; @@ -22,7 +22,6 @@ export class PoolComponent implements OnInit { blocks: BlockExtended[] = []; poolId: number = undefined; - isLoading = false; radioGroupForm: FormGroup; constructor( diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index ec01a2a5c..bf468c467 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -138,12 +138,9 @@ export class ApiService { } getPoolBlocks$(poolId: number, fromHeight: number): Observable { - if (fromHeight !== undefined) { - return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + - `/api/v1/mining/pool/${poolId}/blocks/${fromHeight}`); - } else { - return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + - `/api/v1/mining/pool/${poolId}/blocks`); - } + return this.httpClient.get( + this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${poolId}/blocks` + + (fromHeight !== undefined ? `/${fromHeight}` : '') + ); } } From fa8607c57d30880abcb17b8a5ed69db462da7012 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 15 Feb 2022 18:45:53 +0900 Subject: [PATCH 16/16] [Pool page] - Parse regexes and addresses in the backend --- backend/src/repositories/PoolsRepository.ts | 3 +++ frontend/src/app/components/pool/pool.component.ts | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/src/repositories/PoolsRepository.ts b/backend/src/repositories/PoolsRepository.ts index 7970f6ff1..a7b716da7 100644 --- a/backend/src/repositories/PoolsRepository.ts +++ b/backend/src/repositories/PoolsRepository.ts @@ -63,6 +63,9 @@ class PoolsRepository { const [rows] = await connection.query(query, [poolId]); connection.release(); + rows[0].regexes = JSON.parse(rows[0].regexes); + rows[0].addresses = JSON.parse(rows[0].addresses); + return rows[0]; } } diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index 230b9a0f8..9d094dce0 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -49,11 +49,11 @@ export class PoolComponent implements OnInit { }), map((poolStats) => { let regexes = '"'; - for (const regex of JSON.parse(poolStats.pool.regexes)) { + for (const regex of poolStats.pool.regexes) { regexes += regex + '", "'; } poolStats.pool.regexes = regexes.slice(0, -3); - poolStats.pool.addresses = JSON.parse(poolStats.pool.addresses); + poolStats.pool.addresses = poolStats.pool.addresses; return Object.assign({ logo: `./resources/mining-pools/` + poolStats.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg'
Mined BlocksMined Blocks {{ poolStats.blockCount }}
Empty BlocksEmpty Blocks {{ poolStats.emptyBlocks.length }}
{{ block.height }}{{ block.height }} ‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}