2021-12-11 04:27:58 +04:00
import config from '../config' ;
2022-04-13 17:38:42 +04:00
import DB from '../database' ;
2021-12-11 04:27:58 +04:00
import logger from '../logger' ;
2022-04-22 04:03:08 -04:00
import { Common } from './common' ;
2022-01-12 17:26:10 +09:00
2021-12-11 04:27:58 +04:00
class DatabaseMigration {
2022-06-26 13:49:39 +02:00
private static currentVersion = 23 ;
2021-12-11 04:27:58 +04:00
private queryTimeout = 120000 ;
2022-01-12 14:10:16 +09:00
private statisticsAddedIndexed = false ;
2022-06-13 10:12:27 +02:00
private uniqueLogs : string [ ] = [ ] ;
private blocksTruncatedMessage = ` 'blocks' table has been truncated. Re-indexing from scratch. ` ;
private hashratesTruncatedMessage = ` 'hashrates' table has been truncated. Re-indexing from scratch. ` ;
2021-12-11 04:27:58 +04:00
constructor ( ) { }
2022-06-13 10:12:27 +02:00
/ * *
* Avoid printing multiple time the same message
* /
private uniqueLog ( loggerFunction : any , msg : string ) {
if ( this . uniqueLogs . includes ( msg ) ) {
return ;
}
this . uniqueLogs . push ( msg ) ;
loggerFunction ( msg ) ;
}
2022-01-11 20:43:59 +09:00
/ * *
* Entry point
* /
2021-12-11 04:27:58 +04:00
public async $initializeOrMigrateDatabase ( ) : Promise < void > {
2022-02-21 23:57:44 +09:00
logger . debug ( 'MIGRATIONS: Running migrations' ) ;
2022-01-11 20:43:59 +09:00
2022-01-12 16:06:45 +09:00
await this . $printDatabaseVersion ( ) ;
2022-01-11 20:43:59 +09:00
// First of all, if the `state` database does not exist, create it so we can track migration version
if ( ! await this . $checkIfTableExists ( 'state' ) ) {
2022-02-21 23:57:44 +09:00
logger . debug ( 'MIGRATIONS: `state` table does not exist. Creating it.' ) ;
2022-01-11 20:43:59 +09:00
try {
await this . $createMigrationStateTable ( ) ;
} catch ( e ) {
2022-01-12 17:43:32 +09:00
logger . err ( 'MIGRATIONS: Unable to create `state` table, aborting in 10 seconds. ' + e ) ;
2022-04-22 04:03:08 -04:00
await Common . sleep $ ( 10000 ) ;
2022-01-11 20:43:59 +09:00
process . exit ( - 1 ) ;
}
2022-02-21 23:57:44 +09:00
logger . debug ( 'MIGRATIONS: `state` table initialized.' ) ;
2022-01-11 20:43:59 +09:00
}
let databaseSchemaVersion = 0 ;
try {
databaseSchemaVersion = await this . $getSchemaVersionFromDatabase ( ) ;
} catch ( e ) {
2022-01-12 17:43:32 +09:00
logger . err ( 'MIGRATIONS: Unable to get current database migration version, aborting in 10 seconds. ' + e ) ;
2022-04-22 04:03:08 -04:00
await Common . sleep $ ( 10000 ) ;
2022-01-11 20:43:59 +09:00
process . exit ( - 1 ) ;
}
2022-06-13 10:12:27 +02:00
if ( databaseSchemaVersion === 0 ) {
logger . info ( 'Initializing database (first run, clean install)' ) ;
}
if ( databaseSchemaVersion <= 2 ) {
// Disable some spam logs when they're not relevant
this . uniqueLogs . push ( this . blocksTruncatedMessage ) ;
this . uniqueLogs . push ( this . hashratesTruncatedMessage ) ;
}
2022-02-21 23:57:44 +09:00
logger . debug ( 'MIGRATIONS: Current state.schema_version ' + databaseSchemaVersion ) ;
logger . debug ( 'MIGRATIONS: Latest DatabaseMigration.version is ' + DatabaseMigration . currentVersion ) ;
2022-01-12 14:10:16 +09:00
if ( databaseSchemaVersion >= DatabaseMigration . currentVersion ) {
2022-02-21 23:57:44 +09:00
logger . debug ( 'MIGRATIONS: Nothing to do.' ) ;
2022-01-11 20:43:59 +09:00
return ;
2021-12-11 04:27:58 +04:00
}
2022-01-12 16:41:27 +09:00
// Now, create missing tables. Those queries cannot be wrapped into a transaction unfortunately
try {
await this . $createMissingTablesAndIndexes ( databaseSchemaVersion ) ;
} catch ( e ) {
2022-01-12 17:43:32 +09:00
logger . err ( 'MIGRATIONS: Unable to create required tables, aborting in 10 seconds. ' + e ) ;
2022-04-22 04:03:08 -04:00
await Common . sleep $ ( 10000 ) ;
2022-01-12 16:41:27 +09:00
process . exit ( - 1 ) ;
}
2022-01-12 14:10:16 +09:00
2022-01-11 20:43:59 +09:00
if ( DatabaseMigration . currentVersion > databaseSchemaVersion ) {
try {
2021-12-11 04:27:58 +04:00
await this . $migrateTableSchemaFromVersion ( databaseSchemaVersion ) ;
2022-06-13 10:12:27 +02:00
if ( databaseSchemaVersion === 0 ) {
logger . notice ( ` MIGRATIONS: OK. Database schema has been properly initialized to version ${ DatabaseMigration . currentVersion } (latest version) ` ) ;
} else {
logger . notice ( ` MIGRATIONS: OK. Database schema have been migrated from version ${ databaseSchemaVersion } to ${ DatabaseMigration . currentVersion } (latest version) ` ) ;
}
2022-01-11 20:43:59 +09:00
} catch ( e ) {
2022-01-12 17:43:32 +09:00
logger . err ( 'MIGRATIONS: Unable to migrate database, aborting. ' + e ) ;
2021-12-11 04:27:58 +04:00
}
}
2022-01-11 20:43:59 +09:00
return ;
2021-12-11 04:27:58 +04:00
}
2022-01-12 16:41:27 +09:00
/ * *
* Create all missing tables
* /
private async $createMissingTablesAndIndexes ( databaseSchemaVersion : number ) {
await this . $setStatisticsAddedIndexedFlag ( databaseSchemaVersion ) ;
2022-02-16 15:19:16 +09:00
const isBitcoin = [ 'mainnet' , 'testnet' , 'signet' ] . includes ( config . MEMPOOL . NETWORK ) ;
2022-01-12 16:41:27 +09:00
try {
2022-04-12 15:15:57 +09:00
await this . $executeQuery ( this . getCreateElementsTableQuery ( ) , await this . $checkIfTableExists ( 'elements_pegs' ) ) ;
await this . $executeQuery ( this . getCreateStatisticsQuery ( ) , await this . $checkIfTableExists ( 'statistics' ) ) ;
2022-01-12 16:41:27 +09:00
if ( databaseSchemaVersion < 2 && this . statisticsAddedIndexed === false ) {
2022-04-12 15:15:57 +09:00
await this . $executeQuery ( ` CREATE INDEX added ON statistics (added); ` ) ;
2022-01-12 16:41:27 +09:00
}
2022-01-19 18:50:52 +09:00
if ( databaseSchemaVersion < 3 ) {
2022-04-12 15:15:57 +09:00
await this . $executeQuery ( this . getCreatePoolsTableQuery ( ) , await this . $checkIfTableExists ( 'pools' ) ) ;
2022-01-18 17:37:04 +09:00
}
if ( databaseSchemaVersion < 4 ) {
2022-04-12 15:15:57 +09:00
await this . $executeQuery ( 'DROP table IF EXISTS blocks;' ) ;
await this . $executeQuery ( this . getCreateBlocksTableQuery ( ) , await this . $checkIfTableExists ( 'blocks' ) ) ;
2022-01-19 18:50:52 +09:00
}
2022-02-16 15:19:16 +09:00
if ( databaseSchemaVersion < 5 && isBitcoin === true ) {
2022-06-13 10:12:27 +02:00
this . uniqueLog ( logger . notice , this . blocksTruncatedMessage ) ;
2022-04-12 15:15:57 +09:00
await this . $executeQuery ( 'TRUNCATE blocks;' ) ; // Need to re-index
await this . $executeQuery ( 'ALTER TABLE blocks ADD `reward` double unsigned NOT NULL DEFAULT "0"' ) ;
2022-02-16 15:19:16 +09:00
}
if ( databaseSchemaVersion < 6 && isBitcoin === true ) {
2022-06-13 10:12:27 +02:00
this . uniqueLog ( logger . notice , this . blocksTruncatedMessage ) ;
2022-04-12 15:15:57 +09:00
await this . $executeQuery ( 'TRUNCATE blocks;' ) ; // Need to re-index
2022-02-16 15:19:16 +09:00
// Cleanup original blocks fields type
2022-04-12 15:15:57 +09:00
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"' ) ;
2022-02-16 15:19:16 +09:00
// We also fix the pools.id type so we need to drop/re-create the foreign key
2022-04-12 15:15:57 +09:00
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`)' ) ;
2022-02-16 15:19:16 +09:00
// Add new block indexing fields
2022-04-12 15:15:57 +09:00
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' ) ;
2022-02-16 15:19:16 +09:00
}
2022-02-19 20:45:02 +09:00
if ( databaseSchemaVersion < 7 && isBitcoin === true ) {
2022-04-12 15:15:57 +09:00
await this . $executeQuery ( 'DROP table IF EXISTS hashrates;' ) ;
await this . $executeQuery ( this . getCreateDailyStatsTableQuery ( ) , await this . $checkIfTableExists ( 'hashrates' ) ) ;
2022-02-19 20:45:02 +09:00
}
2022-02-24 16:55:18 +09:00
if ( databaseSchemaVersion < 8 && isBitcoin === true ) {
2022-06-13 10:12:27 +02:00
this . uniqueLog ( logger . notice , this . blocksTruncatedMessage ) ;
2022-04-12 15:15:57 +09:00
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"' ) ;
2022-02-24 16:55:18 +09:00
}
2022-03-07 15:56:07 +01:00
if ( databaseSchemaVersion < 9 && isBitcoin === true ) {
2022-06-13 10:12:27 +02:00
this . uniqueLog ( logger . notice , this . hashratesTruncatedMessage ) ;
2022-04-12 15:15:57 +09:00
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`)' ) ;
2022-03-06 12:32:16 +01:00
}
2022-03-08 17:10:29 +01:00
if ( databaseSchemaVersion < 10 && isBitcoin === true ) {
2022-04-12 15:15:57 +09:00
await this . $executeQuery ( 'ALTER TABLE `blocks` ADD INDEX `blockTimestamp` (`blockTimestamp`)' ) ;
2022-03-08 17:10:29 +01:00
}
2022-03-09 19:18:51 +01:00
if ( databaseSchemaVersion < 11 && isBitcoin === true ) {
2022-06-13 10:12:27 +02:00
this . uniqueLog ( logger . notice , this . blocksTruncatedMessage ) ;
2022-04-12 15:15:57 +09:00
await this . $executeQuery ( 'TRUNCATE blocks;' ) ; // Need to re-index
await this . $executeQuery ( ` ALTER TABLE blocks
2022-03-10 14:21:11 +01:00
ADD avg_fee INT UNSIGNED NULL ,
ADD avg_fee_rate INT UNSIGNED NULL
2022-03-09 19:18:51 +01:00
` );
2022-04-12 15:15:57 +09:00
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"' ) ;
2022-03-09 19:18:51 +01:00
}
2022-03-11 20:42:07 +01:00
if ( databaseSchemaVersion < 12 && isBitcoin === true ) {
// No need to re-index because the new data type can contain larger values
2022-04-12 15:15:57 +09:00
await this . $executeQuery ( 'ALTER TABLE blocks MODIFY `fees` BIGINT UNSIGNED NOT NULL DEFAULT "0"' ) ;
2022-03-11 20:42:07 +01:00
}
2022-03-12 11:06:37 +01:00
if ( databaseSchemaVersion < 13 && isBitcoin === true ) {
2022-04-12 15:15:57 +09:00
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"' ) ;
2022-03-12 11:06:37 +01:00
}
2022-03-12 14:47:33 +01:00
if ( databaseSchemaVersion < 14 && isBitcoin === true ) {
2022-06-13 10:12:27 +02:00
this . uniqueLog ( logger . notice , this . hashratesTruncatedMessage ) ;
2022-04-12 15:15:57 +09:00
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"' ) ;
2022-03-12 14:47:33 +01:00
}
2022-03-24 07:40:03 +09:00
if ( databaseSchemaVersion < 16 && isBitcoin === true ) {
2022-06-13 10:12:27 +02:00
this . uniqueLog ( logger . notice , this . hashratesTruncatedMessage ) ;
2022-04-12 15:15:57 +09:00
await this . $executeQuery ( 'TRUNCATE hashrates;' ) ; // Need to re-index because we changed timestamps
2022-03-24 07:40:03 +09:00
}
2022-03-24 19:44:22 +09:00
if ( databaseSchemaVersion < 17 && isBitcoin === true ) {
2022-04-12 15:15:57 +09:00
await this . $executeQuery ( 'ALTER TABLE `pools` ADD `slug` CHAR(50) NULL' ) ;
2022-03-24 19:44:22 +09:00
}
2022-05-19 12:13:43 +02:00
if ( databaseSchemaVersion < 18 && isBitcoin === true ) {
await this . $executeQuery ( 'ALTER TABLE `blocks` ADD INDEX `hash` (`hash`);' ) ;
}
2022-05-25 10:51:35 +02:00
2022-05-31 11:20:20 +02:00
if ( databaseSchemaVersion < 19 ) {
2022-05-25 10:51:35 +02:00
await this . $executeQuery ( this . getCreateRatesTableQuery ( ) , await this . $checkIfTableExists ( 'rates' ) ) ;
}
2022-06-18 16:48:02 +02:00
if ( databaseSchemaVersion < 20 && isBitcoin === true ) {
await this . $executeQuery ( this . getCreateBlocksSummariesTableQuery ( ) , await this . $checkIfTableExists ( 'blocks_summaries' ) ) ;
}
2022-06-23 15:42:42 +02:00
if ( databaseSchemaVersion < 21 ) {
await this . $executeQuery ( 'DROP TABLE IF EXISTS `rates`' ) ;
await this . $executeQuery ( this . getCreatePricesTableQuery ( ) , await this . $checkIfTableExists ( 'prices' ) ) ;
}
2022-06-25 12:14:32 +02:00
if ( databaseSchemaVersion < 22 && isBitcoin === true ) {
await this . $executeQuery ( 'DROP TABLE IF EXISTS `difficulty_adjustments`' ) ;
await this . $executeQuery ( this . getCreateDifficultyAdjustmentsTableQuery ( ) , await this . $checkIfTableExists ( 'difficulty_adjustments' ) ) ;
}
2022-06-26 13:49:39 +02:00
if ( databaseSchemaVersion < 23 ) {
await this . $executeQuery ( 'TRUNCATE `prices`' ) ;
await this . $executeQuery ( 'ALTER TABLE `prices` DROP `avg_prices`' ) ;
await this . $executeQuery ( 'ALTER TABLE `prices` ADD `USD` float DEFAULT "0"' ) ;
await this . $executeQuery ( 'ALTER TABLE `prices` ADD `EUR` float DEFAULT "0"' ) ;
await this . $executeQuery ( 'ALTER TABLE `prices` ADD `GBP` float DEFAULT "0"' ) ;
await this . $executeQuery ( 'ALTER TABLE `prices` ADD `CAD` float DEFAULT "0"' ) ;
await this . $executeQuery ( 'ALTER TABLE `prices` ADD `CHF` float DEFAULT "0"' ) ;
await this . $executeQuery ( 'ALTER TABLE `prices` ADD `AUD` float DEFAULT "0"' ) ;
await this . $executeQuery ( 'ALTER TABLE `prices` ADD `JPY` float DEFAULT "0"' ) ;
}
2022-01-12 16:41:27 +09:00
} catch ( e ) {
throw e ;
}
}
2022-01-12 14:10:16 +09:00
/ * *
* Special case here for the ` statistics ` table - It appeared that somehow some dbs already had the ` added ` field indexed
* while it does not appear in previous schemas . The mariadb command "CREATE INDEX IF NOT EXISTS" is not supported on
* older mariadb version . Therefore we set a flag here in order to know if the index needs to be created or not before
* running the migration process
* /
private async $setStatisticsAddedIndexedFlag ( databaseSchemaVersion : number ) {
if ( databaseSchemaVersion >= 2 ) {
this . statisticsAddedIndexed = true ;
return ;
}
try {
2022-01-12 16:41:27 +09:00
// We don't use "CREATE INDEX IF NOT EXISTS" because it is not supported on old mariadb version 5.X
2022-01-12 14:10:16 +09:00
const query = ` SELECT COUNT(1) hasIndex FROM INFORMATION_SCHEMA.STATISTICS
WHERE table_schema = DATABASE ( ) AND table_name = 'statistics' AND index_name = 'added' ; ` ;
2022-04-12 15:15:57 +09:00
const [ rows ] = await this . $executeQuery ( query , true ) ;
2022-01-12 14:10:16 +09:00
if ( rows [ 0 ] . hasIndex === 0 ) {
2022-02-21 23:57:44 +09:00
logger . debug ( 'MIGRATIONS: `statistics.added` is not indexed' ) ;
2022-01-12 14:10:16 +09:00
this . statisticsAddedIndexed = false ;
} else if ( rows [ 0 ] . hasIndex === 1 ) {
2022-02-21 23:57:44 +09:00
logger . debug ( 'MIGRATIONS: `statistics.added` is already indexed' ) ;
2022-01-12 14:10:16 +09:00
this . statisticsAddedIndexed = true ;
}
} catch ( e ) {
// Should really never happen but just in case it fails, we just don't execute
// any query related to this indexing so it won't fail if the index actually already exists
logger . err ( 'MIGRATIONS: Unable to check if `statistics.added` INDEX exist or not.' ) ;
this . statisticsAddedIndexed = true ;
}
}
2022-01-11 20:43:59 +09:00
/ * *
* Small query execution wrapper to log all executed queries
* /
2022-04-12 15:15:57 +09:00
private async $executeQuery ( query : string , silent : boolean = false ) : Promise < any > {
2022-01-12 16:06:45 +09:00
if ( ! silent ) {
2022-02-21 23:57:44 +09:00
logger . debug ( 'MIGRATIONS: Execute query:\n' + query ) ;
2022-01-12 16:06:45 +09:00
}
2022-04-12 15:15:57 +09:00
return DB . query ( { sql : query , timeout : this.queryTimeout } ) ;
2021-12-11 04:27:58 +04:00
}
2022-01-11 20:43:59 +09:00
/ * *
* Check if 'table' exists in the database
* /
private async $checkIfTableExists ( table : string ) : Promise < boolean > {
const query = ` SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = ' ${ config . DATABASE . DATABASE } ' AND TABLE_NAME = ' ${ table } ' ` ;
2022-04-12 15:15:57 +09:00
const [ rows ] = await DB . query ( { sql : query , timeout : this.queryTimeout } ) ;
2022-01-11 20:43:59 +09:00
return rows [ 0 ] [ 'COUNT(*)' ] === 1 ;
2021-12-11 04:27:58 +04:00
}
2022-01-11 20:43:59 +09:00
/ * *
* Get current database version
* /
2021-12-11 04:27:58 +04:00
private async $getSchemaVersionFromDatabase ( ) : Promise < number > {
const query = ` SELECT number FROM state WHERE name = 'schema_version'; ` ;
2022-04-12 15:15:57 +09:00
const [ rows ] = await this . $executeQuery ( query , true ) ;
2021-12-11 04:27:58 +04:00
return rows [ 0 ] [ 'number' ] ;
}
2022-01-11 20:43:59 +09:00
/ * *
* Create the ` state ` table
* /
private async $createMigrationStateTable ( ) : Promise < void > {
try {
const query = ` CREATE TABLE IF NOT EXISTS state (
name varchar ( 25 ) NOT NULL ,
number int ( 11 ) NULL ,
string varchar ( 100 ) NULL ,
CONSTRAINT name_unique UNIQUE ( name )
) ENGINE = InnoDB DEFAULT CHARSET = utf8 ; ` ;
2022-04-12 15:15:57 +09:00
await this . $executeQuery ( query ) ;
2022-01-11 20:43:59 +09:00
// Set initial values
2022-04-12 15:15:57 +09:00
await this . $executeQuery ( ` INSERT INTO state VALUES('schema_version', 0, NULL); ` ) ;
await this . $executeQuery ( ` INSERT INTO state VALUES('last_elements_block', 0, NULL); ` ) ;
2022-01-11 20:43:59 +09:00
} catch ( e ) {
throw e ;
}
2021-12-11 04:27:58 +04:00
}
2022-01-11 20:43:59 +09:00
/ * *
2022-01-12 16:41:27 +09:00
* We actually execute the migrations queries here
2022-01-11 20:43:59 +09:00
* /
private async $migrateTableSchemaFromVersion ( version : number ) : Promise < void > {
2022-01-12 14:10:16 +09:00
const transactionQueries : string [ ] = [ ] ;
2022-01-11 20:43:59 +09:00
for ( const query of this . getMigrationQueriesFromVersion ( version ) ) {
transactionQueries . push ( query ) ;
}
2022-06-13 10:12:27 +02:00
logger . notice ( ` MIGRATIONS: ${ version > 0 ? 'Upgrading' : 'Initializing' } database schema version number to ${ DatabaseMigration . currentVersion } ` ) ;
2022-01-11 20:43:59 +09:00
transactionQueries . push ( this . getUpdateToLatestSchemaVersionQuery ( ) ) ;
try {
2022-04-12 15:15:57 +09:00
await this . $executeQuery ( 'START TRANSACTION;' ) ;
2022-01-11 20:43:59 +09:00
for ( const query of transactionQueries ) {
2022-04-12 15:15:57 +09:00
await this . $executeQuery ( query ) ;
2022-01-11 20:43:59 +09:00
}
2022-04-12 15:15:57 +09:00
await this . $executeQuery ( 'COMMIT;' ) ;
2022-01-11 20:43:59 +09:00
} catch ( e ) {
2022-04-12 15:15:57 +09:00
await this . $executeQuery ( 'ROLLBACK;' ) ;
2022-01-11 20:43:59 +09:00
throw e ;
}
2021-12-11 04:27:58 +04:00
}
2022-01-11 20:43:59 +09:00
/ * *
* Generate migration queries based on schema version
* /
private getMigrationQueriesFromVersion ( version : number ) : string [ ] {
2021-12-11 04:27:58 +04:00
const queries : string [ ] = [ ] ;
2022-03-11 21:58:25 +01:00
const isBitcoin = [ 'mainnet' , 'testnet' , 'signet' ] . includes ( config . MEMPOOL . NETWORK ) ;
2021-12-11 04:27:58 +04:00
2022-01-11 20:43:59 +09:00
if ( version < 1 ) {
if ( config . MEMPOOL . NETWORK !== 'liquid' && config . MEMPOOL . NETWORK !== 'liquidtestnet' ) {
2022-06-13 10:12:27 +02:00
if ( version > 0 ) {
logger . notice ( ` MIGRATIONS: Migrating (shifting) statistics table data ` ) ;
}
2022-01-12 14:10:16 +09:00
queries . push ( this . getShiftStatisticsQuery ( ) ) ;
2022-01-11 20:43:59 +09:00
}
}
2022-03-11 21:58:25 +01:00
if ( version < 7 && isBitcoin === true ) {
2022-02-21 12:22:20 +09:00
queries . push ( ` INSERT INTO state(name, number, string) VALUES ('last_hashrates_indexing', 0, NULL) ` ) ;
}
2022-03-11 21:58:25 +01:00
if ( version < 9 && isBitcoin === true ) {
2022-03-06 12:32:16 +01:00
queries . push ( ` INSERT INTO state(name, number, string) VALUES ('last_weekly_hashrates_indexing', 0, NULL) ` ) ;
}
2022-01-11 20:43:59 +09:00
return queries ;
}
/ * *
* Save the schema version in the database
* /
2022-01-12 14:10:16 +09:00
private getUpdateToLatestSchemaVersionQuery ( ) : string {
2022-01-11 20:43:59 +09:00
return ` UPDATE state SET number = ${ DatabaseMigration . currentVersion } WHERE name = 'schema_version'; ` ;
}
2022-01-12 16:06:45 +09:00
/ * *
* Print current database version
* /
private async $printDatabaseVersion() {
try {
2022-04-12 15:15:57 +09:00
const [ rows ] = await this . $executeQuery ( 'SELECT VERSION() as version;' , true ) ;
2022-02-21 23:57:44 +09:00
logger . debug ( ` MIGRATIONS: Database engine version ' ${ rows [ 0 ] . version } ' ` ) ;
2022-01-12 16:06:45 +09:00
} catch ( e ) {
2022-02-21 23:57:44 +09:00
logger . debug ( ` MIGRATIONS: Could not fetch database engine version. ` + e ) ;
2022-01-12 16:06:45 +09:00
}
}
2022-01-11 20:43:59 +09:00
// Couple of wrappers to clean the main logic
2022-01-12 16:41:27 +09:00
private getShiftStatisticsQuery ( ) : string {
return ` UPDATE statistics SET
vsize_1 = vsize_1 + vsize_2 , vsize_2 = vsize_3 ,
vsize_3 = vsize_4 , vsize_4 = vsize_5 ,
vsize_5 = vsize_6 , vsize_6 = vsize_8 ,
vsize_8 = vsize_10 , vsize_10 = vsize_12 ,
vsize_12 = vsize_15 , vsize_15 = vsize_20 ,
vsize_20 = vsize_30 , vsize_30 = vsize_40 ,
vsize_40 = vsize_50 , vsize_50 = vsize_60 ,
vsize_60 = vsize_70 , vsize_70 = vsize_80 ,
vsize_80 = vsize_90 , vsize_90 = vsize_100 ,
vsize_100 = vsize_125 , vsize_125 = vsize_150 ,
vsize_150 = vsize_175 , vsize_175 = vsize_200 ,
vsize_200 = vsize_250 , vsize_250 = vsize_300 ,
vsize_300 = vsize_350 , vsize_350 = vsize_400 ,
vsize_400 = vsize_500 , vsize_500 = vsize_600 ,
vsize_600 = vsize_700 , vsize_700 = vsize_800 ,
vsize_800 = vsize_900 , vsize_900 = vsize_1000 ,
vsize_1000 = vsize_1200 , vsize_1200 = vsize_1400 ,
vsize_1400 = vsize_1800 , vsize_1800 = vsize_2000 , vsize_2000 = 0 ; ` ;
}
2022-01-11 20:43:59 +09:00
private getCreateStatisticsQuery ( ) : string {
return ` CREATE TABLE IF NOT EXISTS statistics (
id int ( 11 ) NOT NULL AUTO_INCREMENT ,
2021-12-11 04:27:58 +04:00
added datetime NOT NULL ,
unconfirmed_transactions int ( 11 ) UNSIGNED NOT NULL ,
tx_per_second float UNSIGNED NOT NULL ,
vbytes_per_second int ( 10 ) UNSIGNED NOT NULL ,
mempool_byte_weight int ( 10 ) UNSIGNED NOT NULL ,
fee_data longtext NOT NULL ,
total_fee double UNSIGNED NOT NULL ,
vsize_1 int ( 11 ) NOT NULL ,
vsize_2 int ( 11 ) NOT NULL ,
vsize_3 int ( 11 ) NOT NULL ,
vsize_4 int ( 11 ) NOT NULL ,
vsize_5 int ( 11 ) NOT NULL ,
vsize_6 int ( 11 ) NOT NULL ,
vsize_8 int ( 11 ) NOT NULL ,
vsize_10 int ( 11 ) NOT NULL ,
vsize_12 int ( 11 ) NOT NULL ,
vsize_15 int ( 11 ) NOT NULL ,
vsize_20 int ( 11 ) NOT NULL ,
vsize_30 int ( 11 ) NOT NULL ,
vsize_40 int ( 11 ) NOT NULL ,
vsize_50 int ( 11 ) NOT NULL ,
vsize_60 int ( 11 ) NOT NULL ,
vsize_70 int ( 11 ) NOT NULL ,
vsize_80 int ( 11 ) NOT NULL ,
vsize_90 int ( 11 ) NOT NULL ,
vsize_100 int ( 11 ) NOT NULL ,
vsize_125 int ( 11 ) NOT NULL ,
vsize_150 int ( 11 ) NOT NULL ,
vsize_175 int ( 11 ) NOT NULL ,
vsize_200 int ( 11 ) NOT NULL ,
vsize_250 int ( 11 ) NOT NULL ,
vsize_300 int ( 11 ) NOT NULL ,
vsize_350 int ( 11 ) NOT NULL ,
vsize_400 int ( 11 ) NOT NULL ,
vsize_500 int ( 11 ) NOT NULL ,
vsize_600 int ( 11 ) NOT NULL ,
vsize_700 int ( 11 ) NOT NULL ,
vsize_800 int ( 11 ) NOT NULL ,
vsize_900 int ( 11 ) NOT NULL ,
vsize_1000 int ( 11 ) NOT NULL ,
vsize_1200 int ( 11 ) NOT NULL ,
vsize_1400 int ( 11 ) NOT NULL ,
vsize_1600 int ( 11 ) NOT NULL ,
vsize_1800 int ( 11 ) NOT NULL ,
2022-01-11 20:43:59 +09:00
vsize_2000 int ( 11 ) NOT NULL ,
CONSTRAINT PRIMARY KEY ( id )
2022-01-12 16:06:45 +09:00
) ENGINE = InnoDB DEFAULT CHARSET = utf8 ; ` ;
2021-12-11 04:27:58 +04:00
}
2022-01-12 16:41:27 +09:00
2022-01-11 20:43:59 +09:00
private getCreateElementsTableQuery ( ) : string {
return ` CREATE TABLE IF NOT EXISTS elements_pegs (
block int ( 11 ) NOT NULL ,
datetime int ( 11 ) NOT NULL ,
amount bigint ( 20 ) NOT NULL ,
txid varchar ( 65 ) NOT NULL ,
txindex int ( 11 ) NOT NULL ,
bitcoinaddress varchar ( 100 ) NOT NULL ,
bitcointxid varchar ( 65 ) NOT NULL ,
bitcoinindex int ( 11 ) NOT NULL ,
final_tx int ( 11 ) NOT NULL
2022-01-12 16:06:45 +09:00
) ENGINE = InnoDB DEFAULT CHARSET = utf8 ; ` ;
2021-12-11 04:27:58 +04:00
}
2022-01-19 18:50:52 +09:00
2022-01-13 12:18:39 +09:00
private getCreatePoolsTableQuery ( ) : string {
return ` CREATE TABLE IF NOT EXISTS pools (
id int ( 11 ) NOT NULL AUTO_INCREMENT ,
name varchar ( 50 ) NOT NULL ,
link varchar ( 255 ) NOT NULL ,
addresses text NOT NULL ,
regexes text NOT NULL ,
PRIMARY KEY ( id )
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 ; ` ;
}
private getCreateBlocksTableQuery ( ) : string {
return ` CREATE TABLE IF NOT EXISTS blocks (
height int ( 11 ) unsigned NOT NULL ,
hash varchar ( 65 ) NOT NULL ,
blockTimestamp timestamp NOT NULL ,
size int ( 11 ) unsigned NOT NULL ,
weight int ( 11 ) unsigned NOT NULL ,
tx_count int ( 11 ) unsigned NOT NULL ,
coinbase_raw text ,
difficulty bigint ( 20 ) unsigned NOT NULL ,
pool_id int ( 11 ) DEFAULT - 1 ,
fees double unsigned NOT NULL ,
fee_span json NOT NULL ,
median_fee double unsigned NOT NULL ,
PRIMARY KEY ( height ) ,
INDEX ( pool_id ) ,
FOREIGN KEY ( pool_id ) REFERENCES pools ( id )
) ENGINE = InnoDB DEFAULT CHARSET = utf8 ; ` ;
}
2022-02-19 20:45:02 +09:00
private getCreateDailyStatsTableQuery ( ) : string {
return ` CREATE TABLE IF NOT EXISTS hashrates (
hashrate_timestamp timestamp NOT NULL ,
avg_hashrate double unsigned DEFAULT '0' ,
pool_id smallint unsigned NULL ,
PRIMARY KEY ( hashrate_timestamp ) ,
INDEX ( pool_id ) ,
FOREIGN KEY ( pool_id ) REFERENCES pools ( id )
) ENGINE = InnoDB DEFAULT CHARSET = utf8 ; ` ;
}
2022-02-21 16:38:18 +09:00
2022-06-25 12:14:32 +02:00
private getCreateRatesTableQuery ( ) : string { // This table has been replaced by the prices table
2022-05-25 10:51:35 +02:00
return ` CREATE TABLE IF NOT EXISTS rates (
height int ( 10 ) unsigned NOT NULL ,
bisq_rates JSON NOT NULL ,
PRIMARY KEY ( height )
) ENGINE = InnoDB DEFAULT CHARSET = utf8 ; ` ;
}
2022-06-18 16:48:02 +02:00
private getCreateBlocksSummariesTableQuery ( ) : string {
return ` CREATE TABLE IF NOT EXISTS blocks_summaries (
height int ( 10 ) unsigned NOT NULL ,
id varchar ( 65 ) NOT NULL ,
transactions JSON NOT NULL ,
2022-06-20 16:35:10 +02:00
PRIMARY KEY ( id ) ,
INDEX ( height )
) ENGINE = InnoDB DEFAULT CHARSET = utf8 ; ` ;
2022-06-18 16:48:02 +02:00
}
2022-06-23 15:42:42 +02:00
private getCreatePricesTableQuery ( ) : string {
return ` CREATE TABLE IF NOT EXISTS prices (
time timestamp NOT NULL ,
avg_prices JSON NOT NULL ,
PRIMARY KEY ( time )
) ENGINE = InnoDB DEFAULT CHARSET = utf8 ; ` ;
}
2022-06-25 12:14:32 +02:00
private getCreateDifficultyAdjustmentsTableQuery ( ) : string {
return ` CREATE TABLE IF NOT EXISTS difficulty_adjustments (
time timestamp NOT NULL ,
height int ( 10 ) unsigned NOT NULL ,
difficulty double unsigned NOT NULL ,
adjustment float NOT NULL ,
PRIMARY KEY ( height ) ,
INDEX ( time )
) ENGINE = InnoDB DEFAULT CHARSET = utf8 ; ` ;
}
2022-02-21 16:38:18 +09:00
public async $truncateIndexedData ( tables : string [ ] ) {
2022-06-23 15:42:42 +02:00
const allowedTables = [ 'blocks' , 'hashrates' , 'prices' ] ;
2022-02-21 16:38:18 +09:00
try {
for ( const table of tables ) {
if ( ! allowedTables . includes ( table ) ) {
2022-02-21 23:57:44 +09:00
logger . debug ( ` Table ${ table } cannot to be re-indexed (not allowed) ` ) ;
2022-02-21 16:38:18 +09:00
continue ;
2022-04-12 15:15:57 +09:00
}
2022-02-21 16:38:18 +09:00
2022-04-12 15:15:57 +09:00
await this . $executeQuery ( ` TRUNCATE ${ table } ` , true ) ;
2022-02-21 16:38:18 +09:00
if ( table === 'hashrates' ) {
2022-04-12 15:15:57 +09:00
await this . $executeQuery ( 'UPDATE state set number = 0 where name = "last_hashrates_indexing"' , true ) ;
2022-02-21 16:38:18 +09:00
}
2022-02-21 23:57:44 +09:00
logger . notice ( ` Table ${ table } has been truncated ` ) ;
2022-02-21 16:38:18 +09:00
}
} catch ( e ) {
logger . warn ( ` Unable to erase indexed data ` ) ;
}
}
2021-12-11 04:27:58 +04:00
}
2022-01-06 02:26:07 +09:00
export default new DatabaseMigration ( ) ;