2022-11-23 19:07:17 +09:00
|
|
|
import blocks from '../api/blocks';
|
2022-07-06 22:27:45 +02:00
|
|
|
import DB from '../database';
|
|
|
|
import logger from '../logger';
|
2024-07-10 13:13:53 +09:00
|
|
|
import { BlockAudit, AuditScore, TransactionAudit } from '../mempool.interfaces';
|
2022-07-06 22:27:45 +02:00
|
|
|
|
|
|
|
class BlocksAuditRepositories {
|
|
|
|
public async $saveAudit(audit: BlockAudit): Promise<void> {
|
|
|
|
try {
|
2024-04-02 02:02:17 +00:00
|
|
|
await DB.query(`INSERT INTO blocks_audits(time, height, hash, missing_txs, added_txs, prioritized_txs, fresh_txs, sigop_txs, fullrbf_txs, accelerated_txs, match_rate, expected_fees, expected_weight)
|
|
|
|
VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [audit.time, audit.height, audit.hash, JSON.stringify(audit.missingTxs),
|
|
|
|
JSON.stringify(audit.addedTxs), JSON.stringify(audit.prioritizedTxs), JSON.stringify(audit.freshTxs), JSON.stringify(audit.sigopTxs), JSON.stringify(audit.fullrbfTxs), JSON.stringify(audit.acceleratedTxs), audit.matchRate, audit.expectedFees, audit.expectedWeight]);
|
2022-07-06 22:27:45 +02:00
|
|
|
} catch (e: any) {
|
|
|
|
if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart
|
|
|
|
logger.debug(`Cannot save block audit for block ${audit.hash} because it has already been indexed, ignoring`);
|
|
|
|
} else {
|
|
|
|
logger.err(`Cannot save block audit into db. Reason: ` + (e instanceof Error ? e.message : e));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-09 13:46:43 -04:00
|
|
|
public async $setSummary(hash: string, expectedFees: number, expectedWeight: number) {
|
|
|
|
try {
|
|
|
|
await DB.query(`
|
|
|
|
UPDATE blocks_audits SET
|
|
|
|
expected_fees = ?,
|
|
|
|
expected_weight = ?
|
|
|
|
WHERE hash = ?
|
|
|
|
`, [expectedFees, expectedWeight, hash]);
|
|
|
|
} catch (e: any) {
|
|
|
|
logger.err(`Cannot update block audit in db. Reason: ` + (e instanceof Error ? e.message : e));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-16 17:03:32 +09:00
|
|
|
public async $getBlocksHealthHistory(div: number, interval: string | null): Promise<any> {
|
2022-07-06 22:27:45 +02:00
|
|
|
try {
|
|
|
|
let query = `SELECT UNIX_TIMESTAMP(time) as time, height, match_rate FROM blocks_audits`;
|
|
|
|
|
|
|
|
if (interval !== null) {
|
|
|
|
query += ` WHERE time BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
|
|
|
|
}
|
|
|
|
|
|
|
|
query += ` GROUP BY UNIX_TIMESTAMP(time) DIV ${div} ORDER BY height`;
|
|
|
|
|
|
|
|
const [rows] = await DB.query(query);
|
|
|
|
return rows;
|
|
|
|
} catch (e: any) {
|
2023-03-16 17:03:32 +09:00
|
|
|
logger.err(`Cannot fetch blocks health history. Reason: ` + (e instanceof Error ? e.message : e));
|
2022-07-06 22:27:45 +02:00
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-16 17:03:32 +09:00
|
|
|
public async $getBlocksHealthCount(): Promise<number> {
|
2022-07-06 22:27:45 +02:00
|
|
|
try {
|
|
|
|
const [rows] = await DB.query(`SELECT count(hash) as count FROM blocks_audits`);
|
|
|
|
return rows[0].count;
|
|
|
|
} catch (e: any) {
|
2023-03-16 17:03:32 +09:00
|
|
|
logger.err(`Cannot fetch blocks health count. Reason: ` + (e instanceof Error ? e.message : e));
|
2022-07-06 22:27:45 +02:00
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
2022-07-07 19:11:42 +02:00
|
|
|
|
2024-01-30 16:41:35 +00:00
|
|
|
public async $getBlockAudit(hash: string): Promise<BlockAudit | null> {
|
2022-07-07 19:11:42 +02:00
|
|
|
try {
|
|
|
|
const [rows]: any[] = await DB.query(
|
2023-07-08 00:33:14 -04:00
|
|
|
`SELECT blocks_audits.height, blocks_audits.hash as id, UNIX_TIMESTAMP(blocks_audits.time) as timestamp,
|
2023-06-09 13:46:14 -04:00
|
|
|
template,
|
|
|
|
missing_txs as missingTxs,
|
|
|
|
added_txs as addedTxs,
|
2024-04-02 02:02:17 +00:00
|
|
|
prioritized_txs as prioritizedTxs,
|
2023-06-09 13:46:14 -04:00
|
|
|
fresh_txs as freshTxs,
|
|
|
|
sigop_txs as sigopTxs,
|
2023-06-19 18:14:09 -04:00
|
|
|
fullrbf_txs as fullrbfTxs,
|
2023-05-22 18:16:58 -04:00
|
|
|
accelerated_txs as acceleratedTxs,
|
2023-06-09 13:46:14 -04:00
|
|
|
match_rate as matchRate,
|
|
|
|
expected_fees as expectedFees,
|
|
|
|
expected_weight as expectedWeight
|
2022-07-07 19:11:42 +02:00
|
|
|
FROM blocks_audits
|
2023-05-25 17:39:45 -04:00
|
|
|
JOIN blocks_templates ON blocks_templates.id = blocks_audits.hash
|
2024-01-31 18:38:56 +00:00
|
|
|
WHERE blocks_audits.hash = ?
|
|
|
|
`, [hash]);
|
2022-07-07 19:11:42 +02:00
|
|
|
|
2022-10-27 18:39:26 -06:00
|
|
|
if (rows.length) {
|
|
|
|
rows[0].missingTxs = JSON.parse(rows[0].missingTxs);
|
|
|
|
rows[0].addedTxs = JSON.parse(rows[0].addedTxs);
|
2024-04-02 02:02:17 +00:00
|
|
|
rows[0].prioritizedTxs = JSON.parse(rows[0].prioritizedTxs);
|
2022-11-23 19:03:28 +09:00
|
|
|
rows[0].freshTxs = JSON.parse(rows[0].freshTxs);
|
2023-05-17 11:46:50 -04:00
|
|
|
rows[0].sigopTxs = JSON.parse(rows[0].sigopTxs);
|
2023-06-19 18:14:09 -04:00
|
|
|
rows[0].fullrbfTxs = JSON.parse(rows[0].fullrbfTxs);
|
2023-05-22 18:16:58 -04:00
|
|
|
rows[0].acceleratedTxs = JSON.parse(rows[0].acceleratedTxs);
|
2022-10-27 18:39:26 -06:00
|
|
|
rows[0].template = JSON.parse(rows[0].template);
|
2022-11-25 10:16:58 +09:00
|
|
|
|
2023-06-20 14:54:25 -04:00
|
|
|
return rows[0];
|
2022-10-27 18:39:26 -06:00
|
|
|
}
|
2022-11-25 19:32:50 +09:00
|
|
|
return null;
|
2022-07-07 19:11:42 +02:00
|
|
|
} catch (e: any) {
|
|
|
|
logger.err(`Cannot fetch block audit from db. Reason: ` + (e instanceof Error ? e.message : e));
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
2022-10-28 10:31:55 -06:00
|
|
|
|
2024-07-10 13:13:53 +09:00
|
|
|
public async $getBlockTxAudit(hash: string, txid: string): Promise<TransactionAudit | null> {
|
|
|
|
try {
|
|
|
|
const blockAudit = await this.$getBlockAudit(hash);
|
|
|
|
|
|
|
|
if (blockAudit) {
|
|
|
|
const isAdded = blockAudit.addedTxs.includes(txid);
|
|
|
|
const isPrioritized = blockAudit.prioritizedTxs.includes(txid);
|
|
|
|
const isAccelerated = blockAudit.acceleratedTxs.includes(txid);
|
|
|
|
const isConflict = blockAudit.fullrbfTxs.includes(txid);
|
|
|
|
let isExpected = false;
|
|
|
|
let firstSeen = undefined;
|
|
|
|
blockAudit.template?.forEach(tx => {
|
|
|
|
if (tx.txid === txid) {
|
|
|
|
isExpected = true;
|
|
|
|
firstSeen = tx.time;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return {
|
|
|
|
seen: isExpected || isPrioritized || isAccelerated,
|
|
|
|
expected: isExpected,
|
|
|
|
added: isAdded,
|
|
|
|
prioritized: isPrioritized,
|
|
|
|
conflict: isConflict,
|
|
|
|
accelerated: isAccelerated,
|
|
|
|
firstSeen,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
} catch (e: any) {
|
|
|
|
logger.err(`Cannot fetch block transaction audit from db. Reason: ` + (e instanceof Error ? e.message : e));
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-01 14:01:50 -06:00
|
|
|
public async $getBlockAuditScore(hash: string): Promise<AuditScore> {
|
2022-10-28 10:31:55 -06:00
|
|
|
try {
|
|
|
|
const [rows]: any[] = await DB.query(
|
2023-06-09 13:46:14 -04:00
|
|
|
`SELECT hash, match_rate as matchRate, expected_fees as expectedFees, expected_weight as expectedWeight
|
2022-10-28 10:31:55 -06:00
|
|
|
FROM blocks_audits
|
2024-01-31 18:38:56 +00:00
|
|
|
WHERE blocks_audits.hash = ?
|
|
|
|
`, [hash]);
|
2022-10-28 10:31:55 -06:00
|
|
|
return rows[0];
|
|
|
|
} catch (e: any) {
|
|
|
|
logger.err(`Cannot fetch block audit from db. Reason: ` + (e instanceof Error ? e.message : e));
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
2022-11-24 17:03:54 +09:00
|
|
|
|
|
|
|
public async $getBlockAuditScores(maxHeight: number, minHeight: number): Promise<AuditScore[]> {
|
|
|
|
try {
|
|
|
|
const [rows]: any[] = await DB.query(
|
2023-06-09 13:46:14 -04:00
|
|
|
`SELECT hash, match_rate as matchRate, expected_fees as expectedFees, expected_weight as expectedWeight
|
2022-11-24 17:03:54 +09:00
|
|
|
FROM blocks_audits
|
|
|
|
WHERE blocks_audits.height BETWEEN ? AND ?
|
|
|
|
`, [minHeight, maxHeight]);
|
|
|
|
return rows;
|
|
|
|
} catch (e: any) {
|
|
|
|
logger.err(`Cannot fetch block audit from db. Reason: ` + (e instanceof Error ? e.message : e));
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
2023-06-09 13:46:43 -04:00
|
|
|
|
|
|
|
public async $getBlocksWithoutSummaries(): Promise<string[]> {
|
|
|
|
try {
|
|
|
|
const [fromRows]: any[] = await DB.query(`
|
|
|
|
SELECT height
|
|
|
|
FROM blocks_audits
|
|
|
|
WHERE expected_fees IS NULL
|
|
|
|
ORDER BY height DESC
|
|
|
|
LIMIT 1
|
|
|
|
`);
|
|
|
|
if (!fromRows?.length) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
const fromHeight = fromRows[0].height;
|
|
|
|
const [idRows]: any[] = await DB.query(`
|
|
|
|
SELECT hash
|
|
|
|
FROM blocks_audits
|
|
|
|
WHERE height <= ?
|
|
|
|
ORDER BY height DESC
|
|
|
|
`, [fromHeight]);
|
|
|
|
return idRows.map(row => row.hash);
|
|
|
|
} catch (e: any) {
|
|
|
|
logger.err(`Cannot fetch block audit from db. Reason: ` + (e instanceof Error ? e.message : e));
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
2022-07-06 22:27:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export default new BlocksAuditRepositories();
|
|
|
|
|