Merge pull request #1517 from mempool/nymkappa/feature/refactor-db
Refactor database pool use
This commit is contained in:
		
						commit
						5d8f4f4c7e
					
				@ -1,6 +1,5 @@
 | 
			
		||||
import { PoolConnection } from 'mysql2/promise';
 | 
			
		||||
import config from '../config';
 | 
			
		||||
import { DB } from '../database';
 | 
			
		||||
import DB from '../database';
 | 
			
		||||
import logger from '../logger';
 | 
			
		||||
 | 
			
		||||
const sleep = (ms: number) => new Promise(res => setTimeout(res, ms));
 | 
			
		||||
@ -77,116 +76,112 @@ class DatabaseMigration {
 | 
			
		||||
    await this.$setStatisticsAddedIndexedFlag(databaseSchemaVersion);
 | 
			
		||||
 | 
			
		||||
    const isBitcoin = ['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK);
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
    try {
 | 
			
		||||
      await this.$executeQuery(connection, this.getCreateElementsTableQuery(), await this.$checkIfTableExists('elements_pegs'));
 | 
			
		||||
      await this.$executeQuery(connection, this.getCreateStatisticsQuery(), await this.$checkIfTableExists('statistics'));
 | 
			
		||||
      await this.$executeQuery(this.getCreateElementsTableQuery(), await this.$checkIfTableExists('elements_pegs'));
 | 
			
		||||
      await this.$executeQuery(this.getCreateStatisticsQuery(), await this.$checkIfTableExists('statistics'));
 | 
			
		||||
      if (databaseSchemaVersion < 2 && this.statisticsAddedIndexed === false) {
 | 
			
		||||
        await this.$executeQuery(connection, `CREATE INDEX added ON statistics (added);`);
 | 
			
		||||
        await this.$executeQuery(`CREATE INDEX added ON statistics (added);`);
 | 
			
		||||
      }
 | 
			
		||||
      if (databaseSchemaVersion < 3) {
 | 
			
		||||
        await this.$executeQuery(connection, this.getCreatePoolsTableQuery(), await this.$checkIfTableExists('pools'));
 | 
			
		||||
        await this.$executeQuery(this.getCreatePoolsTableQuery(), await this.$checkIfTableExists('pools'));
 | 
			
		||||
      }
 | 
			
		||||
      if (databaseSchemaVersion < 4) {
 | 
			
		||||
        await this.$executeQuery(connection, 'DROP table IF EXISTS blocks;');
 | 
			
		||||
        await this.$executeQuery(connection, this.getCreateBlocksTableQuery(), await this.$checkIfTableExists('blocks'));
 | 
			
		||||
        await this.$executeQuery('DROP table IF EXISTS blocks;');
 | 
			
		||||
        await this.$executeQuery(this.getCreateBlocksTableQuery(), await this.$checkIfTableExists('blocks'));
 | 
			
		||||
      }
 | 
			
		||||
      if (databaseSchemaVersion < 5 && isBitcoin === true) {
 | 
			
		||||
        logger.warn(`'blocks' table has been truncated. Re-indexing from scratch.`);
 | 
			
		||||
        await this.$executeQuery(connection, 'TRUNCATE blocks;'); // Need to re-index
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE blocks ADD `reward` double unsigned NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery('TRUNCATE blocks;'); // Need to re-index
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE blocks ADD `reward` double unsigned NOT NULL DEFAULT "0"');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (databaseSchemaVersion < 6 && isBitcoin === true) {
 | 
			
		||||
        logger.warn(`'blocks' table has been truncated. Re-indexing from scratch.`);
 | 
			
		||||
        await this.$executeQuery(connection, 'TRUNCATE blocks;');  // Need to re-index
 | 
			
		||||
        await this.$executeQuery('TRUNCATE blocks;');  // Need to re-index
 | 
			
		||||
        // Cleanup original blocks fields type
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE blocks MODIFY `height` integer unsigned NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE blocks MODIFY `tx_count` smallint unsigned NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE blocks MODIFY `size` integer unsigned NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE blocks MODIFY `weight` integer unsigned NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE blocks MODIFY `difficulty` double NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE blocks MODIFY `height` integer unsigned NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE blocks MODIFY `tx_count` smallint unsigned NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE blocks MODIFY `size` integer unsigned NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE blocks MODIFY `weight` integer unsigned NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE blocks MODIFY `difficulty` double NOT NULL DEFAULT "0"');
 | 
			
		||||
        // We also fix the pools.id type so we need to drop/re-create the foreign key
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE blocks DROP FOREIGN KEY IF EXISTS `blocks_ibfk_1`');
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE pools MODIFY `id` smallint unsigned AUTO_INCREMENT');
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE blocks MODIFY `pool_id` smallint unsigned NULL');
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE blocks ADD FOREIGN KEY (`pool_id`) REFERENCES `pools` (`id`)');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE blocks DROP FOREIGN KEY IF EXISTS `blocks_ibfk_1`');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE pools MODIFY `id` smallint unsigned AUTO_INCREMENT');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE blocks MODIFY `pool_id` smallint unsigned NULL');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE blocks ADD FOREIGN KEY (`pool_id`) REFERENCES `pools` (`id`)');
 | 
			
		||||
        // Add new block indexing fields
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE blocks ADD `version` integer unsigned NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE blocks ADD `bits` integer unsigned NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE blocks ADD `nonce` bigint unsigned NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE blocks ADD `merkle_root` varchar(65) NOT NULL DEFAULT ""');
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE blocks ADD `previous_block_hash` varchar(65) NULL');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE blocks ADD `version` integer unsigned NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE blocks ADD `bits` integer unsigned NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE blocks ADD `nonce` bigint unsigned NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE blocks ADD `merkle_root` varchar(65) NOT NULL DEFAULT ""');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE blocks ADD `previous_block_hash` varchar(65) NULL');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (databaseSchemaVersion < 7 && isBitcoin === true) {
 | 
			
		||||
        await this.$executeQuery(connection, 'DROP table IF EXISTS hashrates;');
 | 
			
		||||
        await this.$executeQuery(connection, this.getCreateDailyStatsTableQuery(), await this.$checkIfTableExists('hashrates'));
 | 
			
		||||
        await this.$executeQuery('DROP table IF EXISTS hashrates;');
 | 
			
		||||
        await this.$executeQuery(this.getCreateDailyStatsTableQuery(), await this.$checkIfTableExists('hashrates'));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (databaseSchemaVersion < 8 && isBitcoin === true) {
 | 
			
		||||
        logger.warn(`'hashrates' table has been truncated. Re-indexing from scratch.`);
 | 
			
		||||
        await this.$executeQuery(connection, 'TRUNCATE hashrates;'); // Need to re-index
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE `hashrates` DROP INDEX `PRIMARY`');
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE `hashrates` ADD `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST');
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE `hashrates` ADD `share` float NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE `hashrates` ADD `type` enum("daily", "weekly") DEFAULT "daily"');
 | 
			
		||||
        await this.$executeQuery('TRUNCATE hashrates;'); // Need to re-index
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE `hashrates` DROP INDEX `PRIMARY`');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE `hashrates` ADD `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE `hashrates` ADD `share` float NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE `hashrates` ADD `type` enum("daily", "weekly") DEFAULT "daily"');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (databaseSchemaVersion < 9 && isBitcoin === true) {
 | 
			
		||||
        logger.warn(`'hashrates' table has been truncated. Re-indexing from scratch.`);
 | 
			
		||||
        await this.$executeQuery(connection, 'TRUNCATE hashrates;'); // Need to re-index
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE `state` CHANGE `name` `name` varchar(100)');
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE `hashrates` ADD UNIQUE `hashrate_timestamp_pool_id` (`hashrate_timestamp`, `pool_id`)');
 | 
			
		||||
        await this.$executeQuery('TRUNCATE hashrates;'); // Need to re-index
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE `state` CHANGE `name` `name` varchar(100)');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE `hashrates` ADD UNIQUE `hashrate_timestamp_pool_id` (`hashrate_timestamp`, `pool_id`)');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (databaseSchemaVersion < 10 && isBitcoin === true) {
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE `blocks` ADD INDEX `blockTimestamp` (`blockTimestamp`)');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE `blocks` ADD INDEX `blockTimestamp` (`blockTimestamp`)');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (databaseSchemaVersion < 11 && isBitcoin === true) {
 | 
			
		||||
        logger.warn(`'blocks' table has been truncated. Re-indexing from scratch.`);
 | 
			
		||||
        await this.$executeQuery(connection, 'TRUNCATE blocks;'); // Need to re-index
 | 
			
		||||
        await this.$executeQuery(connection, `ALTER TABLE blocks
 | 
			
		||||
        await this.$executeQuery('TRUNCATE blocks;'); // Need to re-index
 | 
			
		||||
        await this.$executeQuery(`ALTER TABLE blocks
 | 
			
		||||
          ADD avg_fee INT UNSIGNED NULL,
 | 
			
		||||
          ADD avg_fee_rate INT UNSIGNED NULL
 | 
			
		||||
        `);
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE blocks MODIFY `reward` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE blocks MODIFY `median_fee` INT UNSIGNED NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE blocks MODIFY `fees` INT UNSIGNED NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE blocks MODIFY `reward` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE blocks MODIFY `median_fee` INT UNSIGNED NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE blocks MODIFY `fees` INT UNSIGNED NOT NULL DEFAULT "0"');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (databaseSchemaVersion < 12 && isBitcoin === true) {
 | 
			
		||||
        // No need to re-index because the new data type can contain larger values
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE blocks MODIFY `fees` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE blocks MODIFY `fees` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (databaseSchemaVersion < 13 && isBitcoin === true) {
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE blocks MODIFY `difficulty` DOUBLE UNSIGNED NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE blocks MODIFY `median_fee` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE blocks MODIFY `avg_fee` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE blocks MODIFY `avg_fee_rate` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE blocks MODIFY `difficulty` DOUBLE UNSIGNED NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE blocks MODIFY `median_fee` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE blocks MODIFY `avg_fee` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE blocks MODIFY `avg_fee_rate` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (databaseSchemaVersion < 14 && isBitcoin === true) {
 | 
			
		||||
        logger.warn(`'hashrates' table has been truncated. Re-indexing from scratch.`);
 | 
			
		||||
        await this.$executeQuery(connection, 'TRUNCATE hashrates;'); // Need to re-index
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE `hashrates` DROP FOREIGN KEY `hashrates_ibfk_1`');
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE `hashrates` MODIFY `pool_id` SMALLINT UNSIGNED NOT NULL DEFAULT "0"');
 | 
			
		||||
        await this.$executeQuery('TRUNCATE hashrates;'); // Need to re-index
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE `hashrates` DROP FOREIGN KEY `hashrates_ibfk_1`');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE `hashrates` MODIFY `pool_id` SMALLINT UNSIGNED NOT NULL DEFAULT "0"');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (databaseSchemaVersion < 16 && isBitcoin === true) {
 | 
			
		||||
        logger.warn(`'hashrates' table has been truncated. Re-indexing from scratch.`);
 | 
			
		||||
        await this.$executeQuery(connection, 'TRUNCATE hashrates;'); // Need to re-index because we changed timestamps
 | 
			
		||||
        await this.$executeQuery('TRUNCATE hashrates;'); // Need to re-index because we changed timestamps
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (databaseSchemaVersion < 17 && isBitcoin === true) {
 | 
			
		||||
        await this.$executeQuery(connection, 'ALTER TABLE `pools` ADD `slug` CHAR(50) NULL');
 | 
			
		||||
        await this.$executeQuery('ALTER TABLE `pools` ADD `slug` CHAR(50) NULL');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      connection.release();
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -203,13 +198,11 @@ class DatabaseMigration {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      // We don't use "CREATE INDEX IF NOT EXISTS" because it is not supported on old mariadb version 5.X
 | 
			
		||||
      const query = `SELECT COUNT(1) hasIndex FROM INFORMATION_SCHEMA.STATISTICS
 | 
			
		||||
        WHERE table_schema=DATABASE() AND table_name='statistics' AND index_name='added';`;
 | 
			
		||||
      const [rows] = await this.$executeQuery(connection, query, true);
 | 
			
		||||
      const [rows] = await this.$executeQuery(query, true);
 | 
			
		||||
      if (rows[0].hasIndex === 0) {
 | 
			
		||||
        logger.debug('MIGRATIONS: `statistics.added` is not indexed');
 | 
			
		||||
        this.statisticsAddedIndexed = false;
 | 
			
		||||
@ -223,28 +216,24 @@ class DatabaseMigration {
 | 
			
		||||
      logger.err('MIGRATIONS: Unable to check if `statistics.added` INDEX exist or not.');
 | 
			
		||||
      this.statisticsAddedIndexed = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    connection.release();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Small query execution wrapper to log all executed queries
 | 
			
		||||
   */
 | 
			
		||||
  private async $executeQuery(connection: PoolConnection, query: string, silent: boolean = false): Promise<any> {
 | 
			
		||||
  private async $executeQuery(query: string, silent: boolean = false): Promise<any> {
 | 
			
		||||
    if (!silent) {
 | 
			
		||||
      logger.debug('MIGRATIONS: Execute query:\n' + query);
 | 
			
		||||
    }
 | 
			
		||||
    return connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
    return DB.query({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Check if 'table' exists in the database
 | 
			
		||||
   */
 | 
			
		||||
  private async $checkIfTableExists(table: string): Promise<boolean> {
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
    const query = `SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '${config.DATABASE.DATABASE}' AND TABLE_NAME = '${table}'`;
 | 
			
		||||
    const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
    connection.release();
 | 
			
		||||
    const [rows] = await DB.query({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
    return rows[0]['COUNT(*)'] === 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -252,10 +241,8 @@ class DatabaseMigration {
 | 
			
		||||
   * Get current database version
 | 
			
		||||
   */
 | 
			
		||||
  private async $getSchemaVersionFromDatabase(): Promise<number> {
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
    const query = `SELECT number FROM state WHERE name = 'schema_version';`;
 | 
			
		||||
    const [rows] = await this.$executeQuery(connection, query, true);
 | 
			
		||||
    connection.release();
 | 
			
		||||
    const [rows] = await this.$executeQuery(query, true);
 | 
			
		||||
    return rows[0]['number'];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -263,8 +250,6 @@ class DatabaseMigration {
 | 
			
		||||
   * Create the `state` table
 | 
			
		||||
   */
 | 
			
		||||
  private async $createMigrationStateTable(): Promise<void> {
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const query = `CREATE TABLE IF NOT EXISTS state (
 | 
			
		||||
        name varchar(25) NOT NULL,
 | 
			
		||||
@ -272,15 +257,12 @@ class DatabaseMigration {
 | 
			
		||||
        string varchar(100) NULL,
 | 
			
		||||
        CONSTRAINT name_unique UNIQUE (name)
 | 
			
		||||
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
 | 
			
		||||
      await this.$executeQuery(connection, query);
 | 
			
		||||
      await this.$executeQuery(query);
 | 
			
		||||
 | 
			
		||||
      // Set initial values
 | 
			
		||||
      await this.$executeQuery(connection, `INSERT INTO state VALUES('schema_version', 0, NULL);`);
 | 
			
		||||
      await this.$executeQuery(connection, `INSERT INTO state VALUES('last_elements_block', 0, NULL);`);
 | 
			
		||||
 | 
			
		||||
      connection.release();
 | 
			
		||||
      await this.$executeQuery(`INSERT INTO state VALUES('schema_version', 0, NULL);`);
 | 
			
		||||
      await this.$executeQuery(`INSERT INTO state VALUES('last_elements_block', 0, NULL);`);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -295,18 +277,14 @@ class DatabaseMigration {
 | 
			
		||||
    }
 | 
			
		||||
    transactionQueries.push(this.getUpdateToLatestSchemaVersionQuery());
 | 
			
		||||
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
    try {
 | 
			
		||||
      await this.$executeQuery(connection, 'START TRANSACTION;');
 | 
			
		||||
      await this.$executeQuery('START TRANSACTION;');
 | 
			
		||||
      for (const query of transactionQueries) {
 | 
			
		||||
        await this.$executeQuery(connection, query);
 | 
			
		||||
        await this.$executeQuery(query);
 | 
			
		||||
      }
 | 
			
		||||
      await this.$executeQuery(connection, 'COMMIT;');
 | 
			
		||||
 | 
			
		||||
      connection.release();
 | 
			
		||||
      await this.$executeQuery('COMMIT;');
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      await this.$executeQuery(connection, 'ROLLBACK;');
 | 
			
		||||
      connection.release();
 | 
			
		||||
      await this.$executeQuery('ROLLBACK;');
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -346,14 +324,12 @@ class DatabaseMigration {
 | 
			
		||||
   * Print current database version
 | 
			
		||||
   */
 | 
			
		||||
  private async $printDatabaseVersion() {
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
    try {
 | 
			
		||||
      const [rows] = await this.$executeQuery(connection, 'SELECT VERSION() as version;', true);
 | 
			
		||||
      const [rows] = await this.$executeQuery('SELECT VERSION() as version;', true);
 | 
			
		||||
      logger.debug(`MIGRATIONS: Database engine version '${rows[0].version}'`);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.debug(`MIGRATIONS: Could not fetch database engine version. ` + e);
 | 
			
		||||
    }
 | 
			
		||||
    connection.release();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Couple of wrappers to clean the main logic
 | 
			
		||||
@ -490,24 +466,22 @@ class DatabaseMigration {
 | 
			
		||||
  public async $truncateIndexedData(tables: string[]) {
 | 
			
		||||
    const allowedTables = ['blocks', 'hashrates'];
 | 
			
		||||
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
    try {
 | 
			
		||||
      for (const table of tables) {
 | 
			
		||||
        if (!allowedTables.includes(table)) {
 | 
			
		||||
          logger.debug(`Table ${table} cannot to be re-indexed (not allowed)`);
 | 
			
		||||
          continue;
 | 
			
		||||
        };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await this.$executeQuery(connection, `TRUNCATE ${table}`, true);
 | 
			
		||||
        await this.$executeQuery(`TRUNCATE ${table}`, true);
 | 
			
		||||
        if (table === 'hashrates') {
 | 
			
		||||
          await this.$executeQuery(connection, 'UPDATE state set number = 0 where name = "last_hashrates_indexing"', true);
 | 
			
		||||
          await this.$executeQuery('UPDATE state set number = 0 where name = "last_hashrates_indexing"', true);
 | 
			
		||||
        }
 | 
			
		||||
        logger.notice(`Table ${table} has been truncated`);
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.warn(`Unable to erase indexed data`);
 | 
			
		||||
    }
 | 
			
		||||
    connection.release();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@ import { IBitcoinApi } from '../bitcoin/bitcoin-api.interface';
 | 
			
		||||
import bitcoinClient from '../bitcoin/bitcoin-client';
 | 
			
		||||
import bitcoinSecondClient from '../bitcoin/bitcoin-second-client';
 | 
			
		||||
import { Common } from '../common';
 | 
			
		||||
import { DB } from '../../database';
 | 
			
		||||
import DB from '../../database';
 | 
			
		||||
import logger from '../../logger';
 | 
			
		||||
 | 
			
		||||
class ElementsParser {
 | 
			
		||||
@ -33,10 +33,8 @@ class ElementsParser {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $getPegDataByMonth(): Promise<any> {
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
    const query = `SELECT SUM(amount) AS amount, DATE_FORMAT(FROM_UNIXTIME(datetime), '%Y-%m-01') AS date FROM elements_pegs GROUP BY DATE_FORMAT(FROM_UNIXTIME(datetime), '%Y%m')`;
 | 
			
		||||
    const [rows] = await connection.query<any>(query);
 | 
			
		||||
    connection.release();
 | 
			
		||||
    const [rows] = await DB.query(query);
 | 
			
		||||
    return rows;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -79,7 +77,6 @@ class ElementsParser {
 | 
			
		||||
 | 
			
		||||
  protected async $savePegToDatabase(height: number, blockTime: number, amount: number, txid: string,
 | 
			
		||||
    txindex: number, bitcoinaddress: string, bitcointxid: string, bitcoinindex: number, final_tx: number): Promise<void> {
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
    const query = `INSERT INTO elements_pegs(
 | 
			
		||||
        block, datetime, amount, txid, txindex, bitcoinaddress, bitcointxid, bitcoinindex, final_tx
 | 
			
		||||
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`;
 | 
			
		||||
@ -87,24 +84,19 @@ class ElementsParser {
 | 
			
		||||
    const params: (string | number)[] = [
 | 
			
		||||
      height, blockTime, amount, txid, txindex, bitcoinaddress, bitcointxid, bitcoinindex, final_tx
 | 
			
		||||
    ];
 | 
			
		||||
    await connection.query(query, params);
 | 
			
		||||
    connection.release();
 | 
			
		||||
    await DB.query(query, params);
 | 
			
		||||
    logger.debug(`Saved L-BTC peg from block height #${height} with TXID ${txid}.`);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected async $getLatestBlockHeightFromDatabase(): Promise<number> {
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
    const query = `SELECT number FROM state WHERE name = 'last_elements_block'`;
 | 
			
		||||
    const [rows] = await connection.query<any>(query);
 | 
			
		||||
    connection.release();
 | 
			
		||||
    const [rows] = await DB.query(query);
 | 
			
		||||
    return rows[0]['number'];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected async $saveLatestBlockToDatabase(blockHeight: number) {
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
    const query = `UPDATE state SET number = ? WHERE name = 'last_elements_block'`;
 | 
			
		||||
    await connection.query<any>(query, [blockHeight]);
 | 
			
		||||
    connection.release();
 | 
			
		||||
    await DB.query(query, [blockHeight]);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,4 @@
 | 
			
		||||
import { readFileSync } from 'fs';
 | 
			
		||||
import { DB } from '../database';
 | 
			
		||||
import DB from '../database';
 | 
			
		||||
import logger from '../logger';
 | 
			
		||||
import config from '../config';
 | 
			
		||||
 | 
			
		||||
@ -59,13 +58,11 @@ class PoolsParser {
 | 
			
		||||
    logger.debug(`Found ${poolNames.length} unique mining pools`);
 | 
			
		||||
 | 
			
		||||
    // Get existing pools from the db
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
    let existingPools;
 | 
			
		||||
    try {
 | 
			
		||||
      [existingPools] = await connection.query<any>({ sql: 'SELECT * FROM pools;', timeout: 120000 });
 | 
			
		||||
      [existingPools] = await DB.query({ sql: 'SELECT * FROM pools;', timeout: 120000 });
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('Cannot get existing pools from the database, skipping pools.json import');
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -145,17 +142,15 @@ class PoolsParser {
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      if (finalPoolDataAdd.length > 0) {
 | 
			
		||||
        await connection.query<any>({ sql: queryAdd, timeout: 120000 });
 | 
			
		||||
        await DB.query({ sql: queryAdd, timeout: 120000 });
 | 
			
		||||
      }
 | 
			
		||||
      for (const query of updateQueries) {
 | 
			
		||||
        await connection.query<any>({ sql: query, timeout: 120000 });
 | 
			
		||||
        await DB.query({ sql: query, timeout: 120000 });
 | 
			
		||||
      }
 | 
			
		||||
      await this.insertUnknownPool();
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.info('Mining pools.json import completed');
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err(`Unable to import pools in the database`);
 | 
			
		||||
      logger.err(`Cannot import pools in the database`);
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -164,16 +159,15 @@ class PoolsParser {
 | 
			
		||||
   * Manually add the 'unknown pool'
 | 
			
		||||
   */
 | 
			
		||||
  private async insertUnknownPool() {
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
    try {
 | 
			
		||||
      const [rows]: any[] = await connection.query({ sql: 'SELECT name from pools where name="Unknown"', timeout: 120000 });
 | 
			
		||||
      const [rows]: any[] = await DB.query({ sql: 'SELECT name from pools where name="Unknown"', timeout: 120000 });
 | 
			
		||||
      if (rows.length === 0) {
 | 
			
		||||
        await connection.query({
 | 
			
		||||
        await DB.query({
 | 
			
		||||
          sql: `INSERT INTO pools(name, link, regexes, addresses, slug)
 | 
			
		||||
          VALUES("Unknown", "https://learnmeabitcoin.com/technical/coinbase-transaction", "[]", "[]", "unknown");
 | 
			
		||||
        `});
 | 
			
		||||
      } else {
 | 
			
		||||
        await connection.query(`UPDATE pools
 | 
			
		||||
        await DB.query(`UPDATE pools
 | 
			
		||||
          SET name='Unknown', link='https://learnmeabitcoin.com/technical/coinbase-transaction',
 | 
			
		||||
          regexes='[]', addresses='[]',
 | 
			
		||||
          slug='unknown'
 | 
			
		||||
@ -183,8 +177,6 @@ class PoolsParser {
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('Unable to insert "Unknown" mining pool');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    connection.release();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import memPool from './mempool';
 | 
			
		||||
import { DB } from '../database';
 | 
			
		||||
import DB from '../database';
 | 
			
		||||
import logger from '../logger';
 | 
			
		||||
 | 
			
		||||
import { Statistic, TransactionExtended, OptimizedStatistic } from '../mempool.interfaces';
 | 
			
		||||
@ -155,7 +155,6 @@ class Statistics {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async $createZeroedStatistic(): Promise<number | undefined> {
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
    try {
 | 
			
		||||
      const query = `INSERT INTO statistics(
 | 
			
		||||
              added,
 | 
			
		||||
@ -206,17 +205,14 @@ class Statistics {
 | 
			
		||||
            )
 | 
			
		||||
            VALUES (NOW(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
			
		||||
               0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)`;
 | 
			
		||||
      const [result]: any = await connection.query(query);
 | 
			
		||||
      connection.release();
 | 
			
		||||
      const [result]: any = await DB.query(query);
 | 
			
		||||
      return result.insertId;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err('$create() error' + (e instanceof Error ? e.message : e));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async $create(statistics: Statistic): Promise<number | undefined> {
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
    try {
 | 
			
		||||
      const query = `INSERT INTO statistics(
 | 
			
		||||
              added,
 | 
			
		||||
@ -314,11 +310,9 @@ class Statistics {
 | 
			
		||||
        statistics.vsize_1800,
 | 
			
		||||
        statistics.vsize_2000,
 | 
			
		||||
      ];
 | 
			
		||||
      const [result]: any = await connection.query(query, params);
 | 
			
		||||
      connection.release();
 | 
			
		||||
      const [result]: any = await DB.query(query, params);
 | 
			
		||||
      return result.insertId;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err('$create() error' + (e instanceof Error ? e.message : e));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -421,10 +415,8 @@ class Statistics {
 | 
			
		||||
 | 
			
		||||
  private async $get(id: number): Promise<OptimizedStatistic | undefined> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.getConnection();
 | 
			
		||||
      const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics WHERE id = ?`;
 | 
			
		||||
      const [rows] = await connection.query<any>(query, [id]);
 | 
			
		||||
      connection.release();
 | 
			
		||||
      const [rows] = await DB.query(query, [id]);
 | 
			
		||||
      if (rows[0]) {
 | 
			
		||||
        return this.mapStatisticToOptimizedStatistic([rows[0]])[0];
 | 
			
		||||
      }
 | 
			
		||||
@ -435,11 +427,9 @@ class Statistics {
 | 
			
		||||
 | 
			
		||||
  public async $list2H(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.getConnection();
 | 
			
		||||
      const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics ORDER BY statistics.added DESC LIMIT 120`;
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
      const [rows] = await DB.query({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('$list2H() error' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      return [];
 | 
			
		||||
@ -448,11 +438,9 @@ class Statistics {
 | 
			
		||||
 | 
			
		||||
  public async $list24H(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.getConnection();
 | 
			
		||||
      const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics ORDER BY statistics.added DESC LIMIT 1440`;
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
      const [rows] = await DB.query({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('$list24h() error' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      return [];
 | 
			
		||||
@ -461,11 +449,9 @@ class Statistics {
 | 
			
		||||
 | 
			
		||||
  public async $list1W(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.getConnection();
 | 
			
		||||
      const query = this.getQueryForDaysAvg(300, '1 WEEK'); // 5m interval
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
      const [rows] = await DB.query({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('$list1W() error' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      return [];
 | 
			
		||||
@ -474,11 +460,9 @@ class Statistics {
 | 
			
		||||
 | 
			
		||||
  public async $list1M(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.getConnection();
 | 
			
		||||
      const query = this.getQueryForDaysAvg(1800, '1 MONTH'); // 30m interval
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
      const [rows] = await DB.query({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('$list1M() error' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      return [];
 | 
			
		||||
@ -487,11 +471,9 @@ class Statistics {
 | 
			
		||||
 | 
			
		||||
  public async $list3M(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.getConnection();
 | 
			
		||||
      const query = this.getQueryForDaysAvg(7200, '3 MONTH'); // 2h interval
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
      const [rows] = await DB.query({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('$list3M() error' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      return [];
 | 
			
		||||
@ -500,11 +482,9 @@ class Statistics {
 | 
			
		||||
 | 
			
		||||
  public async $list6M(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.getConnection();
 | 
			
		||||
      const query = this.getQueryForDaysAvg(10800, '6 MONTH'); // 3h interval 
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
      const query = this.getQueryForDaysAvg(10800, '6 MONTH'); // 3h interval
 | 
			
		||||
      const [rows] = await DB.query({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('$list6M() error' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      return [];
 | 
			
		||||
@ -513,11 +493,9 @@ class Statistics {
 | 
			
		||||
 | 
			
		||||
  public async $list1Y(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.getConnection();
 | 
			
		||||
      const query = this.getQueryForDays(28800, '1 YEAR'); // 8h interval
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
      const [rows] = await DB.query({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('$list1Y() error' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      return [];
 | 
			
		||||
@ -526,11 +504,9 @@ class Statistics {
 | 
			
		||||
 | 
			
		||||
  public async $list2Y(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.getConnection();
 | 
			
		||||
      const query = this.getQueryForDays(28800, "2 YEAR"); // 8h interval
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
      const query = this.getQueryForDays(28800, '2 YEAR'); // 8h interval
 | 
			
		||||
      const [rows] = await DB.query({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('$list2Y() error' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      return [];
 | 
			
		||||
@ -539,11 +515,9 @@ class Statistics {
 | 
			
		||||
 | 
			
		||||
  public async $list3Y(): Promise<OptimizedStatistic[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const connection = await DB.getConnection();
 | 
			
		||||
      const query = this.getQueryForDays(43200, "3 YEAR"); // 12h interval
 | 
			
		||||
      const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows);
 | 
			
		||||
      const query = this.getQueryForDays(43200, '3 YEAR'); // 12h interval
 | 
			
		||||
      const [rows] = await DB.query({ sql: query, timeout: this.queryTimeout });
 | 
			
		||||
      return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('$list3Y() error' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      return [];
 | 
			
		||||
 | 
			
		||||
@ -1,51 +1,51 @@
 | 
			
		||||
import config from './config';
 | 
			
		||||
import { createPool, PoolConnection } from 'mysql2/promise';
 | 
			
		||||
import { createPool, Pool, PoolConnection } from 'mysql2/promise';
 | 
			
		||||
import logger from './logger';
 | 
			
		||||
import { PoolOptions } from 'mysql2/typings/mysql';
 | 
			
		||||
 | 
			
		||||
export class DB {
 | 
			
		||||
  static poolConfig = ():PoolOptions => {
 | 
			
		||||
    let poolConfig:PoolOptions = {
 | 
			
		||||
      port: config.DATABASE.PORT,
 | 
			
		||||
      database: config.DATABASE.DATABASE,
 | 
			
		||||
      user: config.DATABASE.USERNAME,
 | 
			
		||||
      password: config.DATABASE.PASSWORD,
 | 
			
		||||
      connectionLimit: 10,
 | 
			
		||||
      supportBigNumbers: true,
 | 
			
		||||
      timezone: '+00:00',
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config.DATABASE.SOCKET !== "") {
 | 
			
		||||
      poolConfig.socketPath = config.DATABASE.SOCKET;
 | 
			
		||||
 class DB {
 | 
			
		||||
  constructor() {
 | 
			
		||||
    if (config.DATABASE.SOCKET !== '') {
 | 
			
		||||
      this.poolConfig.socketPath = config.DATABASE.SOCKET;
 | 
			
		||||
    } else {
 | 
			
		||||
      poolConfig.host = config.DATABASE.HOST;
 | 
			
		||||
      this.poolConfig.host = config.DATABASE.HOST;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return poolConfig;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  static pool = createPool(DB.poolConfig());
 | 
			
		||||
  private pool: Pool | null = null;
 | 
			
		||||
  private poolConfig: PoolOptions = {
 | 
			
		||||
    port: config.DATABASE.PORT,
 | 
			
		||||
    database: config.DATABASE.DATABASE,
 | 
			
		||||
    user: config.DATABASE.USERNAME,
 | 
			
		||||
    password: config.DATABASE.PASSWORD,
 | 
			
		||||
    connectionLimit: 10,
 | 
			
		||||
    supportBigNumbers: true,
 | 
			
		||||
    timezone: '+00:00',
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  static connectionsReady: number[] = [];
 | 
			
		||||
  public async query(query, params?) {
 | 
			
		||||
    const pool = await this.getPool();
 | 
			
		||||
    return pool.query(query, params);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static async getConnection() {
 | 
			
		||||
    const connection: PoolConnection = await DB.pool.getConnection();
 | 
			
		||||
    const connectionId = connection['connection'].connectionId;
 | 
			
		||||
    if (!DB.connectionsReady.includes(connectionId)) {
 | 
			
		||||
      await connection.query(`SET time_zone='+00:00';`);
 | 
			
		||||
      this.connectionsReady.push(connectionId);
 | 
			
		||||
  public async checkDbConnection() {
 | 
			
		||||
    try {
 | 
			
		||||
      await this.query('SELECT ?', [1]);
 | 
			
		||||
      logger.info('Database connection established.');
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('Could not connect to database: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      process.exit(1);
 | 
			
		||||
    }
 | 
			
		||||
    return connection;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async getPool(): Promise<Pool> {
 | 
			
		||||
    if (this.pool === null) {
 | 
			
		||||
      this.pool = createPool(this.poolConfig);
 | 
			
		||||
      this.pool.on('connection', function (newConnection: PoolConnection) {
 | 
			
		||||
        newConnection.query(`SET time_zone='+00:00'`);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    return this.pool;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function checkDbConnection() {
 | 
			
		||||
  try {
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
    logger.info('Database connection established.');
 | 
			
		||||
    connection.release();
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    logger.err('Could not connect to database: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
    process.exit(1);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
export default new DB();
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ import * as WebSocket from 'ws';
 | 
			
		||||
import * as cluster from 'cluster';
 | 
			
		||||
import axios from 'axios';
 | 
			
		||||
 | 
			
		||||
import { checkDbConnection, DB } from './database';
 | 
			
		||||
import DB from './database';
 | 
			
		||||
import config from './config';
 | 
			
		||||
import routes from './routes';
 | 
			
		||||
import blocks from './api/blocks';
 | 
			
		||||
@ -89,7 +89,7 @@ class Server {
 | 
			
		||||
    diskCache.loadMempoolCache();
 | 
			
		||||
 | 
			
		||||
    if (config.DATABASE.ENABLED) {
 | 
			
		||||
      await checkDbConnection();
 | 
			
		||||
      await DB.checkDbConnection();
 | 
			
		||||
      try {
 | 
			
		||||
        if (process.env.npm_config_reindex != undefined) { // Re-index requests
 | 
			
		||||
          const tables = process.env.npm_config_reindex.split(',');
 | 
			
		||||
@ -188,7 +188,7 @@ class Server {
 | 
			
		||||
        await BlocksRepository.$deleteBlocks(10);
 | 
			
		||||
        await HashratesRepository.$deleteLastEntries();
 | 
			
		||||
      }
 | 
			
		||||
      blocks.$generateBlockDatabase();
 | 
			
		||||
      await blocks.$generateBlockDatabase();
 | 
			
		||||
      await mining.$generateNetworkHashrateHistory();
 | 
			
		||||
      await mining.$generatePoolHashrateHistory();
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import { BlockExtended, PoolTag } from '../mempool.interfaces';
 | 
			
		||||
import { DB } from '../database';
 | 
			
		||||
import { BlockExtended } from '../mempool.interfaces';
 | 
			
		||||
import DB from '../database';
 | 
			
		||||
import logger from '../logger';
 | 
			
		||||
import { Common } from '../api/common';
 | 
			
		||||
import { prepareBlock } from '../utils/blocks-utils';
 | 
			
		||||
@ -10,11 +10,7 @@ class BlocksRepository {
 | 
			
		||||
   * Save indexed block data in the database
 | 
			
		||||
   */
 | 
			
		||||
  public async $saveBlockInDatabase(block: BlockExtended) {
 | 
			
		||||
    let connection;
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      connection = await DB.getConnection();
 | 
			
		||||
 | 
			
		||||
      const query = `INSERT INTO blocks(
 | 
			
		||||
        height,           hash,                blockTimestamp, size,
 | 
			
		||||
        weight,           tx_count,            coinbase_raw,   difficulty,
 | 
			
		||||
@ -52,14 +48,11 @@ class BlocksRepository {
 | 
			
		||||
        block.extras.avgFeeRate,
 | 
			
		||||
      ];
 | 
			
		||||
 | 
			
		||||
      await connection.query(query, params);
 | 
			
		||||
      connection.release();
 | 
			
		||||
      await DB.query(query, params);
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart
 | 
			
		||||
        logger.debug(`$saveBlockInDatabase() - Block ${block.height} has already been indexed, ignoring`);
 | 
			
		||||
      } else {
 | 
			
		||||
        connection.release();
 | 
			
		||||
        logger.err('Cannot save indexed block into db. Reason: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
        throw e;
 | 
			
		||||
      }
 | 
			
		||||
@ -74,16 +67,13 @@ class BlocksRepository {
 | 
			
		||||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let connection;
 | 
			
		||||
    try {
 | 
			
		||||
      connection = await DB.getConnection();
 | 
			
		||||
      const [rows]: any[] = await connection.query(`
 | 
			
		||||
      const [rows]: any[] = await DB.query(`
 | 
			
		||||
        SELECT height
 | 
			
		||||
        FROM blocks
 | 
			
		||||
        WHERE height <= ? AND height >= ?
 | 
			
		||||
        ORDER BY height DESC;
 | 
			
		||||
      `, [startHeight, endHeight]);
 | 
			
		||||
      connection.release();
 | 
			
		||||
 | 
			
		||||
      const indexedBlockHeights: number[] = [];
 | 
			
		||||
      rows.forEach((row: any) => { indexedBlockHeights.push(row.height); });
 | 
			
		||||
@ -92,7 +82,6 @@ class BlocksRepository {
 | 
			
		||||
 | 
			
		||||
      return missingBlocksHeights;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err('Cannot retrieve blocks list to index. Reason: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
@ -121,15 +110,10 @@ class BlocksRepository {
 | 
			
		||||
 | 
			
		||||
    query += ` GROUP by pools.id`;
 | 
			
		||||
 | 
			
		||||
    let connection;
 | 
			
		||||
    try {
 | 
			
		||||
      connection = await DB.getConnection();
 | 
			
		||||
      const [rows] = await connection.query(query, params);
 | 
			
		||||
      connection.release();
 | 
			
		||||
 | 
			
		||||
      const [rows] = await DB.query(query, params);
 | 
			
		||||
      return rows;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err('Cannot count empty blocks. Reason: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
@ -159,15 +143,10 @@ class BlocksRepository {
 | 
			
		||||
      query += ` blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let connection;
 | 
			
		||||
    try {
 | 
			
		||||
      connection = await DB.getConnection();
 | 
			
		||||
      const [rows] = await connection.query(query, params);
 | 
			
		||||
      connection.release();
 | 
			
		||||
 | 
			
		||||
      const [rows] = await DB.query(query, params);
 | 
			
		||||
      return <number>rows[0].blockCount;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err(`Cannot count blocks for this pool (using offset). Reason: ` + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
@ -199,15 +178,10 @@ class BlocksRepository {
 | 
			
		||||
    }
 | 
			
		||||
    query += ` blockTimestamp BETWEEN FROM_UNIXTIME('${from}') AND FROM_UNIXTIME('${to}')`;
 | 
			
		||||
 | 
			
		||||
    let connection;
 | 
			
		||||
    try {
 | 
			
		||||
      connection = await DB.getConnection();
 | 
			
		||||
      const [rows] = await connection.query(query, params);
 | 
			
		||||
      connection.release();
 | 
			
		||||
 | 
			
		||||
      const [rows] = await DB.query(query, params);
 | 
			
		||||
      return <number>rows[0];
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err(`Cannot count blocks for this pool (using timestamps). Reason: ` + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
@ -222,11 +196,8 @@ class BlocksRepository {
 | 
			
		||||
      ORDER BY height
 | 
			
		||||
      LIMIT 1;`;
 | 
			
		||||
 | 
			
		||||
    let connection;
 | 
			
		||||
    try {
 | 
			
		||||
      connection = await DB.getConnection();
 | 
			
		||||
      const [rows]: any[] = await connection.query(query);
 | 
			
		||||
      connection.release();
 | 
			
		||||
      const [rows]: any[] = await DB.query(query);
 | 
			
		||||
 | 
			
		||||
      if (rows.length <= 0) {
 | 
			
		||||
        return -1;
 | 
			
		||||
@ -234,7 +205,6 @@ class BlocksRepository {
 | 
			
		||||
 | 
			
		||||
      return <number>rows[0].blockTimestamp;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err('Cannot get oldest indexed block timestamp. Reason: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
@ -243,7 +213,7 @@ class BlocksRepository {
 | 
			
		||||
  /**
 | 
			
		||||
   * Get blocks mined by a specific mining pool
 | 
			
		||||
   */
 | 
			
		||||
  public async $getBlocksByPool(slug: string, startHeight: number | undefined = undefined): Promise<object[]> {
 | 
			
		||||
  public async $getBlocksByPool(slug: string, startHeight?: number): Promise<object[]> {
 | 
			
		||||
    const pool = await PoolsRepository.$getPool(slug);
 | 
			
		||||
    if (!pool) {
 | 
			
		||||
      throw new Error(`This mining pool does not exist`);
 | 
			
		||||
@ -264,20 +234,16 @@ class BlocksRepository {
 | 
			
		||||
    query += ` ORDER BY height DESC
 | 
			
		||||
      LIMIT 10`;
 | 
			
		||||
 | 
			
		||||
    let connection;
 | 
			
		||||
    try {
 | 
			
		||||
      connection = await DB.getConnection();
 | 
			
		||||
      const [rows] = await connection.query(query, params);
 | 
			
		||||
      connection.release();
 | 
			
		||||
      const [rows] = await DB.query(query, params);
 | 
			
		||||
 | 
			
		||||
      const blocks: BlockExtended[] = [];
 | 
			
		||||
      for (let block of <object[]>rows) {
 | 
			
		||||
      for (const block of <object[]>rows) {
 | 
			
		||||
        blocks.push(prepareBlock(block));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return blocks;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err('Cannot get blocks for this pool. Reason: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
@ -287,10 +253,8 @@ class BlocksRepository {
 | 
			
		||||
   * Get one block by height
 | 
			
		||||
   */
 | 
			
		||||
  public async $getBlockByHeight(height: number): Promise<object | null> {
 | 
			
		||||
    let connection;
 | 
			
		||||
    try {
 | 
			
		||||
      connection = await DB.getConnection();
 | 
			
		||||
      const [rows]: any[] = await connection.query(`
 | 
			
		||||
      const [rows]: any[] = await DB.query(`
 | 
			
		||||
        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.addresses as pool_addresses, pools.regexes as pool_regexes,
 | 
			
		||||
@ -299,7 +263,6 @@ class BlocksRepository {
 | 
			
		||||
        JOIN pools ON blocks.pool_id = pools.id
 | 
			
		||||
        WHERE height = ${height};
 | 
			
		||||
      `);
 | 
			
		||||
      connection.release();
 | 
			
		||||
 | 
			
		||||
      if (rows.length <= 0) {
 | 
			
		||||
        return null;
 | 
			
		||||
@ -307,7 +270,6 @@ class BlocksRepository {
 | 
			
		||||
 | 
			
		||||
      return rows[0];
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err(`Cannot get indexed block ${height}. Reason: ` + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
@ -351,20 +313,15 @@ class BlocksRepository {
 | 
			
		||||
      ORDER BY t.height
 | 
			
		||||
    `;
 | 
			
		||||
 | 
			
		||||
    let connection;
 | 
			
		||||
    try {
 | 
			
		||||
      connection = await DB.getConnection();
 | 
			
		||||
      const [rows]: any[] = await connection.query(query);
 | 
			
		||||
      connection.release();
 | 
			
		||||
      const [rows]: any[] = await DB.query(query);
 | 
			
		||||
 | 
			
		||||
      for (const row of rows) {
 | 
			
		||||
        delete row['rn'];
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return rows;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err('Cannot generate difficulty history. Reason: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
@ -374,10 +331,7 @@ class BlocksRepository {
 | 
			
		||||
   * Get general block stats
 | 
			
		||||
   */
 | 
			
		||||
  public async $getBlockStats(blockCount: number): Promise<any> {
 | 
			
		||||
    let connection;
 | 
			
		||||
    try {
 | 
			
		||||
      connection = await DB.getConnection();
 | 
			
		||||
 | 
			
		||||
      // We need to use a subquery
 | 
			
		||||
      const query = `
 | 
			
		||||
        SELECT MIN(height) as startBlock, MAX(height) as endBlock, SUM(reward) as totalReward, SUM(fees) as totalFee, SUM(tx_count) as totalTx
 | 
			
		||||
@ -386,12 +340,10 @@ class BlocksRepository {
 | 
			
		||||
          ORDER by height DESC
 | 
			
		||||
          LIMIT ?) as sub`;
 | 
			
		||||
 | 
			
		||||
      const [rows]: any = await connection.query(query, [blockCount]);
 | 
			
		||||
      connection.release();
 | 
			
		||||
 
 | 
			
		||||
      const [rows]: any = await DB.query(query, [blockCount]);
 | 
			
		||||
 | 
			
		||||
      return rows[0];
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err('Cannot generate reward stats. Reason: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
@ -401,12 +353,8 @@ class BlocksRepository {
 | 
			
		||||
   * 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();
 | 
			
		||||
      const [lastBlocks]: any[] = await DB.query(`SELECT height, hash, previous_block_hash FROM blocks ORDER BY height DESC LIMIT 10`);
 | 
			
		||||
 | 
			
		||||
      for (let i = 0; i < lastBlocks.length - 1; ++i) {
 | 
			
		||||
        if (lastBlocks[i].previous_block_hash !== lastBlocks[i + 1].hash) {
 | 
			
		||||
@ -417,8 +365,6 @@ class BlocksRepository {
 | 
			
		||||
 | 
			
		||||
      return true;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
 | 
			
		||||
      return true; // Don't do anything if there is a db error
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -428,26 +374,19 @@ class BlocksRepository {
 | 
			
		||||
   */
 | 
			
		||||
  public async $deleteBlocks(count: number) {
 | 
			
		||||
    logger.info(`Delete ${count} most recent indexed blocks from the database`);
 | 
			
		||||
    let connection;
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      connection = await DB.getConnection();
 | 
			
		||||
      await connection.query(`DELETE FROM blocks ORDER BY height DESC LIMIT ${count};`);
 | 
			
		||||
      await DB.query(`DELETE FROM blocks ORDER BY height DESC LIMIT ${count};`);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('Cannot delete recent indexed blocks. Reason: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    connection.release();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the historical averaged block fees
 | 
			
		||||
   */
 | 
			
		||||
  public async $getHistoricalBlockFees(div: number, interval: string | null): Promise<any> {
 | 
			
		||||
    let connection;
 | 
			
		||||
    try {
 | 
			
		||||
      connection = await DB.getConnection();
 | 
			
		||||
 | 
			
		||||
      let query = `SELECT CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp,
 | 
			
		||||
        CAST(AVG(fees) as INT) as avg_fees
 | 
			
		||||
        FROM blocks`;
 | 
			
		||||
@ -458,12 +397,9 @@ class BlocksRepository {
 | 
			
		||||
 | 
			
		||||
      query += ` GROUP BY UNIX_TIMESTAMP(blockTimestamp) DIV ${div}`;
 | 
			
		||||
 | 
			
		||||
      const [rows]: any = await connection.query(query);
 | 
			
		||||
      connection.release();
 | 
			
		||||
 | 
			
		||||
      const [rows]: any = await DB.query(query);
 | 
			
		||||
      return rows;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err('Cannot generate block fees history. Reason: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
@ -473,10 +409,7 @@ class BlocksRepository {
 | 
			
		||||
   * Get the historical averaged block rewards
 | 
			
		||||
   */
 | 
			
		||||
   public async $getHistoricalBlockRewards(div: number, interval: string | null): Promise<any> {
 | 
			
		||||
    let connection;
 | 
			
		||||
    try {
 | 
			
		||||
      connection = await DB.getConnection();
 | 
			
		||||
 | 
			
		||||
      let query = `SELECT CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp,
 | 
			
		||||
        CAST(AVG(reward) as INT) as avg_rewards
 | 
			
		||||
        FROM blocks`;
 | 
			
		||||
@ -487,12 +420,9 @@ class BlocksRepository {
 | 
			
		||||
 | 
			
		||||
      query += ` GROUP BY UNIX_TIMESTAMP(blockTimestamp) DIV ${div}`;
 | 
			
		||||
 | 
			
		||||
      const [rows]: any = await connection.query(query);
 | 
			
		||||
      connection.release();
 | 
			
		||||
 | 
			
		||||
      const [rows]: any = await DB.query(query);
 | 
			
		||||
      return rows;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err('Cannot generate block rewards history. Reason: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import { Common } from '../api/common';
 | 
			
		||||
import { DB } from '../database';
 | 
			
		||||
import DB from '../database';
 | 
			
		||||
import logger from '../logger';
 | 
			
		||||
import PoolsRepository from './PoolsRepository';
 | 
			
		||||
 | 
			
		||||
@ -20,13 +20,9 @@ class HashratesRepository {
 | 
			
		||||
    }
 | 
			
		||||
    query = query.slice(0, -1);
 | 
			
		||||
 | 
			
		||||
    let connection;
 | 
			
		||||
    try {
 | 
			
		||||
      connection = await DB.getConnection();
 | 
			
		||||
      await connection.query(query);
 | 
			
		||||
      connection.release();
 | 
			
		||||
      await DB.query(query);
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err('Cannot save indexed hashrate into db. Reason: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
@ -35,8 +31,6 @@ class HashratesRepository {
 | 
			
		||||
  public async $getNetworkDailyHashrate(interval: string | null): Promise<any[]> {
 | 
			
		||||
    interval = Common.getSqlInterval(interval);
 | 
			
		||||
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
 | 
			
		||||
    let query = `SELECT UNIX_TIMESTAMP(hashrate_timestamp) as timestamp, avg_hashrate as avgHashrate
 | 
			
		||||
      FROM hashrates`;
 | 
			
		||||
 | 
			
		||||
@ -50,32 +44,24 @@ class HashratesRepository {
 | 
			
		||||
    query += ` ORDER by hashrate_timestamp`;
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const [rows]: any[] = await connection.query(query);
 | 
			
		||||
      connection.release();
 | 
			
		||||
 | 
			
		||||
      const [rows]: any[] = await DB.query(query);
 | 
			
		||||
      return rows;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err('Cannot fetch network hashrate history. Reason: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $getWeeklyHashrateTimestamps(): Promise<number[]> {
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
 | 
			
		||||
    const query = `SELECT UNIX_TIMESTAMP(hashrate_timestamp) as timestamp
 | 
			
		||||
      FROM hashrates
 | 
			
		||||
      WHERE type = 'weekly'
 | 
			
		||||
      GROUP BY hashrate_timestamp`;
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const [rows]: any[] = await connection.query(query);
 | 
			
		||||
      connection.release();
 | 
			
		||||
 | 
			
		||||
      const [rows]: any[] = await DB.query(query);
 | 
			
		||||
      return rows.map(row => row.timestamp);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err('Cannot retreive indexed weekly hashrate timestamps. Reason: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
@ -87,7 +73,6 @@ class HashratesRepository {
 | 
			
		||||
  public async $getPoolsWeeklyHashrate(interval: string | null): Promise<any[]> {
 | 
			
		||||
    interval = Common.getSqlInterval(interval);
 | 
			
		||||
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
    const topPoolsId = (await PoolsRepository.$getPoolsInfo('1w')).map((pool) => pool.poolId);
 | 
			
		||||
 | 
			
		||||
    let query = `SELECT UNIX_TIMESTAMP(hashrate_timestamp) as timestamp, avg_hashrate as avgHashrate, share, pools.name as poolName
 | 
			
		||||
@ -106,12 +91,9 @@ class HashratesRepository {
 | 
			
		||||
    query += ` ORDER by hashrate_timestamp, FIELD(pool_id, ${topPoolsId})`;
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const [rows]: any[] = await connection.query(query);
 | 
			
		||||
      connection.release();
 | 
			
		||||
 | 
			
		||||
      const [rows]: any[] = await DB.query(query);
 | 
			
		||||
      return rows;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err('Cannot fetch weekly pools hashrate history. Reason: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
@ -128,8 +110,8 @@ class HashratesRepository {
 | 
			
		||||
 | 
			
		||||
    // Find hashrate boundaries
 | 
			
		||||
    let query = `SELECT MIN(hashrate_timestamp) as firstTimestamp, MAX(hashrate_timestamp) as lastTimestamp
 | 
			
		||||
      FROM hashrates 
 | 
			
		||||
      JOIN pools on pools.id = pool_id 
 | 
			
		||||
      FROM hashrates
 | 
			
		||||
      JOIN pools on pools.id = pool_id
 | 
			
		||||
      WHERE hashrates.type = 'weekly' AND pool_id = ? AND avg_hashrate != 0
 | 
			
		||||
      ORDER by hashrate_timestamp LIMIT 1`;
 | 
			
		||||
 | 
			
		||||
@ -138,14 +120,10 @@ class HashratesRepository {
 | 
			
		||||
      lastTimestamp: '9999-01-01'
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let connection;
 | 
			
		||||
    try {
 | 
			
		||||
      connection = await DB.getConnection();
 | 
			
		||||
      const [rows]: any[] = await connection.query(query, [pool.id]);
 | 
			
		||||
      const [rows]: any[] = await DB.query(query, [pool.id]);
 | 
			
		||||
      boundaries = rows[0];
 | 
			
		||||
      connection.release();
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err('Cannot fetch hashrate start/end timestamps for this pool. Reason: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -158,12 +136,9 @@ class HashratesRepository {
 | 
			
		||||
      ORDER by hashrate_timestamp`;
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const [rows]: any[] = await connection.query(query, [boundaries.firstTimestamp, boundaries.lastTimestamp, pool.id]);
 | 
			
		||||
      connection.release();
 | 
			
		||||
 | 
			
		||||
      const [rows]: any[] = await DB.query(query, [boundaries.firstTimestamp, boundaries.lastTimestamp, pool.id]);
 | 
			
		||||
      return rows;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err('Cannot fetch pool hashrate history for this pool. Reason: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
@ -173,14 +148,11 @@ class HashratesRepository {
 | 
			
		||||
   * Set latest run timestamp
 | 
			
		||||
   */
 | 
			
		||||
  public async $setLatestRunTimestamp(key: string, val: any = null) {
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
    const query = `UPDATE state SET number = ? WHERE name = ?`;
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      await connection.query<any>(query, (val === null) ? [Math.round(new Date().getTime() / 1000), key] : [val, key]);
 | 
			
		||||
      connection.release();
 | 
			
		||||
      await DB.query(query, (val === null) ? [Math.round(new Date().getTime() / 1000), key] : [val, key]);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err(`Cannot set last indexing timestamp for ${key}. Reason: ` + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
@ -190,19 +162,16 @@ class HashratesRepository {
 | 
			
		||||
   * Get latest run timestamp
 | 
			
		||||
   */
 | 
			
		||||
  public async $getLatestRunTimestamp(key: string): Promise<number> {
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
    const query = `SELECT number FROM state WHERE name = ?`;
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const [rows] = await connection.query<any>(query, [key]);
 | 
			
		||||
      connection.release();
 | 
			
		||||
      const [rows]: any[] = await DB.query(query, [key]);
 | 
			
		||||
 | 
			
		||||
      if (rows.length === 0) {
 | 
			
		||||
        return 0;
 | 
			
		||||
      }
 | 
			
		||||
      return rows[0]['number'];
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err(`Cannot retreive last indexing timestamp for ${key}. Reason: ` + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
@ -214,12 +183,10 @@ class HashratesRepository {
 | 
			
		||||
  public async $deleteLastEntries() {
 | 
			
		||||
    logger.info(`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`);
 | 
			
		||||
      const [rows]: any[] = await DB.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]);
 | 
			
		||||
        await DB.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);
 | 
			
		||||
@ -227,8 +194,6 @@ class HashratesRepository {
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('Cannot delete latest hashrates data points. Reason: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    connection.release();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { Common } from '../api/common';
 | 
			
		||||
import config from '../config';
 | 
			
		||||
import { DB } from '../database';
 | 
			
		||||
import DB from '../database';
 | 
			
		||||
import logger from '../logger';
 | 
			
		||||
import { PoolInfo, PoolTag } from '../mempool.interfaces';
 | 
			
		||||
 | 
			
		||||
@ -9,9 +9,7 @@ class PoolsRepository {
 | 
			
		||||
   * Get all pools tagging info
 | 
			
		||||
   */
 | 
			
		||||
  public async $getPools(): Promise<PoolTag[]> {
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
    const [rows] = await connection.query('SELECT id, name, addresses, regexes, slug FROM pools;');
 | 
			
		||||
    connection.release();
 | 
			
		||||
    const [rows] = await DB.query('SELECT id, name, addresses, regexes, slug FROM pools;');
 | 
			
		||||
    return <PoolTag[]>rows;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -19,9 +17,7 @@ class PoolsRepository {
 | 
			
		||||
   * Get unknown pool tagging info
 | 
			
		||||
   */
 | 
			
		||||
  public async $getUnknownPool(): Promise<PoolTag> {
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
    const [rows] = await connection.query('SELECT id, name, slug FROM pools where name = "Unknown"');
 | 
			
		||||
    connection.release();
 | 
			
		||||
    const [rows] = await DB.query('SELECT id, name, slug FROM pools where name = "Unknown"');
 | 
			
		||||
    return <PoolTag>rows[0];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -42,14 +38,10 @@ class PoolsRepository {
 | 
			
		||||
    query += ` GROUP BY pool_id
 | 
			
		||||
      ORDER BY COUNT(height) DESC`;
 | 
			
		||||
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
    try {
 | 
			
		||||
      const [rows] = await connection.query(query);
 | 
			
		||||
      connection.release();
 | 
			
		||||
 | 
			
		||||
      const [rows] = await DB.query(query);
 | 
			
		||||
      return <PoolInfo[]>rows;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err(`Cannot generate pools stats. Reason: ` + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
@ -64,14 +56,10 @@ class PoolsRepository {
 | 
			
		||||
      LEFT JOIN blocks on pools.id = blocks.pool_id AND blocks.blockTimestamp BETWEEN FROM_UNIXTIME(?) AND FROM_UNIXTIME(?)
 | 
			
		||||
      GROUP BY pools.id`;
 | 
			
		||||
 | 
			
		||||
    const connection = await DB.getConnection();
 | 
			
		||||
    try {
 | 
			
		||||
      const [rows] = await connection.query(query, [from, to]);
 | 
			
		||||
      connection.release();
 | 
			
		||||
 | 
			
		||||
      const [rows] = await DB.query(query, [from, to]);
 | 
			
		||||
      return <PoolInfo[]>rows;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err('Cannot generate pools blocks count. Reason: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
@ -86,12 +74,8 @@ class PoolsRepository {
 | 
			
		||||
      FROM pools
 | 
			
		||||
      WHERE pools.slug = ?`;
 | 
			
		||||
 | 
			
		||||
    let connection;
 | 
			
		||||
    try {
 | 
			
		||||
      connection = await DB.getConnection();
 | 
			
		||||
 | 
			
		||||
      const [rows] = await connection.query(query, [slug]);
 | 
			
		||||
      connection.release();
 | 
			
		||||
      const [rows]: any[] = await DB.query(query, [slug]);
 | 
			
		||||
 | 
			
		||||
      if (rows.length < 1) {
 | 
			
		||||
        logger.debug(`This slug does not match any known pool`);
 | 
			
		||||
@ -107,7 +91,6 @@ class PoolsRepository {
 | 
			
		||||
 | 
			
		||||
      return rows[0];
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      connection.release();
 | 
			
		||||
      logger.err('Cannot get pool from db. Reason: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
const https = require('https');
 | 
			
		||||
import poolsParser from "../api/pools-parser";
 | 
			
		||||
import config from "../config";
 | 
			
		||||
import { DB } from "../database";
 | 
			
		||||
import logger from "../logger";
 | 
			
		||||
import poolsParser from '../api/pools-parser';
 | 
			
		||||
import config from '../config';
 | 
			
		||||
import DB from '../database';
 | 
			
		||||
import logger from '../logger';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Maintain the most recent version of pools.json
 | 
			
		||||
@ -64,15 +64,11 @@ class PoolsUpdater {
 | 
			
		||||
   * Fetch our latest pools.json sha from the db
 | 
			
		||||
   */
 | 
			
		||||
  private async updateDBSha(githubSha: string) {
 | 
			
		||||
    let connection;
 | 
			
		||||
    try {
 | 
			
		||||
      connection = await DB.getConnection();
 | 
			
		||||
      await connection.query('DELETE FROM state where name="pools_json_sha"');
 | 
			
		||||
      await connection.query(`INSERT INTO state VALUES('pools_json_sha', NULL, '${githubSha}')`);
 | 
			
		||||
      connection.release();
 | 
			
		||||
      await DB.query('DELETE FROM state where name="pools_json_sha"');
 | 
			
		||||
      await DB.query(`INSERT INTO state VALUES('pools_json_sha', NULL, '${githubSha}')`);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('Cannot save github pools.json sha into the db. Reason: '  + (e instanceof Error ? e.message : e));
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return undefined;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -81,15 +77,11 @@ class PoolsUpdater {
 | 
			
		||||
   * Fetch our latest pools.json sha from the db
 | 
			
		||||
   */
 | 
			
		||||
  private async getShaFromDb(): Promise<string | undefined> {
 | 
			
		||||
    let connection;
 | 
			
		||||
    try {
 | 
			
		||||
      connection = await DB.getConnection();
 | 
			
		||||
      const [rows] = await connection.query('SELECT string FROM state WHERE name="pools_json_sha"');
 | 
			
		||||
      connection.release();
 | 
			
		||||
      const [rows]: any[] = await DB.query('SELECT string FROM state WHERE name="pools_json_sha"');
 | 
			
		||||
      return (rows.length > 0 ? rows[0].string : undefined);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('Cannot fetch pools.json sha from db. Reason: '  + (e instanceof Error ? e.message : e));
 | 
			
		||||
      connection.release();
 | 
			
		||||
      return undefined;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -140,7 +132,7 @@ class PoolsUpdater {
 | 
			
		||||
      request.on('error', (error) => {
 | 
			
		||||
        logger.err('Github API query failed. Reason: '  + error);
 | 
			
		||||
        reject(error);
 | 
			
		||||
      })
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user