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) {
 | 
					  private async getBlocksByBulk(req: Request, res: Response) {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === false) { // Liquid, Bisq - Not implemented
 | 
					      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);
 | 
					      const from = parseInt(req.params.from, 10);
 | 
				
			||||||
@ -423,7 +426,7 @@ class BitcoinRoutes {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
 | 
					      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) {
 | 
					    } catch (e) {
 | 
				
			||||||
      res.status(500).send(e instanceof Error ? e.message : e);
 | 
					      res.status(500).send(e instanceof Error ? e.message : e);
 | 
				
			||||||
 | 
				
			|||||||
@ -88,6 +88,7 @@ export namespace IEsploraApi {
 | 
				
			|||||||
    size: number;
 | 
					    size: number;
 | 
				
			||||||
    weight: number;
 | 
					    weight: number;
 | 
				
			||||||
    previousblockhash: string;
 | 
					    previousblockhash: string;
 | 
				
			||||||
 | 
					    medianTime?: number;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export interface Address {
 | 
					  export interface Address {
 | 
				
			||||||
 | 
				
			|||||||
@ -165,33 +165,75 @@ class Blocks {
 | 
				
			|||||||
   * @returns BlockExtended
 | 
					   * @returns BlockExtended
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  private async $getBlockExtended(block: IEsploraApi.Block, transactions: TransactionExtended[]): Promise<BlockExtended> {
 | 
					  private async $getBlockExtended(block: IEsploraApi.Block, transactions: TransactionExtended[]): Promise<BlockExtended> {
 | 
				
			||||||
    const blockExtended: BlockExtended = Object.assign({ extras: {} }, block);
 | 
					    const blk: BlockExtended = Object.assign({ extras: {} }, block);
 | 
				
			||||||
    blockExtended.extras.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0);
 | 
					    blk.extras.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0);
 | 
				
			||||||
    blockExtended.extras.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]);
 | 
					    blk.extras.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]);
 | 
				
			||||||
    blockExtended.extras.coinbaseRaw = blockExtended.extras.coinbaseTx.vin[0].scriptsig;
 | 
					    blk.extras.coinbaseRaw = blk.extras.coinbaseTx.vin[0].scriptsig;
 | 
				
			||||||
    blockExtended.extras.usd = priceUpdater.latestPrices.USD;
 | 
					    blk.extras.usd = priceUpdater.latestPrices.USD;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (block.height === 0) {
 | 
					    if (block.height === 0) {
 | 
				
			||||||
      blockExtended.extras.medianFee = 0; // 50th percentiles
 | 
					      blk.extras.medianFee = 0; // 50th percentiles
 | 
				
			||||||
      blockExtended.extras.feeRange = [0, 0, 0, 0, 0, 0, 0];
 | 
					      blk.extras.feeRange = [0, 0, 0, 0, 0, 0, 0];
 | 
				
			||||||
      blockExtended.extras.totalFees = 0;
 | 
					      blk.extras.totalFees = 0;
 | 
				
			||||||
      blockExtended.extras.avgFee = 0;
 | 
					      blk.extras.avgFee = 0;
 | 
				
			||||||
      blockExtended.extras.avgFeeRate = 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 {
 | 
					    } else {
 | 
				
			||||||
      const stats = await bitcoinClient.getBlockStats(block.id, [
 | 
					      const stats = await bitcoinClient.getBlockStats(block.id);
 | 
				
			||||||
        'feerate_percentiles', 'minfeerate', 'maxfeerate', 'totalfee', 'avgfee', 'avgfeerate'
 | 
					      blk.extras.medianFee = stats.feerate_percentiles[2]; // 50th percentiles
 | 
				
			||||||
      ]);
 | 
					      blk.extras.feeRange = [stats.minfeerate, stats.feerate_percentiles, stats.maxfeerate].flat();
 | 
				
			||||||
      blockExtended.extras.medianFee = stats.feerate_percentiles[2]; // 50th percentiles
 | 
					      blk.extras.totalFees = stats.totalfee;
 | 
				
			||||||
      blockExtended.extras.feeRange = [stats.minfeerate, stats.feerate_percentiles, stats.maxfeerate].flat();
 | 
					      blk.extras.avgFee = stats.avgfee;
 | 
				
			||||||
      blockExtended.extras.totalFees = stats.totalfee;
 | 
					      blk.extras.avgFeeRate = stats.avgfeerate;
 | 
				
			||||||
      blockExtended.extras.avgFee = stats.avgfee;
 | 
					      blk.extras.utxoSetChange = stats.utxo_increase;
 | 
				
			||||||
      blockExtended.extras.avgFeeRate = stats.avgfeerate;
 | 
					      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)) {
 | 
					    if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK)) {
 | 
				
			||||||
      let pool: PoolTag;
 | 
					      let pool: PoolTag;
 | 
				
			||||||
      if (blockExtended.extras?.coinbaseTx !== undefined) {
 | 
					      if (blk.extras?.coinbaseTx !== undefined) {
 | 
				
			||||||
        pool = await this.$findBlockMiner(blockExtended.extras?.coinbaseTx);
 | 
					        pool = await this.$findBlockMiner(blk.extras?.coinbaseTx);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        if (config.DATABASE.ENABLED === true) {
 | 
					        if (config.DATABASE.ENABLED === true) {
 | 
				
			||||||
          pool = await poolsRepository.$getUnknownPool();
 | 
					          pool = await poolsRepository.$getUnknownPool();
 | 
				
			||||||
@ -201,10 +243,10 @@ class Blocks {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (!pool) { // We should never have this situation in practise
 | 
					      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`);
 | 
					          `Check your "pools" table entries`);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        blockExtended.extras.pool = {
 | 
					        blk.extras.pool = {
 | 
				
			||||||
          id: pool.id,
 | 
					          id: pool.id,
 | 
				
			||||||
          name: pool.name,
 | 
					          name: pool.name,
 | 
				
			||||||
          slug: pool.slug,
 | 
					          slug: pool.slug,
 | 
				
			||||||
@ -214,12 +256,12 @@ class Blocks {
 | 
				
			|||||||
      if (config.MEMPOOL.AUDIT) {
 | 
					      if (config.MEMPOOL.AUDIT) {
 | 
				
			||||||
        const auditScore = await BlocksAuditsRepository.$getBlockAuditScore(block.id);
 | 
					        const auditScore = await BlocksAuditsRepository.$getBlockAuditScore(block.id);
 | 
				
			||||||
        if (auditScore != null) {
 | 
					        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;
 | 
					    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[] = [];
 | 
					    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 = {
 | 
					    while (fromHeight <= toHeight) {
 | 
				
			||||||
        blockhash: coreBlock.id,
 | 
					      let block = await blocksRepository.$getBlockByHeight(fromHeight);
 | 
				
			||||||
        blockheight: coreBlock.height,
 | 
					      if (!block) {
 | 
				
			||||||
        prev_blockhash: coreBlock.previousblockhash,
 | 
					        block = await this.$indexBlock(fromHeight);
 | 
				
			||||||
        timestamp: coreBlock.timestamp,
 | 
					      }
 | 
				
			||||||
        median_timestamp: coreBlock.mediantime,
 | 
					      blocks.push(block);
 | 
				
			||||||
        // @ts-ignore
 | 
					      fromHeight++;
 | 
				
			||||||
        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);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return blocks;
 | 
					    return blocks;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
 | 
				
			|||||||
import { RowDataPacket } from 'mysql2';
 | 
					import { RowDataPacket } from 'mysql2';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DatabaseMigration {
 | 
					class DatabaseMigration {
 | 
				
			||||||
  private static currentVersion = 54;
 | 
					  private static currentVersion = 55;
 | 
				
			||||||
  private queryTimeout = 3600_000;
 | 
					  private queryTimeout = 3600_000;
 | 
				
			||||||
  private statisticsAddedIndexed = false;
 | 
					  private statisticsAddedIndexed = false;
 | 
				
			||||||
  private uniqueLogs: string[] = [];
 | 
					  private uniqueLogs: string[] = [];
 | 
				
			||||||
@ -483,6 +483,11 @@ class DatabaseMigration {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
      await this.updateToSchemaVersion(54);
 | 
					      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;`;
 | 
					    ) 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 {
 | 
					  private getCreateDailyStatsTableQuery(): string {
 | 
				
			||||||
    return `CREATE TABLE IF NOT EXISTS hashrates (
 | 
					    return `CREATE TABLE IF NOT EXISTS hashrates (
 | 
				
			||||||
      hashrate_timestamp timestamp NOT NULL,
 | 
					      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> {
 | 
					  public async $generatePoolHashrateHistory(): Promise<void> {
 | 
				
			||||||
    const now = new Date();
 | 
					    const now = new Date();
 | 
				
			||||||
@ -279,7 +279,7 @@ class Mining {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * [INDEXING] Generate daily hashrate data
 | 
					   * Generate daily hashrate data
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  public async $generateNetworkHashrateHistory(): Promise<void> {
 | 
					  public async $generateNetworkHashrateHistory(): Promise<void> {
 | 
				
			||||||
    // We only run this once a day around midnight
 | 
					    // 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
 | 
					   * 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) {
 | 
					    if (this.blocksPriceIndexingRunning === true) {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -520,6 +520,41 @@ class Mining {
 | 
				
			|||||||
    this.blocksPriceIndexingRunning = false;
 | 
					    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 {
 | 
					  private getDateMidnight(date: Date): Date {
 | 
				
			||||||
    date.setUTCHours(0);
 | 
					    date.setUTCHours(0);
 | 
				
			||||||
    date.setUTCMinutes(0);
 | 
					    date.setUTCMinutes(0);
 | 
				
			||||||
 | 
				
			|||||||
@ -14,6 +14,7 @@ class TransactionUtils {
 | 
				
			|||||||
      vout: tx.vout
 | 
					      vout: tx.vout
 | 
				
			||||||
        .map((vout) => ({
 | 
					        .map((vout) => ({
 | 
				
			||||||
          scriptpubkey_address: vout.scriptpubkey_address,
 | 
					          scriptpubkey_address: vout.scriptpubkey_address,
 | 
				
			||||||
 | 
					          scriptpubkey_asm: vout.scriptpubkey_asm,
 | 
				
			||||||
          value: vout.value
 | 
					          value: vout.value
 | 
				
			||||||
        }))
 | 
					        }))
 | 
				
			||||||
        .filter((vout) => 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 fundingTxFetcher from './tasks/lightning/sync-tasks/funding-tx-fetcher';
 | 
				
			||||||
import forensicsService from './tasks/lightning/forensics.service';
 | 
					import forensicsService from './tasks/lightning/forensics.service';
 | 
				
			||||||
import priceUpdater from './tasks/price-updater';
 | 
					import priceUpdater from './tasks/price-updater';
 | 
				
			||||||
 | 
					import mining from './api/mining/mining';
 | 
				
			||||||
import { AxiosError } from 'axios';
 | 
					import { AxiosError } from 'axios';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Server {
 | 
					class Server {
 | 
				
			||||||
 | 
				
			|||||||
@ -8,18 +8,67 @@ import bitcoinClient from './api/bitcoin/bitcoin-client';
 | 
				
			|||||||
import priceUpdater from './tasks/price-updater';
 | 
					import priceUpdater from './tasks/price-updater';
 | 
				
			||||||
import PricesRepository from './repositories/PricesRepository';
 | 
					import PricesRepository from './repositories/PricesRepository';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface CoreIndex {
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  synced: boolean;
 | 
				
			||||||
 | 
					  best_block_height: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Indexer {
 | 
					class Indexer {
 | 
				
			||||||
  runIndexer = true;
 | 
					  runIndexer = true;
 | 
				
			||||||
  indexerRunning = false;
 | 
					  indexerRunning = false;
 | 
				
			||||||
  tasksRunning: string[] = [];
 | 
					  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()) {
 | 
					    if (Common.indexingEnabled()) {
 | 
				
			||||||
      this.runIndexer = true;
 | 
					      this.runIndexer = true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public async runSingleTask(task: 'blocksPrices') {
 | 
					  public async runSingleTask(task: 'blocksPrices' | 'coinStatsIndex'): Promise<void> {
 | 
				
			||||||
    if (!Common.indexingEnabled()) {
 | 
					    if (!Common.indexingEnabled()) {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -28,20 +77,27 @@ class Indexer {
 | 
				
			|||||||
      this.tasksRunning.push(task);
 | 
					      this.tasksRunning.push(task);
 | 
				
			||||||
      const lastestPriceId = await PricesRepository.$getLatestPriceId();
 | 
					      const lastestPriceId = await PricesRepository.$getLatestPriceId();
 | 
				
			||||||
      if (priceUpdater.historyInserted === false || lastestPriceId === null) {
 | 
					      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(() => {
 | 
					        setTimeout(() => {
 | 
				
			||||||
          this.tasksRunning = this.tasksRunning.filter(runningTask => runningTask != task)
 | 
					          this.tasksRunning = this.tasksRunning.filter(runningTask => runningTask !== task);
 | 
				
			||||||
          this.runSingleTask('blocksPrices');
 | 
					          this.runSingleTask('blocksPrices');
 | 
				
			||||||
        }, 10000);
 | 
					        }, 10000);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        logger.debug(`Blocks prices indexer will run now`)
 | 
					        logger.debug(`Blocks prices indexer will run now`);
 | 
				
			||||||
        await mining.$indexBlockPrices();
 | 
					        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 ||
 | 
					    if (!Common.indexingEnabled() || this.runIndexer === false ||
 | 
				
			||||||
      this.indexerRunning === true || mempool.hasPriority()
 | 
					      this.indexerRunning === true || mempool.hasPriority()
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
@ -57,7 +113,9 @@ class Indexer {
 | 
				
			|||||||
    this.runIndexer = false;
 | 
					    this.runIndexer = false;
 | 
				
			||||||
    this.indexerRunning = true;
 | 
					    this.indexerRunning = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    logger.debug(`Running mining indexer`);
 | 
					    logger.info(`Running mining indexer`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await this.checkAvailableCoreIndexes();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      await priceUpdater.$run();
 | 
					      await priceUpdater.$run();
 | 
				
			||||||
@ -93,7 +151,7 @@ class Indexer {
 | 
				
			|||||||
    setTimeout(() => this.reindex(), runEvery);
 | 
					    setTimeout(() => this.reindex(), runEvery);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async $resetHashratesIndexingState() {
 | 
					  async $resetHashratesIndexingState(): Promise<void> {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      await HashratesRepository.$setLatestRun('last_hashrates_indexing', 0);
 | 
					      await HashratesRepository.$setLatestRun('last_hashrates_indexing', 0);
 | 
				
			||||||
      await HashratesRepository.$setLatestRun('last_weekly_hashrates_indexing', 0);
 | 
					      await HashratesRepository.$setLatestRun('last_weekly_hashrates_indexing', 0);
 | 
				
			||||||
 | 
				
			|||||||
@ -64,6 +64,7 @@ interface VinStrippedToScriptsig {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
interface VoutStrippedToScriptPubkey {
 | 
					interface VoutStrippedToScriptPubkey {
 | 
				
			||||||
  scriptpubkey_address: string | undefined;
 | 
					  scriptpubkey_address: string | undefined;
 | 
				
			||||||
 | 
					  scriptpubkey_asm: string | undefined;
 | 
				
			||||||
  value: number;
 | 
					  value: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -160,6 +161,26 @@ export interface BlockExtension {
 | 
				
			|||||||
  avgFeeRate?: number;
 | 
					  avgFeeRate?: number;
 | 
				
			||||||
  coinbaseRaw?: string;
 | 
					  coinbaseRaw?: string;
 | 
				
			||||||
  usd?: number | null;
 | 
					  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 {
 | 
					export interface BlockExtended extends IEsploraApi.Block {
 | 
				
			||||||
 | 
				
			|||||||
@ -18,17 +18,27 @@ class BlocksRepository {
 | 
				
			|||||||
  public async $saveBlockInDatabase(block: BlockExtended) {
 | 
					  public async $saveBlockInDatabase(block: BlockExtended) {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const query = `INSERT INTO blocks(
 | 
					      const query = `INSERT INTO blocks(
 | 
				
			||||||
        height,           hash,                blockTimestamp, size,
 | 
					        height,             hash,                blockTimestamp,    size,
 | 
				
			||||||
        weight,           tx_count,            coinbase_raw,   difficulty,
 | 
					        weight,             tx_count,            coinbase_raw,      difficulty,
 | 
				
			||||||
        pool_id,          fees,                fee_span,       median_fee,
 | 
					        pool_id,            fees,                fee_span,          median_fee,
 | 
				
			||||||
        reward,           version,             bits,           nonce,
 | 
					        reward,             version,             bits,              nonce,
 | 
				
			||||||
        merkle_root,      previous_block_hash, avg_fee,        avg_fee_rate
 | 
					        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 (
 | 
					      ) VALUE (
 | 
				
			||||||
        ?, ?, FROM_UNIXTIME(?), ?,
 | 
					        ?, ?, FROM_UNIXTIME(?), ?,
 | 
				
			||||||
        ?, ?, ?, ?,
 | 
					        ?, ?, ?, ?,
 | 
				
			||||||
        ?, ?, ?, ?,
 | 
					        ?, ?, ?, ?,
 | 
				
			||||||
        ?, ?, ?, ?,
 | 
					        ?, ?, ?, ?,
 | 
				
			||||||
        ?, ?, ?, ?
 | 
					        ?, ?, ?, ?,
 | 
				
			||||||
 | 
					        ?, ?, ?, ?,
 | 
				
			||||||
 | 
					        ?, ?, ?, ?,
 | 
				
			||||||
 | 
					        ?, ?, ?, ?,
 | 
				
			||||||
 | 
					        ?, ?, ?, ?,
 | 
				
			||||||
 | 
					        ?
 | 
				
			||||||
      )`;
 | 
					      )`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const params: any[] = [
 | 
					      const params: any[] = [
 | 
				
			||||||
@ -52,6 +62,23 @@ class BlocksRepository {
 | 
				
			|||||||
        block.previousblockhash,
 | 
					        block.previousblockhash,
 | 
				
			||||||
        block.extras.avgFee,
 | 
					        block.extras.avgFee,
 | 
				
			||||||
        block.extras.avgFeeRate,
 | 
					        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);
 | 
					      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]
 | 
					   * 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> {
 | 
					  public async $getBlockByHeight(height: number): Promise<object | null> {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const [rows]: any[] = await DB.query(`SELECT
 | 
					      const [rows]: any[] = await DB.query(`SELECT
 | 
				
			||||||
        blocks.height,
 | 
					        blocks.*,
 | 
				
			||||||
        hash,
 | 
					 | 
				
			||||||
        hash as id,
 | 
					        hash as id,
 | 
				
			||||||
        UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp,
 | 
					        UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp,
 | 
				
			||||||
        size,
 | 
					 | 
				
			||||||
        weight,
 | 
					 | 
				
			||||||
        tx_count,
 | 
					 | 
				
			||||||
        coinbase_raw,
 | 
					 | 
				
			||||||
        difficulty,
 | 
					 | 
				
			||||||
        pools.id as pool_id,
 | 
					        pools.id as pool_id,
 | 
				
			||||||
        pools.name as pool_name,
 | 
					        pools.name as pool_name,
 | 
				
			||||||
        pools.link as pool_link,
 | 
					        pools.link as pool_link,
 | 
				
			||||||
        pools.slug as pool_slug,
 | 
					        pools.slug as pool_slug,
 | 
				
			||||||
        pools.addresses as pool_addresses,
 | 
					        pools.addresses as pool_addresses,
 | 
				
			||||||
        pools.regexes as pool_regexes,
 | 
					        pools.regexes as pool_regexes,
 | 
				
			||||||
        fees,
 | 
					        previous_block_hash as previousblockhash
 | 
				
			||||||
        fee_span,
 | 
					 | 
				
			||||||
        median_fee,
 | 
					 | 
				
			||||||
        reward,
 | 
					 | 
				
			||||||
        version,
 | 
					 | 
				
			||||||
        bits,
 | 
					 | 
				
			||||||
        nonce,
 | 
					 | 
				
			||||||
        merkle_root,
 | 
					 | 
				
			||||||
        previous_block_hash as previousblockhash,
 | 
					 | 
				
			||||||
        avg_fee,
 | 
					 | 
				
			||||||
        avg_fee_rate
 | 
					 | 
				
			||||||
        FROM blocks
 | 
					        FROM blocks
 | 
				
			||||||
        JOIN pools ON blocks.pool_id = pools.id
 | 
					        JOIN pools ON blocks.pool_id = pools.id
 | 
				
			||||||
        WHERE blocks.height = ${height}
 | 
					        WHERE blocks.height = ${height}
 | 
				
			||||||
@ -694,7 +732,6 @@ class BlocksRepository {
 | 
				
			|||||||
      logger.err('Cannot fetch CPFP unindexed blocks. Reason: ' + (e instanceof Error ? e.message : e));
 | 
					      logger.err('Cannot fetch CPFP unindexed blocks. Reason: ' + (e instanceof Error ? e.message : e));
 | 
				
			||||||
      throw e;
 | 
					      throw e;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return [];
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@ -741,7 +778,7 @@ class BlocksRepository {
 | 
				
			|||||||
    try {
 | 
					    try {
 | 
				
			||||||
      let query = `INSERT INTO blocks_prices(height, price_id) VALUES`;
 | 
					      let query = `INSERT INTO blocks_prices(height, price_id) VALUES`;
 | 
				
			||||||
      for (const price of blockPrices) {
 | 
					      for (const price of blockPrices) {
 | 
				
			||||||
        query += ` (${price.height}, ${price.priceId}),`
 | 
					        query += ` (${price.height}, ${price.priceId}),`;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      query = query.slice(0, -1);
 | 
					      query = query.slice(0, -1);
 | 
				
			||||||
      await DB.query(query);
 | 
					      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();
 | 
					export default new BlocksRepository();
 | 
				
			||||||
 | 
				
			|||||||
@ -89,5 +89,6 @@ module.exports = {
 | 
				
			|||||||
  walletLock: 'walletlock',
 | 
					  walletLock: 'walletlock',
 | 
				
			||||||
  walletPassphrase: 'walletpassphrase',
 | 
					  walletPassphrase: 'walletpassphrase',
 | 
				
			||||||
  walletPassphraseChange: 'walletpassphrasechange',
 | 
					  walletPassphraseChange: 'walletpassphrasechange',
 | 
				
			||||||
  getTxoutSetinfo: 'gettxoutsetinfo'
 | 
					  getTxoutSetinfo: 'gettxoutsetinfo',
 | 
				
			||||||
}
 | 
					  getIndexInfo: 'getindexinfo',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user