[Indexing] - Support 10 blocks depth reorgs
This commit is contained in:
		
							parent
							
								
									b0bb6cf8d6
								
							
						
					
					
						commit
						98224b5ddc
					
				@ -23,6 +23,7 @@ class Blocks {
 | 
				
			|||||||
  private newBlockCallbacks: ((block: BlockExtended, txIds: string[], transactions: TransactionExtended[]) => void)[] = [];
 | 
					  private newBlockCallbacks: ((block: BlockExtended, txIds: string[], transactions: TransactionExtended[]) => void)[] = [];
 | 
				
			||||||
  private blockIndexingStarted = false;
 | 
					  private blockIndexingStarted = false;
 | 
				
			||||||
  public blockIndexingCompleted = false;
 | 
					  public blockIndexingCompleted = false;
 | 
				
			||||||
 | 
					  public reindexFlag = true; // Always re-index the latest indexed data in case the node went offline with an invalid block tip (reorg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor() { }
 | 
					  constructor() { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -189,16 +190,19 @@ class Blocks {
 | 
				
			|||||||
   * [INDEXING] Index all blocks metadata for the mining dashboard
 | 
					   * [INDEXING] Index all blocks metadata for the mining dashboard
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  public async $generateBlockDatabase() {
 | 
					  public async $generateBlockDatabase() {
 | 
				
			||||||
    if (this.blockIndexingStarted) {
 | 
					    if (this.blockIndexingStarted && !this.reindexFlag) {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.reindexFlag = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const blockchainInfo = await bitcoinClient.getBlockchainInfo();
 | 
					    const blockchainInfo = await bitcoinClient.getBlockchainInfo();
 | 
				
			||||||
    if (blockchainInfo.blocks !== blockchainInfo.headers) { // Wait for node to sync
 | 
					    if (blockchainInfo.blocks !== blockchainInfo.headers) { // Wait for node to sync
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.blockIndexingStarted = true;
 | 
					    this.blockIndexingStarted = true;
 | 
				
			||||||
 | 
					    this.blockIndexingCompleted = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      let currentBlockHeight = blockchainInfo.blocks;
 | 
					      let currentBlockHeight = blockchainInfo.blocks;
 | 
				
			||||||
@ -316,6 +320,12 @@ class Blocks {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      if (Common.indexingEnabled()) {
 | 
					      if (Common.indexingEnabled()) {
 | 
				
			||||||
        await blocksRepository.$saveBlockInDatabase(blockExtended);
 | 
					        await blocksRepository.$saveBlockInDatabase(blockExtended);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // If the last 10 blocks chain is not valid, re-index them (reorg)
 | 
				
			||||||
 | 
					        const chainValid = await blocksRepository.$validateRecentBlocks();
 | 
				
			||||||
 | 
					        if (!chainValid) {
 | 
				
			||||||
 | 
					          this.reindexFlag = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (block.height % 2016 === 0) {
 | 
					      if (block.height % 2016 === 0) {
 | 
				
			||||||
 | 
				
			|||||||
@ -27,6 +27,7 @@ import icons from './api/liquid/icons';
 | 
				
			|||||||
import { Common } from './api/common';
 | 
					import { Common } from './api/common';
 | 
				
			||||||
import mining from './api/mining';
 | 
					import mining from './api/mining';
 | 
				
			||||||
import HashratesRepository from './repositories/HashratesRepository';
 | 
					import HashratesRepository from './repositories/HashratesRepository';
 | 
				
			||||||
 | 
					import BlocksRepository from './repositories/BlocksRepository';
 | 
				
			||||||
import poolsUpdater from './tasks/pools-updater';
 | 
					import poolsUpdater from './tasks/pools-updater';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Server {
 | 
					class Server {
 | 
				
			||||||
@ -179,6 +180,10 @@ class Server {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      await poolsUpdater.updatePoolsJson();
 | 
					      await poolsUpdater.updatePoolsJson();
 | 
				
			||||||
 | 
					      if (blocks.reindexFlag) {
 | 
				
			||||||
 | 
					        await BlocksRepository.$deleteBlocks(10);
 | 
				
			||||||
 | 
					        await HashratesRepository.$deleteLastEntries();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      blocks.$generateBlockDatabase();
 | 
					      blocks.$generateBlockDatabase();
 | 
				
			||||||
      await mining.$generateNetworkHashrateHistory();
 | 
					      await mining.$generateNetworkHashrateHistory();
 | 
				
			||||||
      await mining.$generatePoolHashrateHistory();
 | 
					      await mining.$generatePoolHashrateHistory();
 | 
				
			||||||
 | 
				
			|||||||
@ -10,9 +10,11 @@ class BlocksRepository {
 | 
				
			|||||||
   * Save indexed block data in the database
 | 
					   * Save indexed block data in the database
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  public async $saveBlockInDatabase(block: BlockExtended) {
 | 
					  public async $saveBlockInDatabase(block: BlockExtended) {
 | 
				
			||||||
    const connection = await DB.getConnection();
 | 
					    let connection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
 | 
					      connection = await DB.getConnection();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      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,
 | 
				
			||||||
@ -72,8 +74,9 @@ class BlocksRepository {
 | 
				
			|||||||
      return [];
 | 
					      return [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const connection = await DB.getConnection();
 | 
					    let connection;
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
 | 
					      connection = await DB.getConnection();
 | 
				
			||||||
      const [rows]: any[] = await connection.query(`
 | 
					      const [rows]: any[] = await connection.query(`
 | 
				
			||||||
        SELECT height
 | 
					        SELECT height
 | 
				
			||||||
        FROM blocks
 | 
					        FROM blocks
 | 
				
			||||||
@ -118,8 +121,9 @@ class BlocksRepository {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    query += ` GROUP by pools.id`;
 | 
					    query += ` GROUP by pools.id`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const connection = await DB.getConnection();
 | 
					    let connection;
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
 | 
					      connection = await DB.getConnection();
 | 
				
			||||||
      const [rows] = await connection.query(query, params);
 | 
					      const [rows] = await connection.query(query, params);
 | 
				
			||||||
      connection.release();
 | 
					      connection.release();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -155,8 +159,9 @@ class BlocksRepository {
 | 
				
			|||||||
      query += ` blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
 | 
					      query += ` blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const connection = await DB.getConnection();
 | 
					    let connection;
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
 | 
					      connection = await DB.getConnection();
 | 
				
			||||||
      const [rows] = await connection.query(query, params);
 | 
					      const [rows] = await connection.query(query, params);
 | 
				
			||||||
      connection.release();
 | 
					      connection.release();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -194,8 +199,9 @@ class BlocksRepository {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    query += ` blockTimestamp BETWEEN FROM_UNIXTIME('${from}') AND FROM_UNIXTIME('${to}')`;
 | 
					    query += ` blockTimestamp BETWEEN FROM_UNIXTIME('${from}') AND FROM_UNIXTIME('${to}')`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const connection = await DB.getConnection();
 | 
					    let connection;
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
 | 
					      connection = await DB.getConnection();
 | 
				
			||||||
      const [rows] = await connection.query(query, params);
 | 
					      const [rows] = await connection.query(query, params);
 | 
				
			||||||
      connection.release();
 | 
					      connection.release();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -216,8 +222,9 @@ class BlocksRepository {
 | 
				
			|||||||
      ORDER BY height
 | 
					      ORDER BY height
 | 
				
			||||||
      LIMIT 1;`;
 | 
					      LIMIT 1;`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const connection = await DB.getConnection();
 | 
					    let connection;
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
 | 
					      connection = await DB.getConnection();
 | 
				
			||||||
      const [rows]: any[] = await connection.query(query);
 | 
					      const [rows]: any[] = await connection.query(query);
 | 
				
			||||||
      connection.release();
 | 
					      connection.release();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -257,8 +264,9 @@ class BlocksRepository {
 | 
				
			|||||||
    query += ` ORDER BY height DESC
 | 
					    query += ` ORDER BY height DESC
 | 
				
			||||||
      LIMIT 10`;
 | 
					      LIMIT 10`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const connection = await DB.getConnection();
 | 
					    let connection;
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
 | 
					      connection = await DB.getConnection();
 | 
				
			||||||
      const [rows] = await connection.query(query, params);
 | 
					      const [rows] = await connection.query(query, params);
 | 
				
			||||||
      connection.release();
 | 
					      connection.release();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -279,8 +287,9 @@ class BlocksRepository {
 | 
				
			|||||||
   * Get one block by height
 | 
					   * Get one block by height
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  public async $getBlockByHeight(height: number): Promise<object | null> {
 | 
					  public async $getBlockByHeight(height: number): Promise<object | null> {
 | 
				
			||||||
    const connection = await DB.getConnection();
 | 
					    let connection;
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
 | 
					      connection = await DB.getConnection();
 | 
				
			||||||
      const [rows]: any[] = await connection.query(`
 | 
					      const [rows]: any[] = await connection.query(`
 | 
				
			||||||
        SELECT *, UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp,
 | 
					        SELECT *, UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp,
 | 
				
			||||||
        pools.id as pool_id, pools.name as pool_name, pools.link as pool_link, pools.slug as pool_slug,
 | 
					        pools.id as pool_id, pools.name as pool_name, pools.link as pool_link, pools.slug as pool_slug,
 | 
				
			||||||
@ -310,8 +319,6 @@ class BlocksRepository {
 | 
				
			|||||||
  public async $getBlocksDifficulty(interval: string | null): Promise<object[]> {
 | 
					  public async $getBlocksDifficulty(interval: string | null): Promise<object[]> {
 | 
				
			||||||
    interval = Common.getSqlInterval(interval);
 | 
					    interval = Common.getSqlInterval(interval);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const connection = await DB.getConnection();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // :D ... Yeah don't ask me about this one https://stackoverflow.com/a/40303162
 | 
					    // :D ... Yeah don't ask me about this one https://stackoverflow.com/a/40303162
 | 
				
			||||||
    // Basically, using temporary user defined fields, we are able to extract all
 | 
					    // Basically, using temporary user defined fields, we are able to extract all
 | 
				
			||||||
    // difficulty adjustments from the blocks tables.
 | 
					    // difficulty adjustments from the blocks tables.
 | 
				
			||||||
@ -344,14 +351,17 @@ class BlocksRepository {
 | 
				
			|||||||
      ORDER BY t.height
 | 
					      ORDER BY t.height
 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let connection;
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
 | 
					      connection = await DB.getConnection();
 | 
				
			||||||
      const [rows]: any[] = await connection.query(query);
 | 
					      const [rows]: any[] = await connection.query(query);
 | 
				
			||||||
      connection.release();
 | 
					      connection.release();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      for (let row of rows) {
 | 
					      for (const row of rows) {
 | 
				
			||||||
        delete row['rn'];
 | 
					        delete row['rn'];
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      connection.release();
 | 
				
			||||||
      return rows;
 | 
					      return rows;
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      connection.release();
 | 
					      connection.release();
 | 
				
			||||||
@ -386,6 +396,49 @@ class BlocksRepository {
 | 
				
			|||||||
      throw e;
 | 
					      throw e;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /*
 | 
				
			||||||
 | 
					   * Check if the last 10 blocks chain is valid
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async $validateRecentBlocks(): Promise<boolean> {
 | 
				
			||||||
 | 
					    let connection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      connection = await DB.getConnection();
 | 
				
			||||||
 | 
					      const [lastBlocks] = await connection.query(`SELECT height, hash, previous_block_hash FROM blocks ORDER BY height DESC LIMIT 10`);
 | 
				
			||||||
 | 
					      connection.release();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      for (let i = 0; i < lastBlocks.length - 1; ++i) {
 | 
				
			||||||
 | 
					        if (lastBlocks[i].previous_block_hash !== lastBlocks[i + 1].hash) {
 | 
				
			||||||
 | 
					          logger.notice(`Chain divergence detected at block ${lastBlocks[i].height}, re-indexing most recent data`);
 | 
				
			||||||
 | 
					          return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      connection.release();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return true; // Don't do anything if there is a db error
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Delete $count blocks from the database
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async $deleteBlocks(count: number) {
 | 
				
			||||||
 | 
					    let connection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      connection = await DB.getConnection();
 | 
				
			||||||
 | 
					      logger.debug(`Delete ${count} most recent indexed blocks from the database`);
 | 
				
			||||||
 | 
					      await connection.query(`DELETE FROM blocks ORDER BY height DESC LIMIT ${count};`);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      logger.err('$deleteBlocks() error' + (e instanceof Error ? e.message : e));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    connection.release();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default new BlocksRepository();
 | 
					export default new BlocksRepository();
 | 
				
			||||||
 | 
				
			|||||||
@ -169,6 +169,9 @@ class HashratesRepository {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Set latest run timestamp
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
  public async $setLatestRunTimestamp(key: string, val: any = null) {
 | 
					  public async $setLatestRunTimestamp(key: string, val: any = null) {
 | 
				
			||||||
    const connection = await DB.getConnection();
 | 
					    const connection = await DB.getConnection();
 | 
				
			||||||
    const query = `UPDATE state SET number = ? WHERE name = ?`;
 | 
					    const query = `UPDATE state SET number = ? WHERE name = ?`;
 | 
				
			||||||
@ -181,6 +184,9 @@ class HashratesRepository {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Get latest run timestamp
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
  public async $getLatestRunTimestamp(key: string): Promise<number> {
 | 
					  public async $getLatestRunTimestamp(key: string): Promise<number> {
 | 
				
			||||||
    const connection = await DB.getConnection();
 | 
					    const connection = await DB.getConnection();
 | 
				
			||||||
    const query = `SELECT number FROM state WHERE name = ?`;
 | 
					    const query = `SELECT number FROM state WHERE name = ?`;
 | 
				
			||||||
@ -199,6 +205,29 @@ class HashratesRepository {
 | 
				
			|||||||
      throw e;
 | 
					      throw e;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Delete most recent data points for re-indexing
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async $deleteLastEntries() {
 | 
				
			||||||
 | 
					    logger.debug(`Delete latest hashrates data points from the database`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let connection;
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      connection = await DB.getConnection();
 | 
				
			||||||
 | 
					      const [rows] = await connection.query(`SELECT MAX(hashrate_timestamp) as timestamp FROM hashrates GROUP BY type`);
 | 
				
			||||||
 | 
					      for (const row of rows) {
 | 
				
			||||||
 | 
					        await connection.query(`DELETE FROM hashrates WHERE hashrate_timestamp = ?`, [row.timestamp]);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      // Re-run the hashrate indexing to fill up missing data
 | 
				
			||||||
 | 
					      await this.$setLatestRunTimestamp('last_hashrates_indexing', 0);
 | 
				
			||||||
 | 
					      await this.$setLatestRunTimestamp('last_weekly_hashrates_indexing', 0);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      logger.err('$deleteLastEntries() error' + (e instanceof Error ? e.message : e));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    connection.release();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default new HashratesRepository();
 | 
					export default new HashratesRepository();
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user