Implemented coinstatsindex indexing
This commit is contained in:
parent
c44896f53e
commit
73f76474dd
@ -407,7 +407,10 @@ class BitcoinRoutes {
|
||||
private async getBlocksByBulk(req: Request, res: Response) {
|
||||
try {
|
||||
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === false) { // Liquid, Bisq - Not implemented
|
||||
return res.status(404).send(`Not implemented`);
|
||||
return res.status(404).send(`This API is only available for Bitcoin networks`);
|
||||
}
|
||||
if (!Common.indexingEnabled()) {
|
||||
return res.status(404).send(`Indexing is required for this API`);
|
||||
}
|
||||
|
||||
const from = parseInt(req.params.from, 10);
|
||||
@ -423,7 +426,7 @@ class BitcoinRoutes {
|
||||
}
|
||||
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
res.json(await blocks.$getBlocksByBulk(from, to));
|
||||
res.json(await blocks.$getBlocksBetweenHeight(from, to));
|
||||
|
||||
} catch (e) {
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
|
@ -88,6 +88,7 @@ export namespace IEsploraApi {
|
||||
size: number;
|
||||
weight: number;
|
||||
previousblockhash: string;
|
||||
medianTime?: number;
|
||||
}
|
||||
|
||||
export interface Address {
|
||||
|
@ -165,33 +165,75 @@ class Blocks {
|
||||
* @returns BlockExtended
|
||||
*/
|
||||
private async $getBlockExtended(block: IEsploraApi.Block, transactions: TransactionExtended[]): Promise<BlockExtended> {
|
||||
const blockExtended: BlockExtended = Object.assign({ extras: {} }, block);
|
||||
blockExtended.extras.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0);
|
||||
blockExtended.extras.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]);
|
||||
blockExtended.extras.coinbaseRaw = blockExtended.extras.coinbaseTx.vin[0].scriptsig;
|
||||
blockExtended.extras.usd = priceUpdater.latestPrices.USD;
|
||||
const blk: BlockExtended = Object.assign({ extras: {} }, block);
|
||||
blk.extras.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0);
|
||||
blk.extras.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]);
|
||||
blk.extras.coinbaseRaw = blk.extras.coinbaseTx.vin[0].scriptsig;
|
||||
blk.extras.usd = priceUpdater.latestPrices.USD;
|
||||
|
||||
if (block.height === 0) {
|
||||
blockExtended.extras.medianFee = 0; // 50th percentiles
|
||||
blockExtended.extras.feeRange = [0, 0, 0, 0, 0, 0, 0];
|
||||
blockExtended.extras.totalFees = 0;
|
||||
blockExtended.extras.avgFee = 0;
|
||||
blockExtended.extras.avgFeeRate = 0;
|
||||
blk.extras.medianFee = 0; // 50th percentiles
|
||||
blk.extras.feeRange = [0, 0, 0, 0, 0, 0, 0];
|
||||
blk.extras.totalFees = 0;
|
||||
blk.extras.avgFee = 0;
|
||||
blk.extras.avgFeeRate = 0;
|
||||
blk.extras.utxoSetChange = 0;
|
||||
blk.extras.avgTxSize = 0;
|
||||
blk.extras.totalInputs = 0;
|
||||
blk.extras.totalOutputs = 1;
|
||||
blk.extras.totalOutputAmt = 0;
|
||||
blk.extras.segwitTotalTxs = 0;
|
||||
blk.extras.segwitTotalSize = 0;
|
||||
blk.extras.segwitTotalWeight = 0;
|
||||
} else {
|
||||
const stats = await bitcoinClient.getBlockStats(block.id, [
|
||||
'feerate_percentiles', 'minfeerate', 'maxfeerate', 'totalfee', 'avgfee', 'avgfeerate'
|
||||
]);
|
||||
blockExtended.extras.medianFee = stats.feerate_percentiles[2]; // 50th percentiles
|
||||
blockExtended.extras.feeRange = [stats.minfeerate, stats.feerate_percentiles, stats.maxfeerate].flat();
|
||||
blockExtended.extras.totalFees = stats.totalfee;
|
||||
blockExtended.extras.avgFee = stats.avgfee;
|
||||
blockExtended.extras.avgFeeRate = stats.avgfeerate;
|
||||
const stats = await bitcoinClient.getBlockStats(block.id);
|
||||
blk.extras.medianFee = stats.feerate_percentiles[2]; // 50th percentiles
|
||||
blk.extras.feeRange = [stats.minfeerate, stats.feerate_percentiles, stats.maxfeerate].flat();
|
||||
blk.extras.totalFees = stats.totalfee;
|
||||
blk.extras.avgFee = stats.avgfee;
|
||||
blk.extras.avgFeeRate = stats.avgfeerate;
|
||||
blk.extras.utxoSetChange = stats.utxo_increase;
|
||||
blk.extras.avgTxSize = Math.round(stats.total_size / stats.txs * 100) * 0.01;
|
||||
blk.extras.totalInputs = stats.ins;
|
||||
blk.extras.totalOutputs = stats.outs;
|
||||
blk.extras.totalOutputAmt = stats.total_out;
|
||||
blk.extras.segwitTotalTxs = stats.swtxs;
|
||||
blk.extras.segwitTotalSize = stats.swtotal_size;
|
||||
blk.extras.segwitTotalWeight = stats.swtotal_weight;
|
||||
}
|
||||
|
||||
blk.extras.feePercentiles = [], // TODO
|
||||
blk.extras.medianFeeAmt = 0; // TODO
|
||||
blk.extras.medianTimestamp = block.medianTime; // TODO
|
||||
blk.extras.blockTime = 0; // TODO
|
||||
blk.extras.orphaned = false; // TODO
|
||||
|
||||
blk.extras.virtualSize = block.weight / 4.0;
|
||||
if (blk.extras.coinbaseTx.vout.length > 0) {
|
||||
blk.extras.coinbaseAddress = blk.extras.coinbaseTx.vout[0].scriptpubkey_address ?? null;
|
||||
blk.extras.coinbaseSignature = blk.extras.coinbaseTx.vout[0].scriptpubkey_asm ?? null;
|
||||
} else {
|
||||
blk.extras.coinbaseAddress = null;
|
||||
blk.extras.coinbaseSignature = null;
|
||||
}
|
||||
|
||||
const header = await bitcoinClient.getBlockHeader(block.id, false);
|
||||
blk.extras.header = header;
|
||||
|
||||
const coinStatsIndex = indexer.isCoreIndexReady('coinstatsindex');
|
||||
if (coinStatsIndex !== null && coinStatsIndex.best_block_height >= block.height) {
|
||||
const txoutset = await bitcoinClient.getTxoutSetinfo('none', block.height);
|
||||
blk.extras.utxoSetSize = txoutset.txouts,
|
||||
blk.extras.totalInputAmt = Math.round(txoutset.block_info.prevout_spent * 100000000);
|
||||
} else {
|
||||
blk.extras.utxoSetSize = null;
|
||||
blk.extras.totalInputAmt = null;
|
||||
}
|
||||
|
||||
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK)) {
|
||||
let pool: PoolTag;
|
||||
if (blockExtended.extras?.coinbaseTx !== undefined) {
|
||||
pool = await this.$findBlockMiner(blockExtended.extras?.coinbaseTx);
|
||||
if (blk.extras?.coinbaseTx !== undefined) {
|
||||
pool = await this.$findBlockMiner(blk.extras?.coinbaseTx);
|
||||
} else {
|
||||
if (config.DATABASE.ENABLED === true) {
|
||||
pool = await poolsRepository.$getUnknownPool();
|
||||
@ -201,10 +243,10 @@ class Blocks {
|
||||
}
|
||||
|
||||
if (!pool) { // We should never have this situation in practise
|
||||
logger.warn(`Cannot assign pool to block ${blockExtended.height} and 'unknown' pool does not exist. ` +
|
||||
logger.warn(`Cannot assign pool to block ${blk.height} and 'unknown' pool does not exist. ` +
|
||||
`Check your "pools" table entries`);
|
||||
} else {
|
||||
blockExtended.extras.pool = {
|
||||
blk.extras.pool = {
|
||||
id: pool.id,
|
||||
name: pool.name,
|
||||
slug: pool.slug,
|
||||
@ -214,12 +256,12 @@ class Blocks {
|
||||
if (config.MEMPOOL.AUDIT) {
|
||||
const auditScore = await BlocksAuditsRepository.$getBlockAuditScore(block.id);
|
||||
if (auditScore != null) {
|
||||
blockExtended.extras.matchRate = auditScore.matchRate;
|
||||
blk.extras.matchRate = auditScore.matchRate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return blockExtended;
|
||||
return blk;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -727,60 +769,28 @@ class Blocks {
|
||||
return returnBlocks;
|
||||
}
|
||||
|
||||
public async $getBlocksByBulk(start: number, end: number) {
|
||||
start = Math.max(1, start);
|
||||
/**
|
||||
* Used for bulk block data query
|
||||
*
|
||||
* @param fromHeight
|
||||
* @param toHeight
|
||||
*/
|
||||
public async $getBlocksBetweenHeight(fromHeight: number, toHeight: number): Promise<any> {
|
||||
if (!Common.indexingEnabled()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const blocks: any[] = [];
|
||||
for (let i = end; i >= start; --i) {
|
||||
const blockHash = await bitcoinApi.$getBlockHash(i);
|
||||
const coreBlock = await bitcoinClient.getBlock(blockHash);
|
||||
const electrsBlock = await bitcoinApi.$getBlock(blockHash);
|
||||
const txs = await this.$getTransactionsExtended(blockHash, i, true);
|
||||
const stats = await bitcoinClient.getBlockStats(blockHash);
|
||||
const header = await bitcoinClient.getBlockHeader(blockHash, false);
|
||||
const txoutset = await bitcoinClient.getTxoutSetinfo('none', i);
|
||||
|
||||
const formatted = {
|
||||
blockhash: coreBlock.id,
|
||||
blockheight: coreBlock.height,
|
||||
prev_blockhash: coreBlock.previousblockhash,
|
||||
timestamp: coreBlock.timestamp,
|
||||
median_timestamp: coreBlock.mediantime,
|
||||
// @ts-ignore
|
||||
blocktime: coreBlock.time,
|
||||
orphaned: null,
|
||||
header: header,
|
||||
version: coreBlock.version,
|
||||
difficulty: coreBlock.difficulty,
|
||||
merkle_root: coreBlock.merkle_root,
|
||||
bits: coreBlock.bits,
|
||||
nonce: coreBlock.nonce,
|
||||
coinbase_scriptsig: txs[0].vin[0].scriptsig,
|
||||
coinbase_address: txs[0].vout[0].scriptpubkey_address,
|
||||
coinbase_signature: txs[0].vout[0].scriptpubkey_asm,
|
||||
size: coreBlock.size,
|
||||
virtual_size: coreBlock.weight / 4.0,
|
||||
weight: coreBlock.weight,
|
||||
utxoset_size: txoutset.txouts,
|
||||
utxoset_change: stats.utxo_increase,
|
||||
total_txs: coreBlock.tx_count,
|
||||
avg_tx_size: Math.round(stats.total_size / stats.txs * 100) * 0.01,
|
||||
total_inputs: stats.ins,
|
||||
total_outputs: stats.outs,
|
||||
total_input_amt: Math.round(txoutset.block_info.prevout_spent * 100000000),
|
||||
total_output_amt: stats.total_out,
|
||||
block_subsidy: txs[0].vout.reduce((acc, curr) => acc + curr.value, 0),
|
||||
total_fee: stats.totalfee,
|
||||
avg_feerate: stats.avgfeerate,
|
||||
feerate_percentiles: [stats.minfeerate, stats.feerate_percentiles, stats.maxfeerate].flat(),
|
||||
avg_fee: stats.avgfee,
|
||||
fee_percentiles: null,
|
||||
segwit_total_txs: stats.swtxs,
|
||||
segwit_total_size: stats.swtotal_size,
|
||||
segwit_total_weight: stats.swtotal_weight,
|
||||
};
|
||||
blocks.push(formatted);
|
||||
while (fromHeight <= toHeight) {
|
||||
let block = await blocksRepository.$getBlockByHeight(fromHeight);
|
||||
if (!block) {
|
||||
block = await this.$indexBlock(fromHeight);
|
||||
}
|
||||
blocks.push(block);
|
||||
fromHeight++;
|
||||
}
|
||||
|
||||
return blocks;
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
|
||||
import { RowDataPacket } from 'mysql2';
|
||||
|
||||
class DatabaseMigration {
|
||||
private static currentVersion = 54;
|
||||
private static currentVersion = 55;
|
||||
private queryTimeout = 3600_000;
|
||||
private statisticsAddedIndexed = false;
|
||||
private uniqueLogs: string[] = [];
|
||||
@ -483,6 +483,11 @@ class DatabaseMigration {
|
||||
}
|
||||
await this.updateToSchemaVersion(54);
|
||||
}
|
||||
|
||||
if (databaseSchemaVersion < 55) {
|
||||
await this.$executeQuery(this.getAdditionalBlocksDataQuery());
|
||||
await this.updateToSchemaVersion(55);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -756,6 +761,28 @@ class DatabaseMigration {
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
|
||||
}
|
||||
|
||||
private getAdditionalBlocksDataQuery(): string {
|
||||
return `ALTER TABLE blocks
|
||||
ADD median_timestamp timestamp NOT NULL,
|
||||
ADD block_time int unsigned NOT NULL,
|
||||
ADD coinbase_address varchar(100) NULL,
|
||||
ADD coinbase_signature varchar(500) NULL,
|
||||
ADD avg_tx_size double unsigned NOT NULL,
|
||||
ADD total_inputs int unsigned NOT NULL,
|
||||
ADD total_outputs int unsigned NOT NULL,
|
||||
ADD total_output_amt bigint unsigned NOT NULL,
|
||||
ADD fee_percentiles longtext NULL,
|
||||
ADD median_fee_amt int unsigned NOT NULL,
|
||||
ADD segwit_total_txs int unsigned NOT NULL,
|
||||
ADD segwit_total_size int unsigned NOT NULL,
|
||||
ADD segwit_total_weight int unsigned NOT NULL,
|
||||
ADD header varchar(160) NOT NULL,
|
||||
ADD utxoset_change int NOT NULL,
|
||||
ADD utxoset_size int unsigned NULL,
|
||||
ADD total_input_amt bigint unsigned NULL
|
||||
`;
|
||||
}
|
||||
|
||||
private getCreateDailyStatsTableQuery(): string {
|
||||
return `CREATE TABLE IF NOT EXISTS hashrates (
|
||||
hashrate_timestamp timestamp NOT NULL,
|
||||
|
@ -172,7 +172,7 @@ class Mining {
|
||||
}
|
||||
|
||||
/**
|
||||
* [INDEXING] Generate weekly mining pool hashrate history
|
||||
* Generate weekly mining pool hashrate history
|
||||
*/
|
||||
public async $generatePoolHashrateHistory(): Promise<void> {
|
||||
const now = new Date();
|
||||
@ -279,7 +279,7 @@ class Mining {
|
||||
}
|
||||
|
||||
/**
|
||||
* [INDEXING] Generate daily hashrate data
|
||||
* Generate daily hashrate data
|
||||
*/
|
||||
public async $generateNetworkHashrateHistory(): Promise<void> {
|
||||
// We only run this once a day around midnight
|
||||
@ -459,7 +459,7 @@ class Mining {
|
||||
/**
|
||||
* Create a link between blocks and the latest price at when they were mined
|
||||
*/
|
||||
public async $indexBlockPrices() {
|
||||
public async $indexBlockPrices(): Promise<void> {
|
||||
if (this.blocksPriceIndexingRunning === true) {
|
||||
return;
|
||||
}
|
||||
@ -520,6 +520,41 @@ class Mining {
|
||||
this.blocksPriceIndexingRunning = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Index core coinstatsindex
|
||||
*/
|
||||
public async $indexCoinStatsIndex(): Promise<void> {
|
||||
let timer = new Date().getTime() / 1000;
|
||||
let totalIndexed = 0;
|
||||
|
||||
const blockchainInfo = await bitcoinClient.getBlockchainInfo();
|
||||
let currentBlockHeight = blockchainInfo.blocks;
|
||||
|
||||
while (currentBlockHeight > 0) {
|
||||
const indexedBlocks = await BlocksRepository.$getBlocksMissingCoinStatsIndex(
|
||||
currentBlockHeight, currentBlockHeight - 10000);
|
||||
|
||||
for (const block of indexedBlocks) {
|
||||
const txoutset = await bitcoinClient.getTxoutSetinfo('none', block.height);
|
||||
await BlocksRepository.$updateCoinStatsIndexData(block.hash, txoutset.txouts,
|
||||
Math.round(txoutset.block_info.prevout_spent * 100000000));
|
||||
++totalIndexed;
|
||||
|
||||
const elapsedSeconds = Math.max(1, new Date().getTime() / 1000 - timer);
|
||||
if (elapsedSeconds > 5) {
|
||||
logger.info(`Indexing coinstatsindex data for block #${block.height}. Indexed ${totalIndexed} blocks.`, logger.tags.mining);
|
||||
timer = new Date().getTime() / 1000;
|
||||
}
|
||||
}
|
||||
|
||||
currentBlockHeight -= 10000;
|
||||
}
|
||||
|
||||
if (totalIndexed) {
|
||||
logger.info(`Indexing missing coinstatsindex data completed`, logger.tags.mining);
|
||||
}
|
||||
}
|
||||
|
||||
private getDateMidnight(date: Date): Date {
|
||||
date.setUTCHours(0);
|
||||
date.setUTCMinutes(0);
|
||||
|
@ -14,6 +14,7 @@ class TransactionUtils {
|
||||
vout: tx.vout
|
||||
.map((vout) => ({
|
||||
scriptpubkey_address: vout.scriptpubkey_address,
|
||||
scriptpubkey_asm: vout.scriptpubkey_asm,
|
||||
value: vout.value
|
||||
}))
|
||||
.filter((vout) => vout.value)
|
||||
|
@ -36,6 +36,7 @@ import bitcoinRoutes from './api/bitcoin/bitcoin.routes';
|
||||
import fundingTxFetcher from './tasks/lightning/sync-tasks/funding-tx-fetcher';
|
||||
import forensicsService from './tasks/lightning/forensics.service';
|
||||
import priceUpdater from './tasks/price-updater';
|
||||
import mining from './api/mining/mining';
|
||||
import { AxiosError } from 'axios';
|
||||
|
||||
class Server {
|
||||
|
@ -8,18 +8,67 @@ import bitcoinClient from './api/bitcoin/bitcoin-client';
|
||||
import priceUpdater from './tasks/price-updater';
|
||||
import PricesRepository from './repositories/PricesRepository';
|
||||
|
||||
export interface CoreIndex {
|
||||
name: string;
|
||||
synced: boolean;
|
||||
best_block_height: number;
|
||||
}
|
||||
|
||||
class Indexer {
|
||||
runIndexer = true;
|
||||
indexerRunning = false;
|
||||
tasksRunning: string[] = [];
|
||||
coreIndexes: CoreIndex[] = [];
|
||||
|
||||
public reindex() {
|
||||
/**
|
||||
* Check which core index is available for indexing
|
||||
*/
|
||||
public async checkAvailableCoreIndexes(): Promise<void> {
|
||||
const updatedCoreIndexes: CoreIndex[] = [];
|
||||
|
||||
const indexes: any = await bitcoinClient.getIndexInfo();
|
||||
for (const indexName in indexes) {
|
||||
const newState = {
|
||||
name: indexName,
|
||||
synced: indexes[indexName].synced,
|
||||
best_block_height: indexes[indexName].best_block_height,
|
||||
};
|
||||
logger.info(`Core index '${indexName}' is ${indexes[indexName].synced ? 'synced' : 'not synced'}. Best block height is ${indexes[indexName].best_block_height}`);
|
||||
updatedCoreIndexes.push(newState);
|
||||
|
||||
if (indexName === 'coinstatsindex' && newState.synced === true) {
|
||||
const previousState = this.isCoreIndexReady('coinstatsindex');
|
||||
// if (!previousState || previousState.synced === false) {
|
||||
this.runSingleTask('coinStatsIndex');
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
this.coreIndexes = updatedCoreIndexes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the best block height if a core index is available, or 0 if not
|
||||
*
|
||||
* @param name
|
||||
* @returns
|
||||
*/
|
||||
public isCoreIndexReady(name: string): CoreIndex | null {
|
||||
for (const index of this.coreIndexes) {
|
||||
if (index.name === name && index.synced === true) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public reindex(): void {
|
||||
if (Common.indexingEnabled()) {
|
||||
this.runIndexer = true;
|
||||
}
|
||||
}
|
||||
|
||||
public async runSingleTask(task: 'blocksPrices') {
|
||||
public async runSingleTask(task: 'blocksPrices' | 'coinStatsIndex'): Promise<void> {
|
||||
if (!Common.indexingEnabled()) {
|
||||
return;
|
||||
}
|
||||
@ -28,20 +77,27 @@ class Indexer {
|
||||
this.tasksRunning.push(task);
|
||||
const lastestPriceId = await PricesRepository.$getLatestPriceId();
|
||||
if (priceUpdater.historyInserted === false || lastestPriceId === null) {
|
||||
logger.debug(`Blocks prices indexer is waiting for the price updater to complete`)
|
||||
logger.debug(`Blocks prices indexer is waiting for the price updater to complete`);
|
||||
setTimeout(() => {
|
||||
this.tasksRunning = this.tasksRunning.filter(runningTask => runningTask != task)
|
||||
this.tasksRunning = this.tasksRunning.filter(runningTask => runningTask !== task);
|
||||
this.runSingleTask('blocksPrices');
|
||||
}, 10000);
|
||||
} else {
|
||||
logger.debug(`Blocks prices indexer will run now`)
|
||||
logger.debug(`Blocks prices indexer will run now`);
|
||||
await mining.$indexBlockPrices();
|
||||
this.tasksRunning = this.tasksRunning.filter(runningTask => runningTask != task)
|
||||
this.tasksRunning = this.tasksRunning.filter(runningTask => runningTask !== task);
|
||||
}
|
||||
}
|
||||
|
||||
if (task === 'coinStatsIndex' && !this.tasksRunning.includes(task)) {
|
||||
this.tasksRunning.push(task);
|
||||
logger.debug(`Indexing coinStatsIndex now`);
|
||||
await mining.$indexCoinStatsIndex();
|
||||
this.tasksRunning = this.tasksRunning.filter(runningTask => runningTask !== task);
|
||||
}
|
||||
}
|
||||
|
||||
public async $run() {
|
||||
public async $run(): Promise<void> {
|
||||
if (!Common.indexingEnabled() || this.runIndexer === false ||
|
||||
this.indexerRunning === true || mempool.hasPriority()
|
||||
) {
|
||||
@ -57,7 +113,9 @@ class Indexer {
|
||||
this.runIndexer = false;
|
||||
this.indexerRunning = true;
|
||||
|
||||
logger.debug(`Running mining indexer`);
|
||||
logger.info(`Running mining indexer`);
|
||||
|
||||
await this.checkAvailableCoreIndexes();
|
||||
|
||||
try {
|
||||
await priceUpdater.$run();
|
||||
@ -93,7 +151,7 @@ class Indexer {
|
||||
setTimeout(() => this.reindex(), runEvery);
|
||||
}
|
||||
|
||||
async $resetHashratesIndexingState() {
|
||||
async $resetHashratesIndexingState(): Promise<void> {
|
||||
try {
|
||||
await HashratesRepository.$setLatestRun('last_hashrates_indexing', 0);
|
||||
await HashratesRepository.$setLatestRun('last_weekly_hashrates_indexing', 0);
|
||||
|
@ -64,6 +64,7 @@ interface VinStrippedToScriptsig {
|
||||
|
||||
interface VoutStrippedToScriptPubkey {
|
||||
scriptpubkey_address: string | undefined;
|
||||
scriptpubkey_asm: string | undefined;
|
||||
value: number;
|
||||
}
|
||||
|
||||
@ -160,6 +161,26 @@ export interface BlockExtension {
|
||||
avgFeeRate?: number;
|
||||
coinbaseRaw?: string;
|
||||
usd?: number | null;
|
||||
medianTimestamp?: number;
|
||||
blockTime?: number;
|
||||
orphaned?: boolean;
|
||||
coinbaseAddress?: string | null;
|
||||
coinbaseSignature?: string | null;
|
||||
virtualSize?: number;
|
||||
avgTxSize?: number;
|
||||
totalInputs?: number;
|
||||
totalOutputs?: number;
|
||||
totalOutputAmt?: number;
|
||||
medianFeeAmt?: number;
|
||||
feePercentiles?: number[],
|
||||
segwitTotalTxs?: number;
|
||||
segwitTotalSize?: number;
|
||||
segwitTotalWeight?: number;
|
||||
header?: string;
|
||||
utxoSetChange?: number;
|
||||
// Requires coinstatsindex, will be set to NULL otherwise
|
||||
utxoSetSize?: number | null;
|
||||
totalInputAmt?: number | null;
|
||||
}
|
||||
|
||||
export interface BlockExtended extends IEsploraApi.Block {
|
||||
|
@ -18,17 +18,27 @@ class BlocksRepository {
|
||||
public async $saveBlockInDatabase(block: BlockExtended) {
|
||||
try {
|
||||
const query = `INSERT INTO blocks(
|
||||
height, hash, blockTimestamp, size,
|
||||
weight, tx_count, coinbase_raw, difficulty,
|
||||
pool_id, fees, fee_span, median_fee,
|
||||
reward, version, bits, nonce,
|
||||
merkle_root, previous_block_hash, avg_fee, avg_fee_rate
|
||||
height, hash, blockTimestamp, size,
|
||||
weight, tx_count, coinbase_raw, difficulty,
|
||||
pool_id, fees, fee_span, median_fee,
|
||||
reward, version, bits, nonce,
|
||||
merkle_root, previous_block_hash, avg_fee, avg_fee_rate,
|
||||
median_timestamp, block_time, header, coinbase_address,
|
||||
coinbase_signature, utxoset_size, utxoset_change, avg_tx_size,
|
||||
total_inputs, total_outputs, total_input_amt, total_output_amt,
|
||||
fee_percentiles, segwit_total_txs, segwit_total_size, segwit_total_weight,
|
||||
median_fee_amt
|
||||
) VALUE (
|
||||
?, ?, FROM_UNIXTIME(?), ?,
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?, ?
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?, ?,
|
||||
?, ?, ?, ?,
|
||||
?
|
||||
)`;
|
||||
|
||||
const params: any[] = [
|
||||
@ -52,6 +62,23 @@ class BlocksRepository {
|
||||
block.previousblockhash,
|
||||
block.extras.avgFee,
|
||||
block.extras.avgFeeRate,
|
||||
block.extras.medianTimestamp,
|
||||
block.extras.blockTime,
|
||||
block.extras.header,
|
||||
block.extras.coinbaseAddress,
|
||||
block.extras.coinbaseSignature,
|
||||
block.extras.utxoSetSize,
|
||||
block.extras.utxoSetChange,
|
||||
block.extras.avgTxSize,
|
||||
block.extras.totalInputs,
|
||||
block.extras.totalOutputs,
|
||||
block.extras.totalInputAmt,
|
||||
block.extras.totalOutputAmt,
|
||||
JSON.stringify(block.extras.feePercentiles),
|
||||
block.extras.segwitTotalTxs,
|
||||
block.extras.segwitTotalSize,
|
||||
block.extras.segwitTotalWeight,
|
||||
block.extras.medianFeeAmt,
|
||||
];
|
||||
|
||||
await DB.query(query, params);
|
||||
@ -65,6 +92,33 @@ class BlocksRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save newly indexed data from core coinstatsindex
|
||||
*
|
||||
* @param utxoSetSize
|
||||
* @param totalInputAmt
|
||||
*/
|
||||
public async $updateCoinStatsIndexData(blockHash: string, utxoSetSize: number,
|
||||
totalInputAmt: number
|
||||
) : Promise<void> {
|
||||
try {
|
||||
const query = `
|
||||
UPDATE blocks
|
||||
SET utxoset_size = ?, total_input_amt = ?
|
||||
WHERE hash = ?
|
||||
`;
|
||||
const params: any[] = [
|
||||
utxoSetSize,
|
||||
totalInputAmt,
|
||||
blockHash
|
||||
];
|
||||
await DB.query(query, params);
|
||||
} catch (e: any) {
|
||||
logger.err('Cannot update indexed block coinstatsindex. Reason: ' + (e instanceof Error ? e.message : e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all block height that have not been indexed between [startHeight, endHeight]
|
||||
*/
|
||||
@ -310,32 +364,16 @@ class BlocksRepository {
|
||||
public async $getBlockByHeight(height: number): Promise<object | null> {
|
||||
try {
|
||||
const [rows]: any[] = await DB.query(`SELECT
|
||||
blocks.height,
|
||||
hash,
|
||||
blocks.*,
|
||||
hash as id,
|
||||
UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp,
|
||||
size,
|
||||
weight,
|
||||
tx_count,
|
||||
coinbase_raw,
|
||||
difficulty,
|
||||
pools.id as pool_id,
|
||||
pools.name as pool_name,
|
||||
pools.link as pool_link,
|
||||
pools.slug as pool_slug,
|
||||
pools.addresses as pool_addresses,
|
||||
pools.regexes as pool_regexes,
|
||||
fees,
|
||||
fee_span,
|
||||
median_fee,
|
||||
reward,
|
||||
version,
|
||||
bits,
|
||||
nonce,
|
||||
merkle_root,
|
||||
previous_block_hash as previousblockhash,
|
||||
avg_fee,
|
||||
avg_fee_rate
|
||||
previous_block_hash as previousblockhash
|
||||
FROM blocks
|
||||
JOIN pools ON blocks.pool_id = pools.id
|
||||
WHERE blocks.height = ${height}
|
||||
@ -694,7 +732,6 @@ class BlocksRepository {
|
||||
logger.err('Cannot fetch CPFP unindexed blocks. Reason: ' + (e instanceof Error ? e.message : e));
|
||||
throw e;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -741,7 +778,7 @@ class BlocksRepository {
|
||||
try {
|
||||
let query = `INSERT INTO blocks_prices(height, price_id) VALUES`;
|
||||
for (const price of blockPrices) {
|
||||
query += ` (${price.height}, ${price.priceId}),`
|
||||
query += ` (${price.height}, ${price.priceId}),`;
|
||||
}
|
||||
query = query.slice(0, -1);
|
||||
await DB.query(query);
|
||||
@ -754,6 +791,24 @@ class BlocksRepository {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all indexed blocsk with missing coinstatsindex data
|
||||
*/
|
||||
public async $getBlocksMissingCoinStatsIndex(maxHeight: number, minHeight: number): Promise<any> {
|
||||
try {
|
||||
const [blocks] = await DB.query(`
|
||||
SELECT height, hash
|
||||
FROM blocks
|
||||
WHERE height >= ${minHeight} AND height <= ${maxHeight} AND
|
||||
(utxoset_size IS NULL OR total_input_amt IS NULL)
|
||||
`);
|
||||
return blocks;
|
||||
} catch (e) {
|
||||
logger.err(`Cannot get blocks with missing coinstatsindex. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new BlocksRepository();
|
||||
|
@ -89,5 +89,6 @@ module.exports = {
|
||||
walletLock: 'walletlock',
|
||||
walletPassphrase: 'walletpassphrase',
|
||||
walletPassphraseChange: 'walletpassphrasechange',
|
||||
getTxoutSetinfo: 'gettxoutsetinfo'
|
||||
}
|
||||
getTxoutSetinfo: 'gettxoutsetinfo',
|
||||
getIndexInfo: 'getindexinfo',
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user