mirror of
https://github.com/mempool/mempool.git
synced 2026-08-13 12:33:11 +02:00
145 lines
6.1 KiB
TypeScript
145 lines
6.1 KiB
TypeScript
import DB from '../database';
|
|
import logger from '../logger';
|
|
|
|
export const INDEXING_PRESETS = [
|
|
{name: 'per block', bucketSize: 1, retentionSpan: 144}, // block span of ~1 day
|
|
{name: 'per week', bucketSize: 1008, retentionSpan: -1}, // all
|
|
{name: 'per month', bucketSize: 4032, retentionSpan: -1}, // all
|
|
];
|
|
|
|
export const INTERVAL_PRESETS = {
|
|
'24h': {retentionSpan: 144, bucketSizes: [1]},
|
|
'6m': {retentionSpan: 24192, bucketSizes: [1008, 4032]},
|
|
'1y': {retentionSpan: 48384, bucketSizes: [1008, 4032]},
|
|
'2y': {retentionSpan: 96768, bucketSizes: [1008, 4032]},
|
|
'3y': {retentionSpan: 145152, bucketSizes: [1008, 4032]},
|
|
'all': {retentionSpan: -1, bucketSizes: [1008, 4032]},
|
|
};
|
|
|
|
class FlagValuesRepository {
|
|
/**
|
|
* Get the latest indexed day from the database
|
|
*
|
|
* @asyncSafe */
|
|
public async $getTipAndTailIndexedByBucketSize(bucketSize: number): Promise<{tip: number, tail: number} | null> {
|
|
try {
|
|
const [rows]: any[] = await DB.query(`SELECT (MAX(start_height) + ?) as tip, MIN(start_height) as tail FROM flag_values WHERE bucket_size = ?`, [bucketSize, bucketSize.toString()]);
|
|
if (rows !== null && rows.length > 0 && rows[0].tip !== null && rows[0].tail !== null) {
|
|
return rows[0];
|
|
}
|
|
} catch (e) {
|
|
logger.err(`Cannot get tip and tail indexed from flag_values. Reason: ` + (e instanceof Error ? e.message : e));
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get the set of bucket that area already indexed between heights by bucketSize
|
|
*
|
|
* @asyncSafe */
|
|
public async $getIndexedStartHeights(bucketSize: number, startHeight: number, latestHeight: number): Promise<number[]> {
|
|
try {
|
|
const [rows]: any[] = await DB.query(
|
|
`SELECT DISTINCT start_height FROM flag_values WHERE bucket_size = ? AND start_height <= ? AND start_height >= ?`,
|
|
[bucketSize.toString(), startHeight, latestHeight]
|
|
);
|
|
return rows.map(row => row.start_height);
|
|
} catch (e) {
|
|
logger.err(`Cannot get indexed start heights from flag_values. Reason: ` + (e instanceof Error ? e.message : e));
|
|
}
|
|
return [];
|
|
}
|
|
|
|
public async $saveBatchFlagValues(bucketSize: number, startHeight: number, dataPerFlag: Record<string, Record<string, number>>, avgTimestamp: number): Promise<void> {
|
|
const params: any[] = [];
|
|
const distinctFlags = Object.keys(dataPerFlag);
|
|
const avgDate = new Date(Math.round(avgTimestamp) * 1000);
|
|
for (const flag of distinctFlags) {
|
|
params.push([bucketSize.toString(), startHeight, avgDate, BigInt(flag), dataPerFlag[flag].txCount, dataPerFlag[flag].vSizeTotal]);
|
|
}
|
|
try {
|
|
await DB.query(`
|
|
INSERT INTO flag_values (bucket_size, start_height, avg_timestamp, flag_value, tx_count, vsize_total) VALUES ?
|
|
ON DUPLICATE KEY UPDATE
|
|
avg_timestamp = VALUES(avg_timestamp), tx_count = VALUES(tx_count), vsize_total = VALUES(vsize_total)
|
|
`, [params]);
|
|
} catch (e) {
|
|
logger.debug(`Cannot save flag batched values. Reason: ${e instanceof Error ? e.message : e}`);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
public async $queryTxCountBasedOnMask(mask: bigint, bucketSize: number, op: 'and' | 'or' | 'nor' | undefined, startHeight: number): Promise<{bucketSize: string, startHeight: number, avgTimestamp: number, txCount: number, vSizeTotal: number}[]> {
|
|
let flagPredicate = '';
|
|
let params: any[]= [];
|
|
switch (op) {
|
|
case 'and': {
|
|
flagPredicate = 'AND (flag_value & ?) = ?';
|
|
params = [bucketSize.toString(), startHeight, mask, mask];
|
|
} break;
|
|
case 'or': {
|
|
flagPredicate = 'AND (flag_value & ?) > 0';
|
|
params = [bucketSize.toString(), startHeight, mask];
|
|
} break;
|
|
case 'nor': {
|
|
flagPredicate = 'AND (flag_value & ?) = 0';
|
|
params = [bucketSize.toString(), startHeight, mask];
|
|
} break;
|
|
case undefined: { // op not passed, no boolean operations
|
|
params = [bucketSize.toString(), startHeight];
|
|
break;
|
|
}
|
|
default: throw new Error(`Invalid op '${op}', expected 'and' | 'or' | 'nor' | undefined`);
|
|
}
|
|
try {
|
|
const [rows]: any[] = await DB.query(`
|
|
SELECT bucket_size as bucketSize, start_height as startHeight, UNIX_TIMESTAMP(avg_timestamp) as avgTimestamp,
|
|
SUM(tx_count) as txCount, SUM(vsize_total) as vSizeTotal
|
|
FROM flag_values
|
|
WHERE bucket_size = ? AND start_height >= ? ${flagPredicate}
|
|
GROUP BY start_height ORDER BY start_height DESC
|
|
`, params);
|
|
if (rows !== null && rows.length > 0) {
|
|
return rows;
|
|
}
|
|
} catch (e) {
|
|
logger.debug(`Cannot get tx counts. Reason: ${e instanceof Error ? e.message : e}`);
|
|
}
|
|
return [];
|
|
}
|
|
|
|
/** @asyncSafe */
|
|
public async $deleteFlagValuesBelowHeight(height: number, bucketSize: number): Promise<void> {
|
|
try {
|
|
await DB.query(`DELETE FROM flag_values WHERE start_height < ? AND bucket_size = ?`, [height, bucketSize.toString()]);
|
|
} catch(e) {
|
|
logger.err(`Cannot delete flag values below block #${height}. Reason: ` + (e instanceof Error ? e.message : e));
|
|
}
|
|
}
|
|
|
|
/** @asyncSafe */
|
|
public async $deleteFlagValuesFromHeight(height: number): Promise<void> {
|
|
try {
|
|
for (const preset of INDEXING_PRESETS) {
|
|
const startHeight = Math.floor(height / preset.bucketSize) * preset.bucketSize;
|
|
await DB.query(`DELETE FROM flag_values WHERE start_height >= ? AND bucket_size = ?`, [startHeight, preset.bucketSize.toString()]);
|
|
}
|
|
} catch (e) {
|
|
logger.err(`Cannot delete flag values above ${height}. Reason: ` + (e instanceof Error ? e.message : e));
|
|
}
|
|
}
|
|
|
|
public async $getTotalBlocksIndexedByBucketSize(bucketSize: number): Promise<number | null> {
|
|
try {
|
|
const [rows]: any[] = await DB.query(`SELECT (count(distinct start_height) * ?) as total FROM flag_values WHERE bucket_size = ?`, [bucketSize, bucketSize.toString()]);
|
|
if (rows !== null && rows.length > 0) {
|
|
return rows[0].total;
|
|
}
|
|
} catch (e) {
|
|
logger.err(`Cannot get total blocks indexed in flag_values. Reason: ` + (e instanceof Error ? e.message : e));
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export default new FlagValuesRepository();
|