Small improvements on the mining page UX

- INDEXING_BLOCKS_AMOUNT = 0 disable indexing, INDEXING_BLOCKS_AMOUNT = -1 indexes everything
- Show only available timespan in the mining page according to available datas
- Change default INDEXING_BLOCKS_AMOUNT to 1100

Don't use unfiltered mysql user input

Enable http cache header for mining pools (1 min)
This commit is contained in:
nymkappa
2022-01-25 18:33:46 +09:00
parent d66bc57165
commit 6ebbc5667d
20 changed files with 183 additions and 121 deletions

View File

@@ -12,6 +12,7 @@
"BLOCK_WEIGHT_UNITS": 4000000,
"INITIAL_BLOCKS_AMOUNT": 8,
"MEMPOOL_BLOCKS_AMOUNT": 8,
"INDEXING_BLOCKS_AMOUNT": 1100,
"PRICE_FEED_UPDATE_INTERVAL": 3600,
"USE_SECOND_NODE_FOR_MINFEE": false,
"EXTERNAL_ASSETS": [

View File

@@ -114,7 +114,7 @@ class Blocks {
* @returns
*/
private async $findBlockMiner(txMinerInfo: TransactionMinerInfo | undefined): Promise<PoolTag> {
if (txMinerInfo === undefined) {
if (txMinerInfo === undefined || txMinerInfo.vout.length < 1) {
return await poolsRepository.$getUnknownPool();
}
@@ -147,9 +147,9 @@ class Blocks {
*/
public async $generateBlockDatabase() {
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === false || // Bitcoin only
config.MEMPOOL.INDEXING_BLOCKS_AMOUNT <= 0 || // Indexing must be enabled
this.blockIndexingStarted === true || // Indexing must not already be in progress
!memPool.isInSync() // We sync the mempool first
config.MEMPOOL.INDEXING_BLOCKS_AMOUNT === 0 || // Indexing must be enabled
!memPool.isInSync() || // We sync the mempool first
this.blockIndexingStarted === true // Indexing must not already be in progress
) {
return;
}
@@ -163,7 +163,13 @@ class Blocks {
try {
let currentBlockHeight = blockchainInfo.blocks;
const lastBlockToIndex = Math.max(0, currentBlockHeight - config.MEMPOOL.INDEXING_BLOCKS_AMOUNT + 1);
let indexingBlockAmount = config.MEMPOOL.INDEXING_BLOCKS_AMOUNT;
if (indexingBlockAmount <= -1) {
indexingBlockAmount = currentBlockHeight + 1;
}
const lastBlockToIndex = Math.max(0, currentBlockHeight - indexingBlockAmount + 1);
logger.info(`Indexing blocks from #${currentBlockHeight} to #${lastBlockToIndex}`);

View File

@@ -118,11 +118,11 @@ class Mempool {
});
}
hasChange = true;
// if (diff > 0) {
// logger.debug('Fetched transaction ' + txCount + ' / ' + diff);
// } else {
// logger.debug('Fetched transaction ' + txCount);
// }
if (diff > 0) {
logger.debug('Fetched transaction ' + txCount + ' / ' + diff);
} else {
logger.debug('Fetched transaction ' + txCount);
}
newTransactions.push(transaction);
} catch (e) {
logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e));

View File

@@ -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 BitcoinApi from './bitcoin/bitcoin-api';
class Mining {
constructor() {
@@ -11,14 +10,25 @@ class Mining {
/**
* Generate high level overview of the pool ranks and general stats
*/
public async $getPoolsStats(interval: string = '100 YEAR') : Promise<object> {
public async $getPoolsStats(interval: string | null) : Promise<object> {
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 poolsStatistics = {};
const blockHeightTip = await bitcoinClient.getBlockCount();
const lastBlockHashrate = await bitcoinClient.getNetworkHashPs(120, blockHeightTip);
const poolsInfo: PoolInfo[] = await PoolsRepository.$getPoolsInfo(interval);
const blockCount: number = await BlocksRepository.$blockCount(interval);
const emptyBlocks: EmptyBlocks[] = await BlocksRepository.$countEmptyBlocks(interval);
const poolsInfo: PoolInfo[] = await PoolsRepository.$getPoolsInfo(sqlInterval);
const emptyBlocks: EmptyBlocks[] = await BlocksRepository.$countEmptyBlocks(sqlInterval);
const poolsStats: PoolStats[] = [];
let rank = 1;
@@ -40,12 +50,20 @@ class Mining {
poolsStats.push(poolStat);
});
poolsStatistics['blockCount'] = blockCount;
poolsStatistics['lastEstimatedHashrate'] = lastBlockHashrate;
poolsStatistics['pools'] = poolsStats;
const oldestBlock = new Date(await BlocksRepository.$oldestBlockTimestamp());
poolsStatistics['oldestIndexedBlockTimestamp'] = oldestBlock.getTime();
const blockCount: number = await BlocksRepository.$blockCount(sqlInterval);
poolsStatistics['blockCount'] = blockCount;
const blockHeightTip = await bitcoinClient.getBlockCount();
const lastBlockHashrate = await bitcoinClient.getNetworkHashPs(120, blockHeightTip);
poolsStatistics['lastEstimatedHashrate'] = lastBlockHashrate;
return poolsStatistics;
}
}
export default new Mining();
export default new Mining();

View File

@@ -78,7 +78,7 @@ const defaults: IConfig = {
'BLOCK_WEIGHT_UNITS': 4000000,
'INITIAL_BLOCKS_AMOUNT': 8,
'MEMPOOL_BLOCKS_AMOUNT': 8,
'INDEXING_BLOCKS_AMOUNT': 432, // ~3 days at 10 minutes / block. Set to 0 to disable indexing
'INDEXING_BLOCKS_AMOUNT': 1100, // 0 = disable indexing, -1 = index all blocks
'PRICE_FEED_UPDATE_INTERVAL': 3600,
'USE_SECOND_NODE_FOR_MINFEE': false,
'EXTERNAL_ASSETS': [

View File

@@ -255,7 +255,7 @@ 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'))
.get(config.MEMPOOL.API_URL_PREFIX + 'pools', routes.$getPools)
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools', routes.$getPools)
;
}

View File

@@ -72,14 +72,16 @@ class BlocksRepository {
/**
* Count empty blocks for all pools
*/
public async $countEmptyBlocks(interval: string = '100 YEAR'): Promise<EmptyBlocks[]> {
const connection = await DB.pool.getConnection();
const [rows] = await connection.query(`
public async $countEmptyBlocks(interval: string | null): Promise<EmptyBlocks[]> {
const query = `
SELECT pool_id as poolId
FROM blocks
WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()
AND tx_count = 1;
`);
WHERE tx_count = 1` +
(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 <EmptyBlocks[]>rows;
@@ -88,17 +90,39 @@ class BlocksRepository {
/**
* Get blocks count for a period
*/
public async $blockCount(interval: string = '100 YEAR'): Promise<number> {
const connection = await DB.pool.getConnection();
const [rows] = await connection.query(`
public async $blockCount(interval: string | null): Promise<number> {
const query = `
SELECT count(height) as blockCount
FROM blocks
WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW();
`);
FROM blocks` +
(interval != null ? ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()` : ``)
;
const connection = await DB.pool.getConnection();
const [rows] = await connection.query(query);
connection.release();
return <number>rows[0].blockCount;
}
/**
* Get the oldest indexed block
*/
public async $oldestBlockTimestamp(): Promise<number> {
const connection = await DB.pool.getConnection();
const [rows]: any[] = await connection.query(`
SELECT blockTimestamp
FROM blocks
ORDER BY height
LIMIT 1;
`);
connection.release();
if (rows.length <= 0) {
return -1;
}
return <number>rows[0].blockTimestamp;
}
}
export default new BlocksRepository();

View File

@@ -25,17 +25,20 @@ class PoolsRepository {
/**
* Get basic pool info and block count
*/
public async $getPoolsInfo(interval: string = '100 YEARS'): Promise<PoolInfo[]> {
const connection = await DB.pool.getConnection();
const [rows] = await connection.query(`
public async $getPoolsInfo(interval: string | null): Promise<PoolInfo[]> {
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
WHERE blocks.blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()
GROUP BY pool_id
ORDER BY COUNT(height) DESC;
`);
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
`;
const connection = await DB.pool.getConnection();
const [rows] = await connection.query(query);
connection.release();
return <PoolInfo[]>rows;
}
}

View File

@@ -535,9 +535,9 @@ class Routes {
public async $getPools(req: Request, res: Response) {
try {
let stats = await miningStats.$getPoolsStats(req.query.interval as string);
// res.header('Pragma', 'public');
// res.header('Cache-control', 'public');
// res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
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);