Merge branch 'master' into update_gha
This commit is contained in:
		
						commit
						d17ccbc5ae
					
				@ -25,7 +25,8 @@
 | 
				
			|||||||
    "AUTOMATIC_BLOCK_REINDEXING": false,
 | 
					    "AUTOMATIC_BLOCK_REINDEXING": false,
 | 
				
			||||||
    "POOLS_JSON_URL": "https://raw.githubusercontent.com/mempool/mining-pools/master/pools.json",
 | 
					    "POOLS_JSON_URL": "https://raw.githubusercontent.com/mempool/mining-pools/master/pools.json",
 | 
				
			||||||
    "POOLS_JSON_TREE_URL": "https://api.github.com/repos/mempool/mining-pools/git/trees/master",
 | 
					    "POOLS_JSON_TREE_URL": "https://api.github.com/repos/mempool/mining-pools/git/trees/master",
 | 
				
			||||||
    "ADVANCED_TRANSACTION_SELECTION": false,
 | 
					    "ADVANCED_GBT_AUDIT": false,
 | 
				
			||||||
 | 
					    "ADVANCED_GBT_MEMPOOL": false,
 | 
				
			||||||
    "TRANSACTION_INDEXING": false
 | 
					    "TRANSACTION_INDEXING": false
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "CORE_RPC": {
 | 
					  "CORE_RPC": {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										3593
									
								
								backend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3593
									
								
								backend/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -34,35 +34,35 @@
 | 
				
			|||||||
    "prettier": "./node_modules/.bin/prettier --write \"src/**/*.{js,ts}\""
 | 
					    "prettier": "./node_modules/.bin/prettier --write \"src/**/*.{js,ts}\""
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@babel/core": "^7.18.6",
 | 
					    "@babel/core": "^7.20.5",
 | 
				
			||||||
    "@mempool/electrum-client": "^1.1.7",
 | 
					    "@mempool/electrum-client": "^1.1.7",
 | 
				
			||||||
    "@types/node": "^16.11.41",
 | 
					    "@types/node": "^16.11.41",
 | 
				
			||||||
    "axios": "~0.27.2",
 | 
					    "axios": "~0.27.2",
 | 
				
			||||||
    "bitcoinjs-lib": "6.0.2",
 | 
					    "bitcoinjs-lib": "~6.0.2",
 | 
				
			||||||
    "crypto-js": "^4.0.0",
 | 
					    "crypto-js": "~4.1.1",
 | 
				
			||||||
    "express": "^4.18.0",
 | 
					    "express": "~4.18.2",
 | 
				
			||||||
    "maxmind": "^4.3.6",
 | 
					    "maxmind": "~4.3.8",
 | 
				
			||||||
    "mysql2": "2.3.3",
 | 
					    "mysql2": "~2.3.3",
 | 
				
			||||||
    "node-worker-threads-pool": "^1.5.1",
 | 
					    "node-worker-threads-pool": "~1.5.1",
 | 
				
			||||||
    "socks-proxy-agent": "~7.0.0",
 | 
					    "socks-proxy-agent": "~7.0.0",
 | 
				
			||||||
    "typescript": "~4.7.4",
 | 
					    "typescript": "~4.7.4",
 | 
				
			||||||
    "ws": "~8.8.0"
 | 
					    "ws": "~8.11.0"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@babel/core": "^7.18.6",
 | 
					    "@babel/core": "^7.20.5",
 | 
				
			||||||
    "@babel/code-frame": "^7.18.6",
 | 
					    "@babel/code-frame": "^7.18.6",
 | 
				
			||||||
    "@types/compression": "^1.7.2",
 | 
					    "@types/compression": "^1.7.2",
 | 
				
			||||||
    "@types/crypto-js": "^4.1.1",
 | 
					    "@types/crypto-js": "^4.1.1",
 | 
				
			||||||
    "@types/express": "^4.17.13",
 | 
					    "@types/express": "^4.17.14",
 | 
				
			||||||
    "@types/jest": "^28.1.4",
 | 
					    "@types/jest": "^29.2.3",
 | 
				
			||||||
    "@types/ws": "~8.5.3",
 | 
					    "@types/ws": "~8.5.3",
 | 
				
			||||||
    "@typescript-eslint/eslint-plugin": "^5.30.5",
 | 
					    "@typescript-eslint/eslint-plugin": "^5.45.0",
 | 
				
			||||||
    "@typescript-eslint/parser": "^5.30.5",
 | 
					    "@typescript-eslint/parser": "^5.45.0",
 | 
				
			||||||
    "eslint": "^8.19.0",
 | 
					    "eslint": "^8.28.0",
 | 
				
			||||||
    "eslint-config-prettier": "^8.5.0",
 | 
					    "eslint-config-prettier": "^8.5.0",
 | 
				
			||||||
    "jest": "^28.1.2",
 | 
					    "jest": "^29.3.1",
 | 
				
			||||||
    "prettier": "^2.7.1",
 | 
					    "prettier": "^2.8.0",
 | 
				
			||||||
    "ts-jest": "^28.0.5",
 | 
					    "ts-jest": "^29.0.3",
 | 
				
			||||||
    "ts-node": "^10.8.2"
 | 
					    "ts-node": "^10.9.1"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -26,7 +26,8 @@
 | 
				
			|||||||
    "INDEXING_BLOCKS_AMOUNT": 14,
 | 
					    "INDEXING_BLOCKS_AMOUNT": 14,
 | 
				
			||||||
    "POOLS_JSON_TREE_URL": "__POOLS_JSON_TREE_URL__",
 | 
					    "POOLS_JSON_TREE_URL": "__POOLS_JSON_TREE_URL__",
 | 
				
			||||||
    "POOLS_JSON_URL": "__POOLS_JSON_URL__",
 | 
					    "POOLS_JSON_URL": "__POOLS_JSON_URL__",
 | 
				
			||||||
    "ADVANCED_TRANSACTION_SELECTION": "__ADVANCED_TRANSACTION_SELECTION__",
 | 
					    "ADVANCED_GBT_AUDIT": "__ADVANCED_GBT_AUDIT__",
 | 
				
			||||||
 | 
					    "ADVANCED_GBT_MEMPOOL": "__ADVANCED_GBT_MEMPOOL__",
 | 
				
			||||||
    "TRANSACTION_INDEXING": "__TRANSACTION_INDEXING__"
 | 
					    "TRANSACTION_INDEXING": "__TRANSACTION_INDEXING__"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "CORE_RPC": {
 | 
					  "CORE_RPC": {
 | 
				
			||||||
 | 
				
			|||||||
@ -38,7 +38,8 @@ describe('Mempool Backend Config', () => {
 | 
				
			|||||||
        STDOUT_LOG_MIN_PRIORITY: 'debug',
 | 
					        STDOUT_LOG_MIN_PRIORITY: 'debug',
 | 
				
			||||||
        POOLS_JSON_TREE_URL: 'https://api.github.com/repos/mempool/mining-pools/git/trees/master',
 | 
					        POOLS_JSON_TREE_URL: 'https://api.github.com/repos/mempool/mining-pools/git/trees/master',
 | 
				
			||||||
        POOLS_JSON_URL: 'https://raw.githubusercontent.com/mempool/mining-pools/master/pools.json',
 | 
					        POOLS_JSON_URL: 'https://raw.githubusercontent.com/mempool/mining-pools/master/pools.json',
 | 
				
			||||||
        ADVANCED_TRANSACTION_SELECTION: false,
 | 
					        ADVANCED_GBT_AUDIT: false,
 | 
				
			||||||
 | 
					        ADVANCED_GBT_MEMPOOL: false,
 | 
				
			||||||
        TRANSACTION_INDEXING: false,
 | 
					        TRANSACTION_INDEXING: false,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -4,8 +4,8 @@ import logger from '../logger';
 | 
				
			|||||||
import { Common } from './common';
 | 
					import { Common } from './common';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DatabaseMigration {
 | 
					class DatabaseMigration {
 | 
				
			||||||
  private static currentVersion = 47;
 | 
					  private static currentVersion = 49;
 | 
				
			||||||
  private queryTimeout = 900_000;
 | 
					  private queryTimeout = 3600_000;
 | 
				
			||||||
  private statisticsAddedIndexed = false;
 | 
					  private statisticsAddedIndexed = false;
 | 
				
			||||||
  private uniqueLogs: string[] = [];
 | 
					  private uniqueLogs: string[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -107,18 +107,22 @@ class DatabaseMigration {
 | 
				
			|||||||
    await this.$executeQuery(this.getCreateStatisticsQuery(), await this.$checkIfTableExists('statistics'));
 | 
					    await this.$executeQuery(this.getCreateStatisticsQuery(), await this.$checkIfTableExists('statistics'));
 | 
				
			||||||
    if (databaseSchemaVersion < 2 && this.statisticsAddedIndexed === false) {
 | 
					    if (databaseSchemaVersion < 2 && this.statisticsAddedIndexed === false) {
 | 
				
			||||||
      await this.$executeQuery(`CREATE INDEX added ON statistics (added);`);
 | 
					      await this.$executeQuery(`CREATE INDEX added ON statistics (added);`);
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(2);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (databaseSchemaVersion < 3) {
 | 
					    if (databaseSchemaVersion < 3) {
 | 
				
			||||||
      await this.$executeQuery(this.getCreatePoolsTableQuery(), await this.$checkIfTableExists('pools'));
 | 
					      await this.$executeQuery(this.getCreatePoolsTableQuery(), await this.$checkIfTableExists('pools'));
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(3);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (databaseSchemaVersion < 4) {
 | 
					    if (databaseSchemaVersion < 4) {
 | 
				
			||||||
      await this.$executeQuery('DROP table IF EXISTS blocks;');
 | 
					      await this.$executeQuery('DROP table IF EXISTS blocks;');
 | 
				
			||||||
      await this.$executeQuery(this.getCreateBlocksTableQuery(), await this.$checkIfTableExists('blocks'));
 | 
					      await this.$executeQuery(this.getCreateBlocksTableQuery(), await this.$checkIfTableExists('blocks'));
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(4);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (databaseSchemaVersion < 5 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 5 && isBitcoin === true) {
 | 
				
			||||||
      this.uniqueLog(logger.notice, this.blocksTruncatedMessage);
 | 
					      this.uniqueLog(logger.notice, this.blocksTruncatedMessage);
 | 
				
			||||||
      await this.$executeQuery('TRUNCATE blocks;'); // Need to re-index
 | 
					      await this.$executeQuery('TRUNCATE blocks;'); // Need to re-index
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE blocks ADD `reward` double unsigned NOT NULL DEFAULT "0"');
 | 
					      await this.$executeQuery('ALTER TABLE blocks ADD `reward` double unsigned NOT NULL DEFAULT "0"');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(5);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 6 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 6 && isBitcoin === true) {
 | 
				
			||||||
@ -141,11 +145,13 @@ class DatabaseMigration {
 | 
				
			|||||||
      await this.$executeQuery('ALTER TABLE blocks ADD `nonce` bigint 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 `merkle_root` varchar(65) NOT NULL DEFAULT ""');
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE blocks ADD `previous_block_hash` varchar(65) NULL');
 | 
					      await this.$executeQuery('ALTER TABLE blocks ADD `previous_block_hash` varchar(65) NULL');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(6);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 7 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 7 && isBitcoin === true) {
 | 
				
			||||||
      await this.$executeQuery('DROP table IF EXISTS hashrates;');
 | 
					      await this.$executeQuery('DROP table IF EXISTS hashrates;');
 | 
				
			||||||
      await this.$executeQuery(this.getCreateDailyStatsTableQuery(), await this.$checkIfTableExists('hashrates'));
 | 
					      await this.$executeQuery(this.getCreateDailyStatsTableQuery(), await this.$checkIfTableExists('hashrates'));
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(7);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 8 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 8 && isBitcoin === true) {
 | 
				
			||||||
@ -155,6 +161,7 @@ class DatabaseMigration {
 | 
				
			|||||||
      await this.$executeQuery('ALTER TABLE `hashrates` ADD `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST');
 | 
					      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 `share` float NOT NULL DEFAULT "0"');
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `hashrates` ADD `type` enum("daily", "weekly") DEFAULT "daily"');
 | 
					      await this.$executeQuery('ALTER TABLE `hashrates` ADD `type` enum("daily", "weekly") DEFAULT "daily"');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(8);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 9 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 9 && isBitcoin === true) {
 | 
				
			||||||
@ -162,10 +169,12 @@ class DatabaseMigration {
 | 
				
			|||||||
      await this.$executeQuery('TRUNCATE hashrates;'); // Need to re-index
 | 
					      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 `state` CHANGE `name` `name` varchar(100)');
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `hashrates` ADD UNIQUE `hashrate_timestamp_pool_id` (`hashrate_timestamp`, `pool_id`)');
 | 
					      await this.$executeQuery('ALTER TABLE `hashrates` ADD UNIQUE `hashrate_timestamp_pool_id` (`hashrate_timestamp`, `pool_id`)');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(9);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 10 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 10 && isBitcoin === true) {
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `blocks` ADD INDEX `blockTimestamp` (`blockTimestamp`)');
 | 
					      await this.$executeQuery('ALTER TABLE `blocks` ADD INDEX `blockTimestamp` (`blockTimestamp`)');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(10);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 11 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 11 && isBitcoin === true) {
 | 
				
			||||||
@ -178,11 +187,13 @@ class DatabaseMigration {
 | 
				
			|||||||
      await this.$executeQuery('ALTER TABLE blocks MODIFY `reward` BIGINT 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 `median_fee` INT UNSIGNED NOT NULL DEFAULT "0"');
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE blocks MODIFY `fees` INT UNSIGNED NOT NULL DEFAULT "0"');
 | 
					      await this.$executeQuery('ALTER TABLE blocks MODIFY `fees` INT UNSIGNED NOT NULL DEFAULT "0"');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(11);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 12 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 12 && isBitcoin === true) {
 | 
				
			||||||
      // No need to re-index because the new data type can contain larger values
 | 
					      // No need to re-index because the new data type can contain larger values
 | 
				
			||||||
      await this.$executeQuery('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"');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(12);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 13 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 13 && isBitcoin === true) {
 | 
				
			||||||
@ -190,6 +201,7 @@ class DatabaseMigration {
 | 
				
			|||||||
      await this.$executeQuery('ALTER TABLE blocks MODIFY `median_fee` BIGINT 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` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE blocks MODIFY `avg_fee_rate` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
 | 
					      await this.$executeQuery('ALTER TABLE blocks MODIFY `avg_fee_rate` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(13);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 14 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 14 && isBitcoin === true) {
 | 
				
			||||||
@ -197,37 +209,45 @@ class DatabaseMigration {
 | 
				
			|||||||
      await this.$executeQuery('TRUNCATE hashrates;'); // Need to re-index
 | 
					      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` DROP FOREIGN KEY `hashrates_ibfk_1`');
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `hashrates` MODIFY `pool_id` SMALLINT UNSIGNED NOT NULL DEFAULT "0"');
 | 
					      await this.$executeQuery('ALTER TABLE `hashrates` MODIFY `pool_id` SMALLINT UNSIGNED NOT NULL DEFAULT "0"');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(14);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 16 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 16 && isBitcoin === true) {
 | 
				
			||||||
      this.uniqueLog(logger.notice, this.hashratesTruncatedMessage);
 | 
					      this.uniqueLog(logger.notice, this.hashratesTruncatedMessage);
 | 
				
			||||||
      await this.$executeQuery('TRUNCATE hashrates;'); // Need to re-index because we changed timestamps
 | 
					      await this.$executeQuery('TRUNCATE hashrates;'); // Need to re-index because we changed timestamps
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(16);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 17 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 17 && isBitcoin === true) {
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `pools` ADD `slug` CHAR(50) NULL');
 | 
					      await this.$executeQuery('ALTER TABLE `pools` ADD `slug` CHAR(50) NULL');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(17);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 18 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 18 && isBitcoin === true) {
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `blocks` ADD INDEX `hash` (`hash`);');
 | 
					      await this.$executeQuery('ALTER TABLE `blocks` ADD INDEX `hash` (`hash`);');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(18);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 19) {
 | 
					    if (databaseSchemaVersion < 19) {
 | 
				
			||||||
      await this.$executeQuery(this.getCreateRatesTableQuery(), await this.$checkIfTableExists('rates'));
 | 
					      await this.$executeQuery(this.getCreateRatesTableQuery(), await this.$checkIfTableExists('rates'));
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(19);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 20 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 20 && isBitcoin === true) {
 | 
				
			||||||
      await this.$executeQuery(this.getCreateBlocksSummariesTableQuery(), await this.$checkIfTableExists('blocks_summaries'));
 | 
					      await this.$executeQuery(this.getCreateBlocksSummariesTableQuery(), await this.$checkIfTableExists('blocks_summaries'));
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(20);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 21) {
 | 
					    if (databaseSchemaVersion < 21) {
 | 
				
			||||||
      await this.$executeQuery('DROP TABLE IF EXISTS `rates`');
 | 
					      await this.$executeQuery('DROP TABLE IF EXISTS `rates`');
 | 
				
			||||||
      await this.$executeQuery(this.getCreatePricesTableQuery(), await this.$checkIfTableExists('prices'));
 | 
					      await this.$executeQuery(this.getCreatePricesTableQuery(), await this.$checkIfTableExists('prices'));
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(21);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 22 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 22 && isBitcoin === true) {
 | 
				
			||||||
      await this.$executeQuery('DROP TABLE IF EXISTS `difficulty_adjustments`');
 | 
					      await this.$executeQuery('DROP TABLE IF EXISTS `difficulty_adjustments`');
 | 
				
			||||||
      await this.$executeQuery(this.getCreateDifficultyAdjustmentsTableQuery(), await this.$checkIfTableExists('difficulty_adjustments'));
 | 
					      await this.$executeQuery(this.getCreateDifficultyAdjustmentsTableQuery(), await this.$checkIfTableExists('difficulty_adjustments'));
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(22);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 23) {
 | 
					    if (databaseSchemaVersion < 23) {
 | 
				
			||||||
@ -240,11 +260,13 @@ class DatabaseMigration {
 | 
				
			|||||||
      await this.$executeQuery('ALTER TABLE `prices` ADD `CHF` 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 `AUD` float DEFAULT "0"');
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `prices` ADD `JPY` float DEFAULT "0"');
 | 
					      await this.$executeQuery('ALTER TABLE `prices` ADD `JPY` float DEFAULT "0"');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(23);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 24 && isBitcoin == true) {
 | 
					    if (databaseSchemaVersion < 24 && isBitcoin == true) {
 | 
				
			||||||
      await this.$executeQuery('DROP TABLE IF EXISTS `blocks_audits`');
 | 
					      await this.$executeQuery('DROP TABLE IF EXISTS `blocks_audits`');
 | 
				
			||||||
      await this.$executeQuery(this.getCreateBlocksAuditsTableQuery(), await this.$checkIfTableExists('blocks_audits'));
 | 
					      await this.$executeQuery(this.getCreateBlocksAuditsTableQuery(), await this.$checkIfTableExists('blocks_audits'));
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(24);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 25 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 25 && isBitcoin === true) {
 | 
				
			||||||
@ -252,6 +274,7 @@ class DatabaseMigration {
 | 
				
			|||||||
      await this.$executeQuery(this.getCreateNodesQuery(), await this.$checkIfTableExists('nodes'));
 | 
					      await this.$executeQuery(this.getCreateNodesQuery(), await this.$checkIfTableExists('nodes'));
 | 
				
			||||||
      await this.$executeQuery(this.getCreateChannelsQuery(), await this.$checkIfTableExists('channels'));
 | 
					      await this.$executeQuery(this.getCreateChannelsQuery(), await this.$checkIfTableExists('channels'));
 | 
				
			||||||
      await this.$executeQuery(this.getCreateNodesStatsQuery(), await this.$checkIfTableExists('node_stats'));
 | 
					      await this.$executeQuery(this.getCreateNodesStatsQuery(), await this.$checkIfTableExists('node_stats'));
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(25);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 26 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 26 && isBitcoin === true) {
 | 
				
			||||||
@ -262,6 +285,7 @@ class DatabaseMigration {
 | 
				
			|||||||
      await this.$executeQuery('ALTER TABLE `lightning_stats` ADD tor_nodes int(11) NOT NULL DEFAULT "0"');
 | 
					      await this.$executeQuery('ALTER TABLE `lightning_stats` ADD tor_nodes int(11) NOT NULL DEFAULT "0"');
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `lightning_stats` ADD clearnet_nodes int(11) NOT NULL DEFAULT "0"');
 | 
					      await this.$executeQuery('ALTER TABLE `lightning_stats` ADD clearnet_nodes int(11) NOT NULL DEFAULT "0"');
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `lightning_stats` ADD unannounced_nodes int(11) NOT NULL DEFAULT "0"');
 | 
					      await this.$executeQuery('ALTER TABLE `lightning_stats` ADD unannounced_nodes int(11) NOT NULL DEFAULT "0"');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(26);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 27 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 27 && isBitcoin === true) {
 | 
				
			||||||
@ -271,6 +295,7 @@ class DatabaseMigration {
 | 
				
			|||||||
      await this.$executeQuery('ALTER TABLE `lightning_stats` ADD med_capacity bigint(20) unsigned NOT NULL DEFAULT "0"');
 | 
					      await this.$executeQuery('ALTER TABLE `lightning_stats` ADD med_capacity bigint(20) unsigned NOT NULL DEFAULT "0"');
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `lightning_stats` ADD med_fee_rate int(11) unsigned NOT NULL DEFAULT "0"');
 | 
					      await this.$executeQuery('ALTER TABLE `lightning_stats` ADD med_fee_rate int(11) unsigned NOT NULL DEFAULT "0"');
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `lightning_stats` ADD med_base_fee_mtokens bigint(20) unsigned NOT NULL DEFAULT "0"');
 | 
					      await this.$executeQuery('ALTER TABLE `lightning_stats` ADD med_base_fee_mtokens bigint(20) unsigned NOT NULL DEFAULT "0"');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(27);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    if (databaseSchemaVersion < 28 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 28 && isBitcoin === true) {
 | 
				
			||||||
@ -280,6 +305,7 @@ class DatabaseMigration {
 | 
				
			|||||||
      await this.$executeQuery(`TRUNCATE lightning_stats`);
 | 
					      await this.$executeQuery(`TRUNCATE lightning_stats`);
 | 
				
			||||||
      await this.$executeQuery(`TRUNCATE node_stats`);
 | 
					      await this.$executeQuery(`TRUNCATE node_stats`);
 | 
				
			||||||
      await this.$executeQuery(`ALTER TABLE lightning_stats MODIFY added DATE`);
 | 
					      await this.$executeQuery(`ALTER TABLE lightning_stats MODIFY added DATE`);
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(28);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 29 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 29 && isBitcoin === true) {
 | 
				
			||||||
@ -291,41 +317,50 @@ class DatabaseMigration {
 | 
				
			|||||||
      await this.$executeQuery('ALTER TABLE `nodes` ADD subdivision_id int(11) unsigned NULL DEFAULT NULL');
 | 
					      await this.$executeQuery('ALTER TABLE `nodes` ADD subdivision_id int(11) unsigned NULL DEFAULT NULL');
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `nodes` ADD longitude double NULL DEFAULT NULL');
 | 
					      await this.$executeQuery('ALTER TABLE `nodes` ADD longitude double NULL DEFAULT NULL');
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `nodes` ADD latitude double NULL DEFAULT NULL');
 | 
					      await this.$executeQuery('ALTER TABLE `nodes` ADD latitude double NULL DEFAULT NULL');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(29);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 30 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 30 && isBitcoin === true) {
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `geo_names` CHANGE `type` `type` enum("city","country","division","continent","as_organization") NOT NULL');
 | 
					      await this.$executeQuery('ALTER TABLE `geo_names` CHANGE `type` `type` enum("city","country","division","continent","as_organization") NOT NULL');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(30);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 31 && isBitcoin == true) { // Link blocks to prices
 | 
					    if (databaseSchemaVersion < 31 && isBitcoin == true) { // Link blocks to prices
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `prices` ADD `id` int NULL AUTO_INCREMENT UNIQUE');
 | 
					      await this.$executeQuery('ALTER TABLE `prices` ADD `id` int NULL AUTO_INCREMENT UNIQUE');
 | 
				
			||||||
      await this.$executeQuery('DROP TABLE IF EXISTS `blocks_prices`');
 | 
					      await this.$executeQuery('DROP TABLE IF EXISTS `blocks_prices`');
 | 
				
			||||||
      await this.$executeQuery(this.getCreateBlocksPricesTableQuery(), await this.$checkIfTableExists('blocks_prices'));
 | 
					      await this.$executeQuery(this.getCreateBlocksPricesTableQuery(), await this.$checkIfTableExists('blocks_prices'));
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(31);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 32 && isBitcoin == true) {
 | 
					    if (databaseSchemaVersion < 32 && isBitcoin == true) {
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `blocks_summaries` ADD `template` JSON DEFAULT "[]"');
 | 
					      await this.$executeQuery('ALTER TABLE `blocks_summaries` ADD `template` JSON DEFAULT "[]"');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(32);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 33 && isBitcoin == true) {
 | 
					    if (databaseSchemaVersion < 33 && isBitcoin == true) {
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `geo_names` CHANGE `type` `type` enum("city","country","division","continent","as_organization", "country_iso_code") NOT NULL');
 | 
					      await this.$executeQuery('ALTER TABLE `geo_names` CHANGE `type` `type` enum("city","country","division","continent","as_organization", "country_iso_code") NOT NULL');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(33);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 34 && isBitcoin == true) {
 | 
					    if (databaseSchemaVersion < 34 && isBitcoin == true) {
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `lightning_stats` ADD clearnet_tor_nodes int(11) NOT NULL DEFAULT "0"');
 | 
					      await this.$executeQuery('ALTER TABLE `lightning_stats` ADD clearnet_tor_nodes int(11) NOT NULL DEFAULT "0"');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(34);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 35 && isBitcoin == true) {
 | 
					    if (databaseSchemaVersion < 35 && isBitcoin == true) {
 | 
				
			||||||
      await this.$executeQuery('DELETE from `lightning_stats` WHERE added > "2021-09-19"');
 | 
					      await this.$executeQuery('DELETE from `lightning_stats` WHERE added > "2021-09-19"');
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `lightning_stats` ADD CONSTRAINT added_unique UNIQUE (added);');
 | 
					      await this.$executeQuery('ALTER TABLE `lightning_stats` ADD CONSTRAINT added_unique UNIQUE (added);');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(35);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 36 && isBitcoin == true) {
 | 
					    if (databaseSchemaVersion < 36 && isBitcoin == true) {
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `nodes` ADD status TINYINT NOT NULL DEFAULT "1"');
 | 
					      await this.$executeQuery('ALTER TABLE `nodes` ADD status TINYINT NOT NULL DEFAULT "1"');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(36);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 37 && isBitcoin == true) {
 | 
					    if (databaseSchemaVersion < 37 && isBitcoin == true) {
 | 
				
			||||||
      await this.$executeQuery(this.getCreateLNNodesSocketsTableQuery(), await this.$checkIfTableExists('nodes_sockets'));
 | 
					      await this.$executeQuery(this.getCreateLNNodesSocketsTableQuery(), await this.$checkIfTableExists('nodes_sockets'));
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(37);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 38 && isBitcoin == true) {
 | 
					    if (databaseSchemaVersion < 38 && isBitcoin == true) {
 | 
				
			||||||
@ -336,48 +371,76 @@ class DatabaseMigration {
 | 
				
			|||||||
      await this.$executeQuery(`TRUNCATE node_stats`);
 | 
					      await this.$executeQuery(`TRUNCATE node_stats`);
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `lightning_stats` CHANGE `added` `added` timestamp NULL');
 | 
					      await this.$executeQuery('ALTER TABLE `lightning_stats` CHANGE `added` `added` timestamp NULL');
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `node_stats` CHANGE `added` `added` timestamp NULL');
 | 
					      await this.$executeQuery('ALTER TABLE `node_stats` CHANGE `added` `added` timestamp NULL');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(38);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 39 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 39 && isBitcoin === true) {
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `nodes` ADD alias_search TEXT NULL DEFAULT NULL AFTER `alias`');
 | 
					      await this.$executeQuery('ALTER TABLE `nodes` ADD alias_search TEXT NULL DEFAULT NULL AFTER `alias`');
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE nodes ADD FULLTEXT(alias_search)');
 | 
					      await this.$executeQuery('ALTER TABLE nodes ADD FULLTEXT(alias_search)');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(39);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 40 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 40 && isBitcoin === true) {
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `nodes` ADD capacity bigint(20) unsigned DEFAULT NULL');
 | 
					      await this.$executeQuery('ALTER TABLE `nodes` ADD capacity bigint(20) unsigned DEFAULT NULL');
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `nodes` ADD channels int(11) unsigned DEFAULT NULL');
 | 
					      await this.$executeQuery('ALTER TABLE `nodes` ADD channels int(11) unsigned DEFAULT NULL');
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `nodes` ADD INDEX `capacity` (`capacity`);');
 | 
					      await this.$executeQuery('ALTER TABLE `nodes` ADD INDEX `capacity` (`capacity`);');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(40);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 41 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 41 && isBitcoin === true) {
 | 
				
			||||||
      await this.$executeQuery('UPDATE channels SET closing_reason = NULL WHERE closing_reason = 1');
 | 
					      await this.$executeQuery('UPDATE channels SET closing_reason = NULL WHERE closing_reason = 1');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(41);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 42 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 42 && isBitcoin === true) {
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `channels` ADD closing_resolved tinyint(1) DEFAULT 0');
 | 
					      await this.$executeQuery('ALTER TABLE `channels` ADD closing_resolved tinyint(1) DEFAULT 0');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(42);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 43 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 43 && isBitcoin === true) {
 | 
				
			||||||
      await this.$executeQuery(this.getCreateLNNodeRecordsTableQuery(), await this.$checkIfTableExists('nodes_records'));
 | 
					      await this.$executeQuery(this.getCreateLNNodeRecordsTableQuery(), await this.$checkIfTableExists('nodes_records'));
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(43);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 44 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 44 && isBitcoin === true) {
 | 
				
			||||||
      await this.$executeQuery('TRUNCATE TABLE `blocks_audits`');
 | 
					 | 
				
			||||||
      await this.$executeQuery('UPDATE blocks_summaries SET template = NULL');
 | 
					      await this.$executeQuery('UPDATE blocks_summaries SET template = NULL');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(44);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 45 && isBitcoin === true) {
 | 
					    if (databaseSchemaVersion < 45 && isBitcoin === true) {
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `blocks_audits` ADD fresh_txs JSON DEFAULT "[]"');
 | 
					      await this.$executeQuery('ALTER TABLE `blocks_audits` ADD fresh_txs JSON DEFAULT "[]"');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(45);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 46) {
 | 
					    if (databaseSchemaVersion < 46) {
 | 
				
			||||||
      await this.$executeQuery(`ALTER TABLE blocks MODIFY blockTimestamp timestamp NOT NULL DEFAULT 0`);
 | 
					      await this.$executeQuery(`ALTER TABLE blocks MODIFY blockTimestamp timestamp NOT NULL DEFAULT 0`);
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(46);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (databaseSchemaVersion < 47) {
 | 
					    if (databaseSchemaVersion < 47) {
 | 
				
			||||||
      await this.$executeQuery('ALTER TABLE `blocks` ADD cpfp_indexed tinyint(1) DEFAULT 0');
 | 
					      await this.$executeQuery('ALTER TABLE `blocks` ADD cpfp_indexed tinyint(1) DEFAULT 0');
 | 
				
			||||||
      await this.$executeQuery(this.getCreateCPFPTableQuery(), await this.$checkIfTableExists('cpfp_clusters'));
 | 
					      await this.$executeQuery(this.getCreateCPFPTableQuery(), await this.$checkIfTableExists('cpfp_clusters'));
 | 
				
			||||||
      await this.$executeQuery(this.getCreateTransactionsTableQuery(), await this.$checkIfTableExists('transactions'));
 | 
					      await this.$executeQuery(this.getCreateTransactionsTableQuery(), await this.$checkIfTableExists('transactions'));
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(47);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (databaseSchemaVersion < 48 && isBitcoin === true) {
 | 
				
			||||||
 | 
					      await this.$executeQuery('ALTER TABLE `channels` ADD source_checked tinyint(1) DEFAULT 0');
 | 
				
			||||||
 | 
					      await this.$executeQuery('ALTER TABLE `channels` ADD closing_fee bigint(20) unsigned DEFAULT 0');
 | 
				
			||||||
 | 
					      await this.$executeQuery('ALTER TABLE `channels` ADD node1_funding_balance bigint(20) unsigned DEFAULT 0');
 | 
				
			||||||
 | 
					      await this.$executeQuery('ALTER TABLE `channels` ADD node2_funding_balance bigint(20) unsigned DEFAULT 0');
 | 
				
			||||||
 | 
					      await this.$executeQuery('ALTER TABLE `channels` ADD node1_closing_balance bigint(20) unsigned DEFAULT 0');
 | 
				
			||||||
 | 
					      await this.$executeQuery('ALTER TABLE `channels` ADD node2_closing_balance bigint(20) unsigned DEFAULT 0');
 | 
				
			||||||
 | 
					      await this.$executeQuery('ALTER TABLE `channels` ADD funding_ratio float unsigned DEFAULT NULL');
 | 
				
			||||||
 | 
					      await this.$executeQuery('ALTER TABLE `channels` ADD closed_by varchar(66) DEFAULT NULL');
 | 
				
			||||||
 | 
					      await this.$executeQuery('ALTER TABLE `channels` ADD single_funded tinyint(1) DEFAULT 0');
 | 
				
			||||||
 | 
					      await this.$executeQuery('ALTER TABLE `channels` ADD outputs JSON DEFAULT "[]"');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(48);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (databaseSchemaVersion < 49 && isBitcoin === true) {
 | 
				
			||||||
 | 
					      await this.$executeQuery('TRUNCATE TABLE `blocks_audits`');
 | 
				
			||||||
 | 
					      await this.updateToSchemaVersion(49);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -516,6 +579,10 @@ class DatabaseMigration {
 | 
				
			|||||||
    return `UPDATE state SET number = ${DatabaseMigration.currentVersion} WHERE name = 'schema_version';`;
 | 
					    return `UPDATE state SET number = ${DatabaseMigration.currentVersion} WHERE name = 'schema_version';`;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async updateToSchemaVersion(version): Promise<void> {
 | 
				
			||||||
 | 
					    await this.$executeQuery(`UPDATE state SET number = ${version} WHERE name = 'schema_version';`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Print current database version
 | 
					   * Print current database version
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
 | 
				
			|||||||
@ -128,6 +128,21 @@ class ChannelsApi {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public async $getChannelsWithoutSourceChecked(): Promise<any[]> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const query = `
 | 
				
			||||||
 | 
					        SELECT channels.*
 | 
				
			||||||
 | 
					        FROM channels
 | 
				
			||||||
 | 
					        WHERE channels.source_checked != 1
 | 
				
			||||||
 | 
					      `;
 | 
				
			||||||
 | 
					      const [rows]: any = await DB.query(query);
 | 
				
			||||||
 | 
					      return rows;
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      logger.err('$getUnresolvedClosedChannels error: ' + (e instanceof Error ? e.message : e));
 | 
				
			||||||
 | 
					      throw e;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public async $getChannelsWithoutCreatedDate(): Promise<any[]> {
 | 
					  public async $getChannelsWithoutCreatedDate(): Promise<any[]> {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const query = `SELECT * FROM channels WHERE created IS NULL`;
 | 
					      const query = `SELECT * FROM channels WHERE created IS NULL`;
 | 
				
			||||||
@ -257,6 +272,108 @@ class ChannelsApi {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public async $getChannelByClosingId(transactionId: string): Promise<any> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const query = `
 | 
				
			||||||
 | 
					        SELECT
 | 
				
			||||||
 | 
					          channels.*
 | 
				
			||||||
 | 
					        FROM channels
 | 
				
			||||||
 | 
					        WHERE channels.closing_transaction_id = ?
 | 
				
			||||||
 | 
					      `;
 | 
				
			||||||
 | 
					      const [rows]: any = await DB.query(query, [transactionId]);
 | 
				
			||||||
 | 
					      if (rows.length > 0) {
 | 
				
			||||||
 | 
					        rows[0].outputs = JSON.parse(rows[0].outputs);
 | 
				
			||||||
 | 
					        return rows[0];
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      logger.err('$getChannelByClosingId error: ' + (e instanceof Error ? e.message : e));
 | 
				
			||||||
 | 
					      // don't throw - this data isn't essential
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public async $getChannelsByOpeningId(transactionId: string): Promise<any> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const query = `
 | 
				
			||||||
 | 
					        SELECT
 | 
				
			||||||
 | 
					          channels.*
 | 
				
			||||||
 | 
					        FROM channels
 | 
				
			||||||
 | 
					        WHERE channels.transaction_id = ?
 | 
				
			||||||
 | 
					      `;
 | 
				
			||||||
 | 
					      const [rows]: any = await DB.query(query, [transactionId]);
 | 
				
			||||||
 | 
					      if (rows.length > 0) {
 | 
				
			||||||
 | 
					        return rows.map(row => {
 | 
				
			||||||
 | 
					          row.outputs = JSON.parse(row.outputs);
 | 
				
			||||||
 | 
					          return row;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      logger.err('$getChannelsByOpeningId error: ' + (e instanceof Error ? e.message : e));
 | 
				
			||||||
 | 
					      // don't throw - this data isn't essential
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public async $updateClosingInfo(channelInfo: { id: string, node1_closing_balance: number, node2_closing_balance: number, closed_by: string | null, closing_fee: number, outputs: ILightningApi.ForensicOutput[]}): Promise<void> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const query = `
 | 
				
			||||||
 | 
					        UPDATE channels SET
 | 
				
			||||||
 | 
					          node1_closing_balance = ?,
 | 
				
			||||||
 | 
					          node2_closing_balance = ?,
 | 
				
			||||||
 | 
					          closed_by = ?,
 | 
				
			||||||
 | 
					          closing_fee = ?,
 | 
				
			||||||
 | 
					          outputs = ?
 | 
				
			||||||
 | 
					        WHERE channels.id = ?
 | 
				
			||||||
 | 
					      `;
 | 
				
			||||||
 | 
					      await DB.query<ResultSetHeader>(query, [
 | 
				
			||||||
 | 
					        channelInfo.node1_closing_balance || 0,
 | 
				
			||||||
 | 
					        channelInfo.node2_closing_balance || 0,
 | 
				
			||||||
 | 
					        channelInfo.closed_by,
 | 
				
			||||||
 | 
					        channelInfo.closing_fee || 0,
 | 
				
			||||||
 | 
					        JSON.stringify(channelInfo.outputs),
 | 
				
			||||||
 | 
					        channelInfo.id,
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      logger.err('$updateClosingInfo error: ' + (e instanceof Error ? e.message : e));
 | 
				
			||||||
 | 
					      // don't throw - this data isn't essential
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public async $updateOpeningInfo(channelInfo: { id: string, node1_funding_balance: number, node2_funding_balance: number, funding_ratio: number, single_funded: boolean | void }): Promise<void> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const query = `
 | 
				
			||||||
 | 
					        UPDATE channels SET
 | 
				
			||||||
 | 
					          node1_funding_balance = ?,
 | 
				
			||||||
 | 
					          node2_funding_balance = ?,
 | 
				
			||||||
 | 
					          funding_ratio = ?,
 | 
				
			||||||
 | 
					          single_funded = ?
 | 
				
			||||||
 | 
					        WHERE channels.id = ?
 | 
				
			||||||
 | 
					      `;
 | 
				
			||||||
 | 
					      await DB.query<ResultSetHeader>(query, [
 | 
				
			||||||
 | 
					        channelInfo.node1_funding_balance || 0,
 | 
				
			||||||
 | 
					        channelInfo.node2_funding_balance || 0,
 | 
				
			||||||
 | 
					        channelInfo.funding_ratio,
 | 
				
			||||||
 | 
					        channelInfo.single_funded ? 1 : 0,
 | 
				
			||||||
 | 
					        channelInfo.id,
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      logger.err('$updateOpeningInfo error: ' + (e instanceof Error ? e.message : e));
 | 
				
			||||||
 | 
					      // don't throw - this data isn't essential
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public async $markChannelSourceChecked(id: string): Promise<void> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const query = `
 | 
				
			||||||
 | 
					        UPDATE channels
 | 
				
			||||||
 | 
					        SET source_checked = 1
 | 
				
			||||||
 | 
					        WHERE id = ?
 | 
				
			||||||
 | 
					      `;
 | 
				
			||||||
 | 
					      await DB.query<ResultSetHeader>(query, [id]);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      logger.err('$markChannelSourceChecked error: ' + (e instanceof Error ? e.message : e));
 | 
				
			||||||
 | 
					      // don't throw - this data isn't essential
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public async $getChannelsForNode(public_key: string, index: number, length: number, status: string): Promise<any[]> {
 | 
					  public async $getChannelsForNode(public_key: string, index: number, length: number, status: string): Promise<any[]> {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      let channelStatusFilter;
 | 
					      let channelStatusFilter;
 | 
				
			||||||
@ -385,11 +502,15 @@ class ChannelsApi {
 | 
				
			|||||||
      'transaction_id': channel.transaction_id,
 | 
					      'transaction_id': channel.transaction_id,
 | 
				
			||||||
      'transaction_vout': channel.transaction_vout,
 | 
					      'transaction_vout': channel.transaction_vout,
 | 
				
			||||||
      'closing_transaction_id': channel.closing_transaction_id,
 | 
					      'closing_transaction_id': channel.closing_transaction_id,
 | 
				
			||||||
 | 
					      'closing_fee': channel.closing_fee,
 | 
				
			||||||
      'closing_reason': channel.closing_reason,
 | 
					      'closing_reason': channel.closing_reason,
 | 
				
			||||||
      'closing_date': channel.closing_date,
 | 
					      'closing_date': channel.closing_date,
 | 
				
			||||||
      'updated_at': channel.updated_at,
 | 
					      'updated_at': channel.updated_at,
 | 
				
			||||||
      'created': channel.created,
 | 
					      'created': channel.created,
 | 
				
			||||||
      'status': channel.status,
 | 
					      'status': channel.status,
 | 
				
			||||||
 | 
					      'funding_ratio': channel.funding_ratio,
 | 
				
			||||||
 | 
					      'closed_by': channel.closed_by,
 | 
				
			||||||
 | 
					      'single_funded': !!channel.single_funded,
 | 
				
			||||||
      'node_left': {
 | 
					      'node_left': {
 | 
				
			||||||
        'alias': channel.alias_left,
 | 
					        'alias': channel.alias_left,
 | 
				
			||||||
        'public_key': channel.node1_public_key,
 | 
					        'public_key': channel.node1_public_key,
 | 
				
			||||||
@ -404,6 +525,9 @@ class ChannelsApi {
 | 
				
			|||||||
        'updated_at': channel.node1_updated_at,
 | 
					        'updated_at': channel.node1_updated_at,
 | 
				
			||||||
        'longitude': channel.node1_longitude,
 | 
					        'longitude': channel.node1_longitude,
 | 
				
			||||||
        'latitude': channel.node1_latitude,
 | 
					        'latitude': channel.node1_latitude,
 | 
				
			||||||
 | 
					        'funding_balance': channel.node1_funding_balance,
 | 
				
			||||||
 | 
					        'closing_balance': channel.node1_closing_balance,
 | 
				
			||||||
 | 
					        'initiated_close': channel.closed_by === channel.node1_public_key ? true : undefined,
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      'node_right': {
 | 
					      'node_right': {
 | 
				
			||||||
        'alias': channel.alias_right,
 | 
					        'alias': channel.alias_right,
 | 
				
			||||||
@ -419,6 +543,9 @@ class ChannelsApi {
 | 
				
			|||||||
        'updated_at': channel.node2_updated_at,
 | 
					        'updated_at': channel.node2_updated_at,
 | 
				
			||||||
        'longitude': channel.node2_longitude,
 | 
					        'longitude': channel.node2_longitude,
 | 
				
			||||||
        'latitude': channel.node2_latitude,
 | 
					        'latitude': channel.node2_latitude,
 | 
				
			||||||
 | 
					        'funding_balance': channel.node2_funding_balance,
 | 
				
			||||||
 | 
					        'closing_balance': channel.node2_closing_balance,
 | 
				
			||||||
 | 
					        'initiated_close': channel.closed_by === channel.node2_public_key ? true : undefined,
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -14,8 +14,8 @@ class NodesApi {
 | 
				
			|||||||
        nodes.longitude, nodes.latitude,
 | 
					        nodes.longitude, nodes.latitude,
 | 
				
			||||||
        geo_names_country.names as country, geo_names_iso.names as isoCode
 | 
					        geo_names_country.names as country, geo_names_iso.names as isoCode
 | 
				
			||||||
        FROM nodes
 | 
					        FROM nodes
 | 
				
			||||||
        LEFT JOIN geo_names geo_names_country ON geo_names_country.id = nodes.country_id AND geo_names_country.type = 'country'
 | 
					        JOIN geo_names geo_names_country ON geo_names_country.id = nodes.country_id AND geo_names_country.type = 'country'
 | 
				
			||||||
        LEFT JOIN geo_names geo_names_iso ON geo_names_iso.id = nodes.country_id AND geo_names_iso.type = 'country_iso_code'
 | 
					        JOIN geo_names geo_names_iso ON geo_names_iso.id = nodes.country_id AND geo_names_iso.type = 'country_iso_code'
 | 
				
			||||||
        WHERE status = 1 AND nodes.as_number IS NOT NULL
 | 
					        WHERE status = 1 AND nodes.as_number IS NOT NULL
 | 
				
			||||||
        ORDER BY capacity
 | 
					        ORDER BY capacity
 | 
				
			||||||
      `;
 | 
					      `;
 | 
				
			||||||
 | 
				
			|||||||
@ -83,4 +83,10 @@ export namespace ILightningApi {
 | 
				
			|||||||
    is_required: boolean;
 | 
					    is_required: boolean;
 | 
				
			||||||
    is_known: boolean;
 | 
					    is_known: boolean;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export interface ForensicOutput {
 | 
				
			||||||
 | 
					    node?: 1 | 2;
 | 
				
			||||||
 | 
					    type: number;
 | 
				
			||||||
 | 
					    value: number;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -250,12 +250,12 @@ class WebsocketHandler {
 | 
				
			|||||||
      throw new Error('WebSocket.Server is not set');
 | 
					      throw new Error('WebSocket.Server is not set');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (config.MEMPOOL.ADVANCED_TRANSACTION_SELECTION) {
 | 
					    if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
 | 
				
			||||||
      await mempoolBlocks.makeBlockTemplates(newMempool, 8, null, true);
 | 
					      await mempoolBlocks.makeBlockTemplates(newMempool, 8, null, true);
 | 
				
			||||||
    }
 | 
					    } else {
 | 
				
			||||||
    else {
 | 
					 | 
				
			||||||
      mempoolBlocks.updateMempoolBlocks(newMempool);
 | 
					      mempoolBlocks.updateMempoolBlocks(newMempool);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const mBlocks = mempoolBlocks.getMempoolBlocks();
 | 
					    const mBlocks = mempoolBlocks.getMempoolBlocks();
 | 
				
			||||||
    const mBlockDeltas = mempoolBlocks.getMempoolBlockDeltas();
 | 
					    const mBlockDeltas = mempoolBlocks.getMempoolBlockDeltas();
 | 
				
			||||||
    const mempoolInfo = memPool.getMempoolInfo();
 | 
					    const mempoolInfo = memPool.getMempoolInfo();
 | 
				
			||||||
@ -417,9 +417,8 @@ class WebsocketHandler {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const _memPool = memPool.getMempool();
 | 
					    const _memPool = memPool.getMempool();
 | 
				
			||||||
    let matchRate;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (config.MEMPOOL.ADVANCED_TRANSACTION_SELECTION) {
 | 
					    if (config.MEMPOOL.ADVANCED_GBT_AUDIT) {
 | 
				
			||||||
      await mempoolBlocks.makeBlockTemplates(_memPool, 2);
 | 
					      await mempoolBlocks.makeBlockTemplates(_memPool, 2);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      mempoolBlocks.updateMempoolBlocks(_memPool);
 | 
					      mempoolBlocks.updateMempoolBlocks(_memPool);
 | 
				
			||||||
@ -429,7 +428,7 @@ class WebsocketHandler {
 | 
				
			|||||||
      const projectedBlocks = mempoolBlocks.getMempoolBlocksWithTransactions();
 | 
					      const projectedBlocks = mempoolBlocks.getMempoolBlocksWithTransactions();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const { censored, added, fresh, score } = Audit.auditBlock(transactions, projectedBlocks, _memPool);
 | 
					      const { censored, added, fresh, score } = Audit.auditBlock(transactions, projectedBlocks, _memPool);
 | 
				
			||||||
      matchRate = Math.round(score * 100 * 100) / 100;
 | 
					      const matchRate = Math.round(score * 100 * 100) / 100;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const stripped = projectedBlocks[0]?.transactions ? projectedBlocks[0].transactions.map((tx) => {
 | 
					      const stripped = projectedBlocks[0]?.transactions ? projectedBlocks[0].transactions.map((tx) => {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
@ -468,7 +467,7 @@ class WebsocketHandler {
 | 
				
			|||||||
      delete _memPool[txId];
 | 
					      delete _memPool[txId];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (config.MEMPOOL.ADVANCED_TRANSACTION_SELECTION) {
 | 
					    if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
 | 
				
			||||||
      await mempoolBlocks.makeBlockTemplates(_memPool, 2);
 | 
					      await mempoolBlocks.makeBlockTemplates(_memPool, 2);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      mempoolBlocks.updateMempoolBlocks(_memPool);
 | 
					      mempoolBlocks.updateMempoolBlocks(_memPool);
 | 
				
			||||||
 | 
				
			|||||||
@ -29,7 +29,8 @@ interface IConfig {
 | 
				
			|||||||
    AUTOMATIC_BLOCK_REINDEXING: boolean;
 | 
					    AUTOMATIC_BLOCK_REINDEXING: boolean;
 | 
				
			||||||
    POOLS_JSON_URL: string,
 | 
					    POOLS_JSON_URL: string,
 | 
				
			||||||
    POOLS_JSON_TREE_URL: string,
 | 
					    POOLS_JSON_TREE_URL: string,
 | 
				
			||||||
    ADVANCED_TRANSACTION_SELECTION: boolean;
 | 
					    ADVANCED_GBT_AUDIT: boolean;
 | 
				
			||||||
 | 
					    ADVANCED_GBT_MEMPOOL: boolean;
 | 
				
			||||||
    TRANSACTION_INDEXING: boolean;
 | 
					    TRANSACTION_INDEXING: boolean;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  ESPLORA: {
 | 
					  ESPLORA: {
 | 
				
			||||||
@ -148,7 +149,8 @@ const defaults: IConfig = {
 | 
				
			|||||||
    'AUTOMATIC_BLOCK_REINDEXING': false,
 | 
					    'AUTOMATIC_BLOCK_REINDEXING': false,
 | 
				
			||||||
    'POOLS_JSON_URL': 'https://raw.githubusercontent.com/mempool/mining-pools/master/pools.json',
 | 
					    'POOLS_JSON_URL': 'https://raw.githubusercontent.com/mempool/mining-pools/master/pools.json',
 | 
				
			||||||
    'POOLS_JSON_TREE_URL': 'https://api.github.com/repos/mempool/mining-pools/git/trees/master',
 | 
					    'POOLS_JSON_TREE_URL': 'https://api.github.com/repos/mempool/mining-pools/git/trees/master',
 | 
				
			||||||
    'ADVANCED_TRANSACTION_SELECTION': false,
 | 
					    'ADVANCED_GBT_AUDIT': false,
 | 
				
			||||||
 | 
					    'ADVANCED_GBT_MEMPOOL': false,
 | 
				
			||||||
    'TRANSACTION_INDEXING': false,
 | 
					    'TRANSACTION_INDEXING': false,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  'ESPLORA': {
 | 
					  'ESPLORA': {
 | 
				
			||||||
 | 
				
			|||||||
@ -5,13 +5,16 @@ import bitcoinApi from '../../api/bitcoin/bitcoin-api-factory';
 | 
				
			|||||||
import config from '../../config';
 | 
					import config from '../../config';
 | 
				
			||||||
import { IEsploraApi } from '../../api/bitcoin/esplora-api.interface';
 | 
					import { IEsploraApi } from '../../api/bitcoin/esplora-api.interface';
 | 
				
			||||||
import { Common } from '../../api/common';
 | 
					import { Common } from '../../api/common';
 | 
				
			||||||
 | 
					import { ILightningApi } from '../../api/lightning/lightning-api.interface';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const throttleDelay = 20; //ms
 | 
					const throttleDelay = 20; //ms
 | 
				
			||||||
 | 
					const tempCacheSize = 10000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ForensicsService {
 | 
					class ForensicsService {
 | 
				
			||||||
  loggerTimer = 0;
 | 
					  loggerTimer = 0;
 | 
				
			||||||
  closedChannelsScanBlock = 0;
 | 
					  closedChannelsScanBlock = 0;
 | 
				
			||||||
  txCache: { [txid: string]: IEsploraApi.Transaction } = {};
 | 
					  txCache: { [txid: string]: IEsploraApi.Transaction } = {};
 | 
				
			||||||
 | 
					  tempCached: string[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor() {}
 | 
					  constructor() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -29,6 +32,7 @@ class ForensicsService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      if (config.MEMPOOL.BACKEND === 'esplora') {
 | 
					      if (config.MEMPOOL.BACKEND === 'esplora') {
 | 
				
			||||||
        await this.$runClosedChannelsForensics(false);
 | 
					        await this.$runClosedChannelsForensics(false);
 | 
				
			||||||
 | 
					        await this.$runOpenedChannelsForensics();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
@ -95,17 +99,10 @@ class ForensicsService {
 | 
				
			|||||||
          const lightningScriptReasons: number[] = [];
 | 
					          const lightningScriptReasons: number[] = [];
 | 
				
			||||||
          for (const outspend of outspends) {
 | 
					          for (const outspend of outspends) {
 | 
				
			||||||
            if (outspend.spent && outspend.txid) {
 | 
					            if (outspend.spent && outspend.txid) {
 | 
				
			||||||
              let spendingTx: IEsploraApi.Transaction | undefined = this.txCache[outspend.txid];
 | 
					              let spendingTx = await this.fetchTransaction(outspend.txid);
 | 
				
			||||||
              if (!spendingTx) {
 | 
					              if (!spendingTx) {
 | 
				
			||||||
                try {
 | 
					 | 
				
			||||||
                  spendingTx = await bitcoinApi.$getRawTransaction(outspend.txid);
 | 
					 | 
				
			||||||
                  await Common.sleep$(throttleDelay);
 | 
					 | 
				
			||||||
                  this.txCache[outspend.txid] = spendingTx;
 | 
					 | 
				
			||||||
                } catch (e) {
 | 
					 | 
				
			||||||
                  logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/tx/' + outspend.txid}. Reason ${e instanceof Error ? e.message : e}`);
 | 
					 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
              cached.push(spendingTx.txid);
 | 
					              cached.push(spendingTx.txid);
 | 
				
			||||||
              const lightningScript = this.findLightningScript(spendingTx.vin[outspend.vin || 0]);
 | 
					              const lightningScript = this.findLightningScript(spendingTx.vin[outspend.vin || 0]);
 | 
				
			||||||
              lightningScriptReasons.push(lightningScript);
 | 
					              lightningScriptReasons.push(lightningScript);
 | 
				
			||||||
@ -124,17 +121,10 @@ class ForensicsService {
 | 
				
			|||||||
              We can detect a commitment transaction (force close) by reading Sequence and Locktime
 | 
					              We can detect a commitment transaction (force close) by reading Sequence and Locktime
 | 
				
			||||||
              https://github.com/lightning/bolts/blob/master/03-transactions.md#commitment-transaction
 | 
					              https://github.com/lightning/bolts/blob/master/03-transactions.md#commitment-transaction
 | 
				
			||||||
            */
 | 
					            */
 | 
				
			||||||
            let closingTx: IEsploraApi.Transaction | undefined = this.txCache[channel.closing_transaction_id];
 | 
					            let closingTx = await this.fetchTransaction(channel.closing_transaction_id, true);
 | 
				
			||||||
            if (!closingTx) {
 | 
					            if (!closingTx) {
 | 
				
			||||||
              try {
 | 
					 | 
				
			||||||
                closingTx = await bitcoinApi.$getRawTransaction(channel.closing_transaction_id);
 | 
					 | 
				
			||||||
                await Common.sleep$(throttleDelay);
 | 
					 | 
				
			||||||
                this.txCache[channel.closing_transaction_id] = closingTx;
 | 
					 | 
				
			||||||
              } catch (e) {
 | 
					 | 
				
			||||||
                logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/tx/' + channel.closing_transaction_id}. Reason ${e instanceof Error ? e.message : e}`);
 | 
					 | 
				
			||||||
              continue;
 | 
					              continue;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            cached.push(closingTx.txid);
 | 
					            cached.push(closingTx.txid);
 | 
				
			||||||
            const sequenceHex: string = closingTx.vin[0].sequence.toString(16);
 | 
					            const sequenceHex: string = closingTx.vin[0].sequence.toString(16);
 | 
				
			||||||
            const locktimeHex: string = closingTx.locktime.toString(16);
 | 
					            const locktimeHex: string = closingTx.locktime.toString(16);
 | 
				
			||||||
@ -174,7 +164,7 @@ class ForensicsService {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private findLightningScript(vin: IEsploraApi.Vin): number {
 | 
					  private findLightningScript(vin: IEsploraApi.Vin): number {
 | 
				
			||||||
    const topElement = vin.witness[vin.witness.length - 2];
 | 
					    const topElement = vin.witness?.length > 2 ? vin.witness[vin.witness.length - 2] : null;
 | 
				
			||||||
      if (/^OP_IF OP_PUSHBYTES_33 \w{66} OP_ELSE OP_PUSH(NUM_\d+|BYTES_(1 \w{2}|2 \w{4})) OP_CSV OP_DROP OP_PUSHBYTES_33 \w{66} OP_ENDIF OP_CHECKSIG$/.test(vin.inner_witnessscript_asm)) {
 | 
					      if (/^OP_IF OP_PUSHBYTES_33 \w{66} OP_ELSE OP_PUSH(NUM_\d+|BYTES_(1 \w{2}|2 \w{4})) OP_CSV OP_DROP OP_PUSHBYTES_33 \w{66} OP_ENDIF OP_CHECKSIG$/.test(vin.inner_witnessscript_asm)) {
 | 
				
			||||||
        // https://github.com/lightning/bolts/blob/master/03-transactions.md#commitment-transaction-outputs
 | 
					        // https://github.com/lightning/bolts/blob/master/03-transactions.md#commitment-transaction-outputs
 | 
				
			||||||
        if (topElement === '01') {
 | 
					        if (topElement === '01') {
 | 
				
			||||||
@ -193,7 +183,7 @@ class ForensicsService {
 | 
				
			|||||||
      ) {
 | 
					      ) {
 | 
				
			||||||
        // https://github.com/lightning/bolts/blob/master/03-transactions.md#offered-htlc-outputs
 | 
					        // https://github.com/lightning/bolts/blob/master/03-transactions.md#offered-htlc-outputs
 | 
				
			||||||
        // https://github.com/lightning/bolts/blob/master/03-transactions.md#received-htlc-outputs
 | 
					        // https://github.com/lightning/bolts/blob/master/03-transactions.md#received-htlc-outputs
 | 
				
			||||||
        if (topElement.length === 66) {
 | 
					        if (topElement?.length === 66) {
 | 
				
			||||||
          // top element is a public key
 | 
					          // top element is a public key
 | 
				
			||||||
          // 'Revoked Lightning HTLC'; Penalty force closed
 | 
					          // 'Revoked Lightning HTLC'; Penalty force closed
 | 
				
			||||||
          return 4;
 | 
					          return 4;
 | 
				
			||||||
@ -220,6 +210,249 @@ class ForensicsService {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
      return 1;
 | 
					      return 1;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // If a channel open tx spends funds from a another channel transaction,
 | 
				
			||||||
 | 
					  // we can attribute that output to a specific counterparty
 | 
				
			||||||
 | 
					  private async $runOpenedChannelsForensics(): Promise<void> {
 | 
				
			||||||
 | 
					    const runTimer = Date.now();
 | 
				
			||||||
 | 
					    let progress = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      logger.info(`Started running open channel forensics...`);
 | 
				
			||||||
 | 
					      const channels = await channelsApi.$getChannelsWithoutSourceChecked();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      for (const openChannel of channels) {
 | 
				
			||||||
 | 
					        let openTx = await this.fetchTransaction(openChannel.transaction_id, true);
 | 
				
			||||||
 | 
					        if (!openTx) {
 | 
				
			||||||
 | 
					          continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (const input of openTx.vin) {
 | 
				
			||||||
 | 
					          const closeChannel = await channelsApi.$getChannelByClosingId(input.txid);
 | 
				
			||||||
 | 
					          if (closeChannel) {
 | 
				
			||||||
 | 
					            // this input directly spends a channel close output
 | 
				
			||||||
 | 
					            await this.$attributeChannelBalances(closeChannel, openChannel, input);
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            const prevOpenChannels = await channelsApi.$getChannelsByOpeningId(input.txid);
 | 
				
			||||||
 | 
					            if (prevOpenChannels?.length) {
 | 
				
			||||||
 | 
					              // this input spends a channel open change output
 | 
				
			||||||
 | 
					              for (const prevOpenChannel of prevOpenChannels) {
 | 
				
			||||||
 | 
					                await this.$attributeChannelBalances(prevOpenChannel, openChannel, input, null, null, true);
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              // check if this input spends any swept channel close outputs
 | 
				
			||||||
 | 
					              await this.$attributeSweptChannelCloses(openChannel, input);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // calculate how much of the total input value is attributable to the channel open output
 | 
				
			||||||
 | 
					        openChannel.funding_ratio = openTx.vout[openChannel.transaction_vout].value / ((openTx.vout.reduce((sum, v) => sum + v.value, 0) || 1) + openTx.fee);
 | 
				
			||||||
 | 
					        // save changes to the opening channel, and mark it as checked
 | 
				
			||||||
 | 
					        if (openTx?.vin?.length === 1) {
 | 
				
			||||||
 | 
					          openChannel.single_funded = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (openChannel.node1_funding_balance || openChannel.node2_funding_balance || openChannel.node1_closing_balance || openChannel.node2_closing_balance || openChannel.closed_by) {
 | 
				
			||||||
 | 
					          await channelsApi.$updateOpeningInfo(openChannel);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        await channelsApi.$markChannelSourceChecked(openChannel.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ++progress;
 | 
				
			||||||
 | 
					        const elapsedSeconds = Math.round((new Date().getTime() / 1000) - this.loggerTimer);
 | 
				
			||||||
 | 
					        if (elapsedSeconds > 10) {
 | 
				
			||||||
 | 
					          logger.info(`Updating opened channel forensics ${progress}/${channels?.length}`);
 | 
				
			||||||
 | 
					          this.loggerTimer = new Date().getTime() / 1000;
 | 
				
			||||||
 | 
					          this.truncateTempCache();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (Date.now() - runTimer > (config.LIGHTNING.FORENSICS_INTERVAL * 1000)) {
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      logger.info(`Open channels forensics scan complete.`);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      logger.err('$runOpenedChannelsForensics() error: ' + (e instanceof Error ? e.message : e));
 | 
				
			||||||
 | 
					    } finally {
 | 
				
			||||||
 | 
					      this.clearTempCache();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Check if a channel open tx input spends the result of a swept channel close output
 | 
				
			||||||
 | 
					  private async $attributeSweptChannelCloses(openChannel: ILightningApi.Channel, input: IEsploraApi.Vin): Promise<void> {
 | 
				
			||||||
 | 
					    let sweepTx = await this.fetchTransaction(input.txid, true);
 | 
				
			||||||
 | 
					    if (!sweepTx) {
 | 
				
			||||||
 | 
					      logger.err(`couldn't find input transaction for channel forensics ${openChannel.channel_id} ${input.txid}`);
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const openContribution = sweepTx.vout[input.vout].value;
 | 
				
			||||||
 | 
					    for (const sweepInput of sweepTx.vin) {
 | 
				
			||||||
 | 
					      const lnScriptType = this.findLightningScript(sweepInput);
 | 
				
			||||||
 | 
					      if (lnScriptType > 1) {
 | 
				
			||||||
 | 
					        const closeChannel = await channelsApi.$getChannelByClosingId(sweepInput.txid);
 | 
				
			||||||
 | 
					        if (closeChannel) {
 | 
				
			||||||
 | 
					          const initiator = (lnScriptType === 2 || lnScriptType === 4) ? 'remote' : (lnScriptType === 3 ? 'local' : null);
 | 
				
			||||||
 | 
					          await this.$attributeChannelBalances(closeChannel, openChannel, sweepInput, openContribution, initiator);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async $attributeChannelBalances(
 | 
				
			||||||
 | 
					    prevChannel, openChannel, input: IEsploraApi.Vin, openContribution: number | null = null,
 | 
				
			||||||
 | 
					    initiator: 'remote' | 'local' | null = null, linkedOpenings: boolean = false
 | 
				
			||||||
 | 
					  ): Promise<void> {
 | 
				
			||||||
 | 
					    // figure out which node controls the input/output
 | 
				
			||||||
 | 
					    let openSide;
 | 
				
			||||||
 | 
					    let prevLocal;
 | 
				
			||||||
 | 
					    let prevRemote;
 | 
				
			||||||
 | 
					    let matched = false;
 | 
				
			||||||
 | 
					    let ambiguous = false; // if counterparties are the same in both channels, we can't tell them apart
 | 
				
			||||||
 | 
					    if (openChannel.node1_public_key === prevChannel.node1_public_key) {
 | 
				
			||||||
 | 
					      openSide = 1;
 | 
				
			||||||
 | 
					      prevLocal = 1;
 | 
				
			||||||
 | 
					      prevRemote = 2;
 | 
				
			||||||
 | 
					      matched = true;
 | 
				
			||||||
 | 
					    } else if (openChannel.node1_public_key === prevChannel.node2_public_key) {
 | 
				
			||||||
 | 
					      openSide = 1;
 | 
				
			||||||
 | 
					      prevLocal = 2;
 | 
				
			||||||
 | 
					      prevRemote = 1;
 | 
				
			||||||
 | 
					      matched = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (openChannel.node2_public_key === prevChannel.node1_public_key) {
 | 
				
			||||||
 | 
					      openSide = 2;
 | 
				
			||||||
 | 
					      prevLocal = 1;
 | 
				
			||||||
 | 
					      prevRemote = 2;
 | 
				
			||||||
 | 
					      if (matched) {
 | 
				
			||||||
 | 
					        ambiguous = true;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      matched = true;
 | 
				
			||||||
 | 
					    } else if (openChannel.node2_public_key === prevChannel.node2_public_key) {
 | 
				
			||||||
 | 
					      openSide = 2;
 | 
				
			||||||
 | 
					      prevLocal = 2;
 | 
				
			||||||
 | 
					      prevRemote = 1;
 | 
				
			||||||
 | 
					      if (matched) {
 | 
				
			||||||
 | 
					        ambiguous = true;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      matched = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (matched && !ambiguous) {
 | 
				
			||||||
 | 
					      // fetch closing channel transaction and perform forensics on the outputs
 | 
				
			||||||
 | 
					      let prevChannelTx = await this.fetchTransaction(input.txid, true);
 | 
				
			||||||
 | 
					      let outspends: IEsploraApi.Outspend[] | undefined;
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        outspends = await bitcoinApi.$getOutspends(input.txid);
 | 
				
			||||||
 | 
					        await Common.sleep$(throttleDelay);
 | 
				
			||||||
 | 
					      } catch (e) {
 | 
				
			||||||
 | 
					        logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/tx/' + input.txid + '/outspends'}. Reason ${e instanceof Error ? e.message : e}`);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (!outspends || !prevChannelTx) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (!linkedOpenings) {
 | 
				
			||||||
 | 
					        if (!prevChannel.outputs || !prevChannel.outputs.length) {
 | 
				
			||||||
 | 
					          prevChannel.outputs = prevChannelTx.vout.map(vout => {
 | 
				
			||||||
 | 
					            return {
 | 
				
			||||||
 | 
					              type: 0,
 | 
				
			||||||
 | 
					              value: vout.value,
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (let i = 0; i < outspends?.length; i++) {
 | 
				
			||||||
 | 
					          const outspend = outspends[i];
 | 
				
			||||||
 | 
					          const output = prevChannel.outputs[i];
 | 
				
			||||||
 | 
					          if (outspend.spent && outspend.txid) {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					              const spendingTx = await this.fetchTransaction(outspend.txid, true);
 | 
				
			||||||
 | 
					              if (spendingTx) {
 | 
				
			||||||
 | 
					                output.type = this.findLightningScript(spendingTx.vin[outspend.vin || 0]);
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            } catch (e) {
 | 
				
			||||||
 | 
					              logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/tx/' + outspend.txid}. Reason ${e instanceof Error ? e.message : e}`);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            output.type = 0;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // attribute outputs to each counterparty, and sum up total known balances
 | 
				
			||||||
 | 
					        prevChannel.outputs[input.vout].node = prevLocal;
 | 
				
			||||||
 | 
					        const isPenalty = prevChannel.outputs.filter((out) => out.type === 2 || out.type === 4)?.length > 0;
 | 
				
			||||||
 | 
					        const normalOutput = [1,3].includes(prevChannel.outputs[input.vout].type);
 | 
				
			||||||
 | 
					        const mutualClose = ((prevChannel.status === 2 || prevChannel.status === 'closed') && prevChannel.closing_reason === 1);
 | 
				
			||||||
 | 
					        let localClosingBalance = 0;
 | 
				
			||||||
 | 
					        let remoteClosingBalance = 0;
 | 
				
			||||||
 | 
					        for (const output of prevChannel.outputs) {
 | 
				
			||||||
 | 
					          if (isPenalty) {
 | 
				
			||||||
 | 
					            // penalty close, so local node takes everything
 | 
				
			||||||
 | 
					            localClosingBalance += output.value;
 | 
				
			||||||
 | 
					          } else if (output.node) {
 | 
				
			||||||
 | 
					            // this output determinstically linked to one of the counterparties
 | 
				
			||||||
 | 
					            if (output.node === prevLocal) {
 | 
				
			||||||
 | 
					              localClosingBalance += output.value;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              remoteClosingBalance += output.value;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          } else if (normalOutput && (output.type === 1 || output.type === 3 || (mutualClose && prevChannel.outputs.length === 2))) {
 | 
				
			||||||
 | 
					            // local node had one main output, therefore remote node takes the other
 | 
				
			||||||
 | 
					            remoteClosingBalance += output.value;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        prevChannel[`node${prevLocal}_closing_balance`] = localClosingBalance;
 | 
				
			||||||
 | 
					        prevChannel[`node${prevRemote}_closing_balance`] = remoteClosingBalance;
 | 
				
			||||||
 | 
					        prevChannel.closing_fee = prevChannelTx.fee;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (initiator && !linkedOpenings) {
 | 
				
			||||||
 | 
					          const initiatorSide = initiator === 'remote' ? prevRemote : prevLocal;
 | 
				
			||||||
 | 
					          prevChannel.closed_by = prevChannel[`node${initiatorSide}_public_key`];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					        // save changes to the closing channel
 | 
				
			||||||
 | 
					        await channelsApi.$updateClosingInfo(prevChannel);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        if (prevChannelTx.vin.length <= 1) {
 | 
				
			||||||
 | 
					          prevChannel[`node${prevLocal}_funding_balance`] = prevChannel.capacity;
 | 
				
			||||||
 | 
					          prevChannel.single_funded = true;
 | 
				
			||||||
 | 
					          prevChannel.funding_ratio = 1;
 | 
				
			||||||
 | 
					          // save changes to the closing channel
 | 
				
			||||||
 | 
					          await channelsApi.$updateOpeningInfo(prevChannel);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      openChannel[`node${openSide}_funding_balance`] = openChannel[`node${openSide}_funding_balance`] + (openContribution || prevChannelTx?.vout[input.vout]?.value || 0);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async fetchTransaction(txid: string, temp: boolean = false): Promise<IEsploraApi.Transaction | null> {
 | 
				
			||||||
 | 
					    let tx = this.txCache[txid];
 | 
				
			||||||
 | 
					    if (!tx) {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        tx = await bitcoinApi.$getRawTransaction(txid);
 | 
				
			||||||
 | 
					        this.txCache[txid] = tx;
 | 
				
			||||||
 | 
					        if (temp) {
 | 
				
			||||||
 | 
					          this.tempCached.push(txid);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        await Common.sleep$(throttleDelay);
 | 
				
			||||||
 | 
					      } catch (e) {
 | 
				
			||||||
 | 
					        logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/tx/' + txid + '/outspends'}. Reason ${e instanceof Error ? e.message : e}`);
 | 
				
			||||||
 | 
					        return null;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return tx;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  clearTempCache(): void {
 | 
				
			||||||
 | 
					    for (const txid of this.tempCached) {
 | 
				
			||||||
 | 
					      delete this.txCache[txid];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this.tempCached = [];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  truncateTempCache(): void {
 | 
				
			||||||
 | 
					    if (this.tempCached.length > tempCacheSize) {
 | 
				
			||||||
 | 
					      const removed = this.tempCached.splice(0, this.tempCached.length - tempCacheSize);
 | 
				
			||||||
 | 
					      for (const txid of removed) {
 | 
				
			||||||
 | 
					        delete this.txCache[txid];
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default new ForensicsService();
 | 
					export default new ForensicsService();
 | 
				
			||||||
 | 
				
			|||||||
@ -31,6 +31,7 @@ class NetworkSyncService {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async $runTasks(): Promise<void> {
 | 
					  private async $runTasks(): Promise<void> {
 | 
				
			||||||
 | 
					    const taskStartTime = Date.now();
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      logger.info(`Updating nodes and channels`);
 | 
					      logger.info(`Updating nodes and channels`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -57,7 +58,7 @@ class NetworkSyncService {
 | 
				
			|||||||
      logger.err('$runTasks() error: ' + (e instanceof Error ? e.message : e));
 | 
					      logger.err('$runTasks() error: ' + (e instanceof Error ? e.message : e));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    setTimeout(() => { this.$runTasks(); }, 1000 * config.LIGHTNING.GRAPH_REFRESH_INTERVAL);
 | 
					    setTimeout(() => { this.$runTasks(); }, Math.max(1, (1000 * config.LIGHTNING.GRAPH_REFRESH_INTERVAL) - (Date.now() - taskStartTime)));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
 | 
				
			|||||||
@ -17,5 +17,8 @@
 | 
				
			|||||||
  "LIQUID_WEBSITE_URL": "https://liquid.network",
 | 
					  "LIQUID_WEBSITE_URL": "https://liquid.network",
 | 
				
			||||||
  "BISQ_WEBSITE_URL": "https://bisq.markets",
 | 
					  "BISQ_WEBSITE_URL": "https://bisq.markets",
 | 
				
			||||||
  "MINING_DASHBOARD": true,
 | 
					  "MINING_DASHBOARD": true,
 | 
				
			||||||
 | 
					  "MAINNET_BLOCK_AUDIT_START_HEIGHT": 0,
 | 
				
			||||||
 | 
					  "TESTNET_BLOCK_AUDIT_START_HEIGHT": 0,
 | 
				
			||||||
 | 
					  "SIGNET_BLOCK_AUDIT_START_HEIGHT": 0,
 | 
				
			||||||
  "LIGHTNING": false
 | 
					  "LIGHTNING": false
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										925
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										925
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -84,28 +84,28 @@
 | 
				
			|||||||
    "browserify": "^17.0.0",
 | 
					    "browserify": "^17.0.0",
 | 
				
			||||||
    "clipboard": "^2.0.11",
 | 
					    "clipboard": "^2.0.11",
 | 
				
			||||||
    "domino": "^2.1.6",
 | 
					    "domino": "^2.1.6",
 | 
				
			||||||
    "echarts": "~5.3.2",
 | 
					    "echarts": "~5.4.0",
 | 
				
			||||||
    "echarts-gl": "^2.0.9",
 | 
					    "echarts-gl": "^2.0.9",
 | 
				
			||||||
    "lightweight-charts": "~3.8.0",
 | 
					    "lightweight-charts": "~3.8.0",
 | 
				
			||||||
    "ngx-echarts": "8.0.1",
 | 
					    "ngx-echarts": "~14.0.0",
 | 
				
			||||||
    "ngx-infinite-scroll": "^14.0.1",
 | 
					    "ngx-infinite-scroll": "^14.0.1",
 | 
				
			||||||
    "qrcode": "1.5.0",
 | 
					    "qrcode": "1.5.1",
 | 
				
			||||||
    "rxjs": "~7.5.7",
 | 
					    "rxjs": "~7.5.7",
 | 
				
			||||||
    "tinyify": "^3.1.0",
 | 
					    "tinyify": "^3.1.0",
 | 
				
			||||||
    "tlite": "^0.1.9",
 | 
					    "tlite": "^0.1.9",
 | 
				
			||||||
    "tslib": "~2.4.1",
 | 
					    "tslib": "~2.4.1",
 | 
				
			||||||
    "zone.js": "~0.11.5"
 | 
					    "zone.js": "~0.12.0"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@angular/compiler-cli": "^14.2.12",
 | 
					    "@angular/compiler-cli": "^14.2.12",
 | 
				
			||||||
    "@angular/language-service": "^14.2.12",
 | 
					    "@angular/language-service": "^14.2.12",
 | 
				
			||||||
    "@types/node": "^12.11.1",
 | 
					    "@types/node": "^18.11.9",
 | 
				
			||||||
    "@typescript-eslint/eslint-plugin": "^5.30.5",
 | 
					    "@typescript-eslint/eslint-plugin": "^5.45.0",
 | 
				
			||||||
    "@typescript-eslint/parser": "^5.30.5",
 | 
					    "@typescript-eslint/parser": "^5.45.0",
 | 
				
			||||||
    "eslint": "^8.19.0",
 | 
					    "eslint": "^8.28.0",
 | 
				
			||||||
    "http-proxy-middleware": "~2.0.6",
 | 
					    "http-proxy-middleware": "~2.0.6",
 | 
				
			||||||
    "prettier": "^2.7.1",
 | 
					    "prettier": "^2.8.0",
 | 
				
			||||||
    "ts-node": "~10.8.1",
 | 
					    "ts-node": "~10.9.1",
 | 
				
			||||||
    "typescript": "~4.6.4"
 | 
					    "typescript": "~4.6.4"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "optionalDependencies": {
 | 
					  "optionalDependencies": {
 | 
				
			||||||
 | 
				
			|||||||
@ -129,7 +129,7 @@
 | 
				
			|||||||
        <span>Gemini</span>
 | 
					        <span>Gemini</span>
 | 
				
			||||||
      </a>
 | 
					      </a>
 | 
				
			||||||
      <a href="https://exodus.com/" target="_blank" title="Exodus">
 | 
					      <a href="https://exodus.com/" target="_blank" title="Exodus">
 | 
				
			||||||
        <svg width="81" height="81" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
 | 
					        <svg width="80" height="80" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
 | 
				
			||||||
          <circle cx="250" cy="250" r="250" fill="#1F2033"/>
 | 
					          <circle cx="250" cy="250" r="250" fill="#1F2033"/>
 | 
				
			||||||
          <g clip-path="url(#clip0_2_14)">
 | 
					          <g clip-path="url(#clip0_2_14)">
 | 
				
			||||||
            <path d="M411.042 178.303L271.79 87V138.048L361.121 196.097L350.612 229.351H271.79V271.648H350.612L361.121 304.903L271.79 362.952V414L411.042 322.989L388.271 250.646L411.042 178.303Z" fill="url(#paint0_linear_2_14)"/>
 | 
					            <path d="M411.042 178.303L271.79 87V138.048L361.121 196.097L350.612 229.351H271.79V271.648H350.612L361.121 304.903L271.79 362.952V414L411.042 322.989L388.271 250.646L411.042 178.303Z" fill="url(#paint0_linear_2_14)"/>
 | 
				
			||||||
 | 
				
			|||||||
@ -3,8 +3,8 @@
 | 
				
			|||||||
  text-align: center;
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .image {
 | 
					  .image {
 | 
				
			||||||
    width: 81px;
 | 
					    width: 80px;
 | 
				
			||||||
    height: 81px;
 | 
					    height: 80px;
 | 
				
			||||||
    background-size: 100%, 100%;
 | 
					    background-size: 100%, 100%;
 | 
				
			||||||
    border-radius: 50%;
 | 
					    border-radius: 50%;
 | 
				
			||||||
    margin: 25px;
 | 
					    margin: 25px;
 | 
				
			||||||
@ -191,6 +191,6 @@
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.community-integrations-sponsor {
 | 
					.community-integrations-sponsor {
 | 
				
			||||||
  max-width: 970px;
 | 
					  max-width: 965px;
 | 
				
			||||||
  margin: auto;
 | 
					  margin: auto;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -30,7 +30,6 @@ export class BisqMasterPageComponent implements OnInit {
 | 
				
			|||||||
    this.connectionState$ = this.stateService.connectionState$;
 | 
					    this.connectionState$ = this.stateService.connectionState$;
 | 
				
			||||||
    this.urlLanguage = this.languageService.getLanguageForUrl();
 | 
					    this.urlLanguage = this.languageService.getLanguageForUrl();
 | 
				
			||||||
    this.navigationService.subnetPaths.subscribe((paths) => {
 | 
					    this.navigationService.subnetPaths.subscribe((paths) => {
 | 
				
			||||||
      console.log('network paths updated...');
 | 
					 | 
				
			||||||
      this.networkPaths = paths;
 | 
					      this.networkPaths = paths;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -61,7 +61,7 @@
 | 
				
			|||||||
                  <span *ngIf="blockAudit?.matchRate === null" i18n="unknown">Unknown</span>
 | 
					                  <span *ngIf="blockAudit?.matchRate === null" i18n="unknown">Unknown</span>
 | 
				
			||||||
                </td>
 | 
					                </td>
 | 
				
			||||||
              </tr>
 | 
					              </tr>
 | 
				
			||||||
              <ng-container *ngIf="!indexingAvailable && webGlEnabled">
 | 
					              <ng-container *ngIf="webGlEnabled && (auditDataMissing || !indexingAvailable)">
 | 
				
			||||||
                <tr *ngIf="isMobile && auditEnabled"></tr>
 | 
					                <tr *ngIf="isMobile && auditEnabled"></tr>
 | 
				
			||||||
                <tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
 | 
					                <tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
 | 
				
			||||||
                  <td i18n="mempool-block.fee-span">Fee span</td>
 | 
					                  <td i18n="mempool-block.fee-span">Fee span</td>
 | 
				
			||||||
@ -146,7 +146,7 @@
 | 
				
			|||||||
              <tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
 | 
					              <tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
 | 
				
			||||||
                <td colspan="2"><span class="skeleton-loader"></span></td>
 | 
					                <td colspan="2"><span class="skeleton-loader"></span></td>
 | 
				
			||||||
              </tr>
 | 
					              </tr>
 | 
				
			||||||
              <ng-container *ngIf="!indexingAvailable && webGlEnabled">
 | 
					              <ng-container *ngIf="webGlEnabled && (!indexingAvailable || auditDataMissing)">
 | 
				
			||||||
                <tr *ngIf="isMobile && !auditEnabled"></tr>
 | 
					                <tr *ngIf="isMobile && !auditEnabled"></tr>
 | 
				
			||||||
                <tr>
 | 
					                <tr>
 | 
				
			||||||
                  <td class="td-width" colspan="2"><span class="skeleton-loader"></span></td>
 | 
					                  <td class="td-width" colspan="2"><span class="skeleton-loader"></span></td>
 | 
				
			||||||
@ -169,7 +169,7 @@
 | 
				
			|||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </ng-template>
 | 
					      </ng-template>
 | 
				
			||||||
      <div class="col-sm">
 | 
					      <div class="col-sm">
 | 
				
			||||||
        <table class="table table-borderless table-striped" *ngIf="!isLoadingBlock && (indexingAvailable || !webGlEnabled)">
 | 
					        <table class="table table-borderless table-striped" *ngIf="!isLoadingBlock && (!auditDataMissing || indexingAvailable && !webGlEnabled)">
 | 
				
			||||||
          <tbody>
 | 
					          <tbody>
 | 
				
			||||||
            <tr *ngIf="isMobile && auditEnabled"></tr>
 | 
					            <tr *ngIf="isMobile && auditEnabled"></tr>
 | 
				
			||||||
            <tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
 | 
					            <tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
 | 
				
			||||||
@ -233,7 +233,7 @@
 | 
				
			|||||||
            </tr>
 | 
					            </tr>
 | 
				
			||||||
          </tbody>
 | 
					          </tbody>
 | 
				
			||||||
        </table>
 | 
					        </table>
 | 
				
			||||||
        <table class="table table-borderless table-striped" *ngIf="isLoadingBlock && (indexingAvailable || !webGlEnabled)">
 | 
					        <table class="table table-borderless table-striped" *ngIf="isLoadingBlock && !auditDataMissing && (indexingAvailable || !webGlEnabled)">
 | 
				
			||||||
          <tbody>
 | 
					          <tbody>
 | 
				
			||||||
            <tr *ngIf="isMobile && !auditEnabled"></tr>
 | 
					            <tr *ngIf="isMobile && !auditEnabled"></tr>
 | 
				
			||||||
            <tr>
 | 
					            <tr>
 | 
				
			||||||
@ -253,7 +253,7 @@
 | 
				
			|||||||
            </tr>
 | 
					            </tr>
 | 
				
			||||||
          </tbody>
 | 
					          </tbody>
 | 
				
			||||||
        </table>
 | 
					        </table>
 | 
				
			||||||
        <div class="col-sm chart-container" *ngIf="webGlEnabled && !indexingAvailable">
 | 
					        <div class="col-sm chart-container" *ngIf="webGlEnabled && (!indexingAvailable || auditDataMissing)">
 | 
				
			||||||
          <app-block-overview-graph
 | 
					          <app-block-overview-graph
 | 
				
			||||||
            #blockGraphActual
 | 
					            #blockGraphActual
 | 
				
			||||||
            [isLoading]="isLoadingOverview"
 | 
					            [isLoading]="isLoadingOverview"
 | 
				
			||||||
@ -273,7 +273,7 @@
 | 
				
			|||||||
  <br>
 | 
					  <br>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <!-- VISUALIZATIONS -->
 | 
					  <!-- VISUALIZATIONS -->
 | 
				
			||||||
  <div class="box" *ngIf="!error && webGlEnabled && indexingAvailable">
 | 
					  <div class="box" *ngIf="!error && webGlEnabled && indexingAvailable && !auditDataMissing">
 | 
				
			||||||
    <div class="nav nav-tabs" *ngIf="isMobile && auditEnabled">
 | 
					    <div class="nav nav-tabs" *ngIf="isMobile && auditEnabled">
 | 
				
			||||||
      <a class="nav-link" [class.active]="mode === 'projected'" i18n="block.projected"
 | 
					      <a class="nav-link" [class.active]="mode === 'projected'" i18n="block.projected"
 | 
				
			||||||
        fragment="projected" (click)="changeMode('projected')">Projected</a>
 | 
					        fragment="projected" (click)="changeMode('projected')">Projected</a>
 | 
				
			||||||
 | 
				
			|||||||
@ -58,6 +58,7 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
  webGlEnabled = true;
 | 
					  webGlEnabled = true;
 | 
				
			||||||
  indexingAvailable = false;
 | 
					  indexingAvailable = false;
 | 
				
			||||||
  auditEnabled = true;
 | 
					  auditEnabled = true;
 | 
				
			||||||
 | 
					  auditDataMissing: boolean;
 | 
				
			||||||
  isMobile = window.innerWidth <= 767.98;
 | 
					  isMobile = window.innerWidth <= 767.98;
 | 
				
			||||||
  hoverTx: string;
 | 
					  hoverTx: string;
 | 
				
			||||||
  numMissing: number = 0;
 | 
					  numMissing: number = 0;
 | 
				
			||||||
@ -137,9 +138,11 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
        this.error = undefined;
 | 
					        this.error = undefined;
 | 
				
			||||||
        this.fees = undefined;
 | 
					        this.fees = undefined;
 | 
				
			||||||
        this.stateService.markBlock$.next({});
 | 
					        this.stateService.markBlock$.next({});
 | 
				
			||||||
 | 
					        this.auditDataMissing = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (history.state.data && history.state.data.blockHeight) {
 | 
					        if (history.state.data && history.state.data.blockHeight) {
 | 
				
			||||||
          this.blockHeight = history.state.data.blockHeight;
 | 
					          this.blockHeight = history.state.data.blockHeight;
 | 
				
			||||||
 | 
					          this.updateAuditDataMissingFromBlockHeight(this.blockHeight);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let isBlockHeight = false;
 | 
					        let isBlockHeight = false;
 | 
				
			||||||
@ -152,6 +155,7 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if (history.state.data && history.state.data.block) {
 | 
					        if (history.state.data && history.state.data.block) {
 | 
				
			||||||
          this.blockHeight = history.state.data.block.height;
 | 
					          this.blockHeight = history.state.data.block.height;
 | 
				
			||||||
 | 
					          this.updateAuditDataMissingFromBlockHeight(this.blockHeight);
 | 
				
			||||||
          return of(history.state.data.block);
 | 
					          return of(history.state.data.block);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          this.isLoadingBlock = true;
 | 
					          this.isLoadingBlock = true;
 | 
				
			||||||
@ -213,7 +217,7 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
            this.apiService.getBlockAudit$(block.previousblockhash);
 | 
					            this.apiService.getBlockAudit$(block.previousblockhash);
 | 
				
			||||||
          }, 100);
 | 
					          }, 100);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        this.updateAuditDataMissingFromBlockHeight(block.height);
 | 
				
			||||||
        this.block = block;
 | 
					        this.block = block;
 | 
				
			||||||
        this.blockHeight = block.height;
 | 
					        this.blockHeight = block.height;
 | 
				
			||||||
        this.lastBlockHeight = this.blockHeight;
 | 
					        this.lastBlockHeight = this.blockHeight;
 | 
				
			||||||
@ -363,6 +367,7 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
            this.auditEnabled = true;
 | 
					            this.auditEnabled = true;
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            this.auditEnabled = false;
 | 
					            this.auditEnabled = false;
 | 
				
			||||||
 | 
					            this.auditDataMissing = true;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          return blockAudit;
 | 
					          return blockAudit;
 | 
				
			||||||
        }),
 | 
					        }),
 | 
				
			||||||
@ -582,4 +587,23 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
      this.hoverTx = null;
 | 
					      this.hoverTx = null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  updateAuditDataMissingFromBlockHeight(blockHeight: number): void {
 | 
				
			||||||
 | 
					    switch (this.stateService.network) {
 | 
				
			||||||
 | 
					      case 'testnet':
 | 
				
			||||||
 | 
					        if (blockHeight < this.stateService.env.TESTNET_BLOCK_AUDIT_START_HEIGHT) {
 | 
				
			||||||
 | 
					          this.auditDataMissing = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case 'signet':
 | 
				
			||||||
 | 
					        if (blockHeight < this.stateService.env.SIGNET_BLOCK_AUDIT_START_HEIGHT) {
 | 
				
			||||||
 | 
					          this.auditDataMissing = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      default:
 | 
				
			||||||
 | 
					        if (blockHeight < this.stateService.env.MAINNET_BLOCK_AUDIT_START_HEIGHT) {
 | 
				
			||||||
 | 
					          this.auditDataMissing = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -33,7 +33,6 @@ export class LiquidMasterPageComponent implements OnInit {
 | 
				
			|||||||
    this.network$ = merge(of(''), this.stateService.networkChanged$);
 | 
					    this.network$ = merge(of(''), this.stateService.networkChanged$);
 | 
				
			||||||
    this.urlLanguage = this.languageService.getLanguageForUrl();
 | 
					    this.urlLanguage = this.languageService.getLanguageForUrl();
 | 
				
			||||||
    this.navigationService.subnetPaths.subscribe((paths) => {
 | 
					    this.navigationService.subnetPaths.subscribe((paths) => {
 | 
				
			||||||
      console.log('network paths updated...');
 | 
					 | 
				
			||||||
      this.networkPaths = paths;
 | 
					      this.networkPaths = paths;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -35,7 +35,6 @@ export class MasterPageComponent implements OnInit {
 | 
				
			|||||||
    this.urlLanguage = this.languageService.getLanguageForUrl();
 | 
					    this.urlLanguage = this.languageService.getLanguageForUrl();
 | 
				
			||||||
    this.subdomain = this.enterpriseService.getSubdomain();
 | 
					    this.subdomain = this.enterpriseService.getSubdomain();
 | 
				
			||||||
    this.navigationService.subnetPaths.subscribe((paths) => {
 | 
					    this.navigationService.subnetPaths.subscribe((paths) => {
 | 
				
			||||||
      console.log('network paths updated...');
 | 
					 | 
				
			||||||
      this.networkPaths = paths;
 | 
					      this.networkPaths = paths;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -63,40 +63,14 @@ export class TransactionPreviewComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
    this.fetchCpfpSubscription = this.fetchCpfp$
 | 
					    this.fetchCpfpSubscription = this.fetchCpfp$
 | 
				
			||||||
      .pipe(
 | 
					      .pipe(
 | 
				
			||||||
        switchMap((txId) =>
 | 
					        switchMap((txId) =>
 | 
				
			||||||
          this.apiService
 | 
					          this.apiService.getCpfpinfo$(txId).pipe(
 | 
				
			||||||
            .getCpfpinfo$(txId)
 | 
					            catchError((err) => {
 | 
				
			||||||
            .pipe(retryWhen((errors) => errors.pipe(delay(2000))))
 | 
					              return of(null);
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
      .subscribe((cpfpInfo) => {
 | 
					      .subscribe((cpfpInfo) => {
 | 
				
			||||||
        if (!this.tx) {
 | 
					 | 
				
			||||||
          return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (cpfpInfo.effectiveFeePerVsize) {
 | 
					 | 
				
			||||||
          this.tx.effectiveFeePerVsize = cpfpInfo.effectiveFeePerVsize;
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          const lowerFeeParents = cpfpInfo.ancestors.filter(
 | 
					 | 
				
			||||||
            (parent) => parent.fee / (parent.weight / 4) < this.tx.feePerVsize
 | 
					 | 
				
			||||||
          );
 | 
					 | 
				
			||||||
          let totalWeight =
 | 
					 | 
				
			||||||
            this.tx.weight +
 | 
					 | 
				
			||||||
            lowerFeeParents.reduce((prev, val) => prev + val.weight, 0);
 | 
					 | 
				
			||||||
          let totalFees =
 | 
					 | 
				
			||||||
            this.tx.fee +
 | 
					 | 
				
			||||||
            lowerFeeParents.reduce((prev, val) => prev + val.fee, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          if (cpfpInfo?.bestDescendant) {
 | 
					 | 
				
			||||||
            totalWeight += cpfpInfo?.bestDescendant.weight;
 | 
					 | 
				
			||||||
            totalFees += cpfpInfo?.bestDescendant.fee;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          this.tx.effectiveFeePerVsize = totalFees / (totalWeight / 4);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (!this.tx.status.confirmed) {
 | 
					 | 
				
			||||||
          this.stateService.markBlock$.next({
 | 
					 | 
				
			||||||
            txFeePerVSize: this.tx.effectiveFeePerVsize,
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        this.cpfpInfo = cpfpInfo;
 | 
					        this.cpfpInfo = cpfpInfo;
 | 
				
			||||||
        this.openGraphService.waitOver('cpfp-data-' + this.txId);
 | 
					        this.openGraphService.waitOver('cpfp-data-' + this.txId);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
				
			|||||||
@ -7,10 +7,11 @@ import {
 | 
				
			|||||||
  catchError,
 | 
					  catchError,
 | 
				
			||||||
  retryWhen,
 | 
					  retryWhen,
 | 
				
			||||||
  delay,
 | 
					  delay,
 | 
				
			||||||
  map
 | 
					  map,
 | 
				
			||||||
 | 
					  mergeMap
 | 
				
			||||||
} from 'rxjs/operators';
 | 
					} from 'rxjs/operators';
 | 
				
			||||||
import { Transaction } from '../../interfaces/electrs.interface';
 | 
					import { Transaction } from '../../interfaces/electrs.interface';
 | 
				
			||||||
import { of, merge, Subscription, Observable, Subject, timer, combineLatest, from } from 'rxjs';
 | 
					import { of, merge, Subscription, Observable, Subject, timer, combineLatest, from, throwError } from 'rxjs';
 | 
				
			||||||
import { StateService } from '../../services/state.service';
 | 
					import { StateService } from '../../services/state.service';
 | 
				
			||||||
import { WebsocketService } from '../../services/websocket.service';
 | 
					import { WebsocketService } from '../../services/websocket.service';
 | 
				
			||||||
import { AudioService } from '../../services/audio.service';
 | 
					import { AudioService } from '../../services/audio.service';
 | 
				
			||||||
@ -110,11 +111,24 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
 | 
				
			|||||||
        switchMap((txId) =>
 | 
					        switchMap((txId) =>
 | 
				
			||||||
          this.apiService
 | 
					          this.apiService
 | 
				
			||||||
            .getCpfpinfo$(txId)
 | 
					            .getCpfpinfo$(txId)
 | 
				
			||||||
            .pipe(retryWhen((errors) => errors.pipe(delay(2000))))
 | 
					            .pipe(retryWhen((errors) => errors.pipe(
 | 
				
			||||||
        )
 | 
					              mergeMap((error) => {
 | 
				
			||||||
 | 
					                if (!this.tx?.status || this.tx.status.confirmed) {
 | 
				
			||||||
 | 
					                  return throwError(error);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                  return of(null);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              }),
 | 
				
			||||||
 | 
					              delay(2000)
 | 
				
			||||||
 | 
					            )))
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        catchError(() => {
 | 
				
			||||||
 | 
					          return of(null);
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
      .subscribe((cpfpInfo) => {
 | 
					      .subscribe((cpfpInfo) => {
 | 
				
			||||||
        if (!this.tx) {
 | 
					        if (!cpfpInfo || !this.tx) {
 | 
				
			||||||
 | 
					          this.cpfpInfo = null;
 | 
				
			||||||
          return;
 | 
					          return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (cpfpInfo.effectiveFeePerVsize) {
 | 
					        if (cpfpInfo.effectiveFeePerVsize) {
 | 
				
			||||||
 | 
				
			|||||||
@ -217,8 +217,8 @@ export interface IChannel {
 | 
				
			|||||||
  updated_at: string;
 | 
					  updated_at: string;
 | 
				
			||||||
  created: string;
 | 
					  created: string;
 | 
				
			||||||
  status: number;
 | 
					  status: number;
 | 
				
			||||||
  node_left: Node,
 | 
					  node_left: INode,
 | 
				
			||||||
  node_right: Node,
 | 
					  node_right: INode,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -236,4 +236,6 @@ export interface INode {
 | 
				
			|||||||
  updated_at: string;
 | 
					  updated_at: string;
 | 
				
			||||||
  longitude: number;
 | 
					  longitude: number;
 | 
				
			||||||
  latitude: number;
 | 
					  latitude: number;
 | 
				
			||||||
 | 
					  funding_balance?: number;
 | 
				
			||||||
 | 
					  closing_balance?: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					<div class="box">
 | 
				
			||||||
 | 
					  <table class="table table-borderless table-striped">
 | 
				
			||||||
 | 
					    <tbody>
 | 
				
			||||||
 | 
					      <tr></tr>
 | 
				
			||||||
 | 
					      <tr>
 | 
				
			||||||
 | 
					        <td i18n="lightning.starting-balance|Channel starting balance">Starting balance</td>
 | 
				
			||||||
 | 
					        <td *ngIf="showStartingBalance && minStartingBalance === maxStartingBalance"><app-sats [satoshis]="minStartingBalance"></app-sats></td>
 | 
				
			||||||
 | 
					        <td *ngIf="showStartingBalance && minStartingBalance !== maxStartingBalance">{{ minStartingBalance | number : '1.0-0' }} - {{ maxStartingBalance | number : '1.0-0' }}<app-sats [valueOverride]=" "></app-sats></td>
 | 
				
			||||||
 | 
					        <td *ngIf="!showStartingBalance">?</td>
 | 
				
			||||||
 | 
					      </tr>
 | 
				
			||||||
 | 
					      <tr *ngIf="channel.status === 2">
 | 
				
			||||||
 | 
					        <td i18n="lightning.closing-balance|Channel closing balance">Closing balance</td>
 | 
				
			||||||
 | 
					        <td *ngIf="showClosingBalance && minClosingBalance === maxClosingBalance"><app-sats [satoshis]="minClosingBalance"></app-sats></td>
 | 
				
			||||||
 | 
					        <td *ngIf="showClosingBalance && minClosingBalance !== maxClosingBalance">{{ minClosingBalance | number : '1.0-0' }} - {{ maxClosingBalance | number : '1.0-0' }}<app-sats [valueOverride]=" "></app-sats></td>
 | 
				
			||||||
 | 
					        <td *ngIf="!showClosingBalance">?</td>
 | 
				
			||||||
 | 
					      </tr>
 | 
				
			||||||
 | 
					    </tbody>
 | 
				
			||||||
 | 
					  </table>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					.box {
 | 
				
			||||||
 | 
					  margin-top: 20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (max-width: 768px) {
 | 
				
			||||||
 | 
					  .box {
 | 
				
			||||||
 | 
					    margin-bottom: 20px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { ChannelCloseBoxComponent } from './channel-close-box.component';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('ChannelCloseBoxComponent', () => {
 | 
				
			||||||
 | 
					  let component: ChannelCloseBoxComponent;
 | 
				
			||||||
 | 
					  let fixture: ComponentFixture<ChannelCloseBoxComponent>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeEach(async () => {
 | 
				
			||||||
 | 
					    await TestBed.configureTestingModule({
 | 
				
			||||||
 | 
					      declarations: [ ChannelCloseBoxComponent ]
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .compileComponents();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeEach(() => {
 | 
				
			||||||
 | 
					    fixture = TestBed.createComponent(ChannelCloseBoxComponent);
 | 
				
			||||||
 | 
					    component = fixture.componentInstance;
 | 
				
			||||||
 | 
					    fixture.detectChanges();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should create', () => {
 | 
				
			||||||
 | 
					    expect(component).toBeTruthy();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component({
 | 
				
			||||||
 | 
					  selector: 'app-channel-close-box',
 | 
				
			||||||
 | 
					  templateUrl: './channel-close-box.component.html',
 | 
				
			||||||
 | 
					  styleUrls: ['./channel-close-box.component.scss'],
 | 
				
			||||||
 | 
					  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class ChannelCloseBoxComponent implements OnChanges {
 | 
				
			||||||
 | 
					  @Input() channel: any;
 | 
				
			||||||
 | 
					  @Input() local: any;
 | 
				
			||||||
 | 
					  @Input() remote: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  showStartingBalance: boolean = false;
 | 
				
			||||||
 | 
					  showClosingBalance: boolean = false;
 | 
				
			||||||
 | 
					  minStartingBalance: number;
 | 
				
			||||||
 | 
					  maxStartingBalance: number;
 | 
				
			||||||
 | 
					  minClosingBalance: number;
 | 
				
			||||||
 | 
					  maxClosingBalance: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor() { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ngOnChanges(changes: SimpleChanges): void {
 | 
				
			||||||
 | 
					    if (this.channel && this.local && this.remote) {
 | 
				
			||||||
 | 
					      this.showStartingBalance = (this.local.funding_balance || this.remote.funding_balance) && this.channel.funding_ratio;
 | 
				
			||||||
 | 
					      this.showClosingBalance = this.local.closing_balance || this.remote.closing_balance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (this.channel.single_funded) {
 | 
				
			||||||
 | 
					        if (this.local.funding_balance) {
 | 
				
			||||||
 | 
					          this.minStartingBalance = this.channel.capacity;
 | 
				
			||||||
 | 
					          this.maxStartingBalance = this.channel.capacity;
 | 
				
			||||||
 | 
					        } else if (this.remote.funding_balance) {
 | 
				
			||||||
 | 
					          this.minStartingBalance = 0;
 | 
				
			||||||
 | 
					          this.maxStartingBalance = 0;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        this.minStartingBalance = clampRound(0, this.channel.capacity, this.local.funding_balance * this.channel.funding_ratio);
 | 
				
			||||||
 | 
					        this.maxStartingBalance = clampRound(0, this.channel.capacity, this.channel.capacity - (this.remote.funding_balance * this.channel.funding_ratio));
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const closingCapacity = this.channel.capacity - this.channel.closing_fee;
 | 
				
			||||||
 | 
					      this.minClosingBalance = clampRound(0, closingCapacity, this.local.closing_balance);
 | 
				
			||||||
 | 
					      this.maxClosingBalance = clampRound(0, closingCapacity, closingCapacity - this.remote.closing_balance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // margin of error to account for 2 x 330 sat anchor outputs
 | 
				
			||||||
 | 
					      if (Math.abs(this.minClosingBalance - this.maxClosingBalance) <= 660) {
 | 
				
			||||||
 | 
					        this.maxClosingBalance = this.minClosingBalance;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      this.showStartingBalance = false;
 | 
				
			||||||
 | 
					      this.showClosingBalance = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function clampRound(min: number, max: number, value: number): number {
 | 
				
			||||||
 | 
					  return Math.max(0, Math.min(max, Math.round(value)));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -48,6 +48,15 @@
 | 
				
			|||||||
                <td i18n="lightning.capacity">Capacity</td>
 | 
					                <td i18n="lightning.capacity">Capacity</td>
 | 
				
			||||||
                <td><app-sats [satoshis]="channel.capacity"></app-sats><app-fiat [value]="channel.capacity" digitsInfo="1.0-0"></app-fiat></td>
 | 
					                <td><app-sats [satoshis]="channel.capacity"></app-sats><app-fiat [value]="channel.capacity" digitsInfo="1.0-0"></app-fiat></td>
 | 
				
			||||||
              </tr>
 | 
					              </tr>
 | 
				
			||||||
 | 
					              <tr *ngIf="channel.closed_by">
 | 
				
			||||||
 | 
					                <td i18n="lightning.closed_by">Closed by</td>
 | 
				
			||||||
 | 
					                <td>
 | 
				
			||||||
 | 
					                  <a [routerLink]="['/lightning/node' | relativeUrl, channel.closed_by]" >
 | 
				
			||||||
 | 
					                    <ng-container *ngIf="channel.closed_by === channel.node_left.public_key">{{ channel.node_left.alias }}</ng-container>
 | 
				
			||||||
 | 
					                    <ng-container *ngIf="channel.closed_by === channel.node_right.public_key">{{ channel.node_right.alias }}</ng-container>
 | 
				
			||||||
 | 
					                  </a>
 | 
				
			||||||
 | 
					                </td>
 | 
				
			||||||
 | 
					              </tr>
 | 
				
			||||||
            </tbody>
 | 
					            </tbody>
 | 
				
			||||||
          </table>
 | 
					          </table>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
@ -59,9 +68,11 @@
 | 
				
			|||||||
  <div class="row row-cols-1 row-cols-md-2">
 | 
					  <div class="row row-cols-1 row-cols-md-2">
 | 
				
			||||||
    <div class="col">
 | 
					    <div class="col">
 | 
				
			||||||
      <app-channel-box [channel]="channel.node_left"></app-channel-box>
 | 
					      <app-channel-box [channel]="channel.node_left"></app-channel-box>
 | 
				
			||||||
 | 
					      <app-channel-close-box *ngIf="showCloseBoxes(channel)" [channel]="channel" [local]="channel.node_left" [remote]="channel.node_right"></app-channel-close-box>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <div class="col">
 | 
					    <div class="col">
 | 
				
			||||||
      <app-channel-box [channel]="channel.node_right"></app-channel-box>
 | 
					      <app-channel-box [channel]="channel.node_right"></app-channel-box>
 | 
				
			||||||
 | 
					      <app-channel-close-box *ngIf="showCloseBoxes(channel)" [channel]="channel" [local]="channel.node_right" [remote]="channel.node_left"></app-channel-close-box>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -78,4 +78,9 @@ export class ChannelComponent implements OnInit {
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  showCloseBoxes(channel: IChannel): boolean {
 | 
				
			||||||
 | 
					    return !!(channel.node_left.funding_balance || channel.node_left.closing_balance 
 | 
				
			||||||
 | 
					      || channel.node_right.funding_balance || channel.node_right.closing_balance);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,7 @@ import { ChannelsListComponent } from './channels-list/channels-list.component';
 | 
				
			|||||||
import { ChannelComponent } from './channel/channel.component';
 | 
					import { ChannelComponent } from './channel/channel.component';
 | 
				
			||||||
import { LightningWrapperComponent } from './lightning-wrapper/lightning-wrapper.component';
 | 
					import { LightningWrapperComponent } from './lightning-wrapper/lightning-wrapper.component';
 | 
				
			||||||
import { ChannelBoxComponent } from './channel/channel-box/channel-box.component';
 | 
					import { ChannelBoxComponent } from './channel/channel-box/channel-box.component';
 | 
				
			||||||
 | 
					import { ChannelCloseBoxComponent } from './channel/channel-close-box/channel-close-box.component';
 | 
				
			||||||
import { ClosingTypeComponent } from './channel/closing-type/closing-type.component';
 | 
					import { ClosingTypeComponent } from './channel/closing-type/closing-type.component';
 | 
				
			||||||
import { LightningStatisticsChartComponent } from './statistics-chart/lightning-statistics-chart.component';
 | 
					import { LightningStatisticsChartComponent } from './statistics-chart/lightning-statistics-chart.component';
 | 
				
			||||||
import { NodeStatisticsChartComponent } from './node-statistics-chart/node-statistics-chart.component';
 | 
					import { NodeStatisticsChartComponent } from './node-statistics-chart/node-statistics-chart.component';
 | 
				
			||||||
@ -45,6 +46,7 @@ import { GroupComponent } from './group/group.component';
 | 
				
			|||||||
    ChannelComponent,
 | 
					    ChannelComponent,
 | 
				
			||||||
    LightningWrapperComponent,
 | 
					    LightningWrapperComponent,
 | 
				
			||||||
    ChannelBoxComponent,
 | 
					    ChannelBoxComponent,
 | 
				
			||||||
 | 
					    ChannelCloseBoxComponent,
 | 
				
			||||||
    ClosingTypeComponent,
 | 
					    ClosingTypeComponent,
 | 
				
			||||||
    LightningStatisticsChartComponent,
 | 
					    LightningStatisticsChartComponent,
 | 
				
			||||||
    NodesNetworksChartComponent,
 | 
					    NodesNetworksChartComponent,
 | 
				
			||||||
@ -81,6 +83,7 @@ import { GroupComponent } from './group/group.component';
 | 
				
			|||||||
    ChannelComponent,
 | 
					    ChannelComponent,
 | 
				
			||||||
    LightningWrapperComponent,
 | 
					    LightningWrapperComponent,
 | 
				
			||||||
    ChannelBoxComponent,
 | 
					    ChannelBoxComponent,
 | 
				
			||||||
 | 
					    ChannelCloseBoxComponent,
 | 
				
			||||||
    ClosingTypeComponent,
 | 
					    ClosingTypeComponent,
 | 
				
			||||||
    LightningStatisticsChartComponent,
 | 
					    LightningStatisticsChartComponent,
 | 
				
			||||||
    NodesNetworksChartComponent,
 | 
					    NodesNetworksChartComponent,
 | 
				
			||||||
 | 
				
			|||||||
@ -39,6 +39,9 @@ export interface Env {
 | 
				
			|||||||
  BISQ_WEBSITE_URL: string;
 | 
					  BISQ_WEBSITE_URL: string;
 | 
				
			||||||
  MINING_DASHBOARD: boolean;
 | 
					  MINING_DASHBOARD: boolean;
 | 
				
			||||||
  LIGHTNING: boolean;
 | 
					  LIGHTNING: boolean;
 | 
				
			||||||
 | 
					  MAINNET_BLOCK_AUDIT_START_HEIGHT: number;
 | 
				
			||||||
 | 
					  TESTNET_BLOCK_AUDIT_START_HEIGHT: number;
 | 
				
			||||||
 | 
					  SIGNET_BLOCK_AUDIT_START_HEIGHT: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const defaultEnv: Env = {
 | 
					const defaultEnv: Env = {
 | 
				
			||||||
@ -64,6 +67,9 @@ const defaultEnv: Env = {
 | 
				
			|||||||
  'BISQ_WEBSITE_URL': 'https://bisq.markets',
 | 
					  'BISQ_WEBSITE_URL': 'https://bisq.markets',
 | 
				
			||||||
  'MINING_DASHBOARD': true,
 | 
					  'MINING_DASHBOARD': true,
 | 
				
			||||||
  'LIGHTNING': false,
 | 
					  'LIGHTNING': false,
 | 
				
			||||||
 | 
					  'MAINNET_BLOCK_AUDIT_START_HEIGHT': 0,
 | 
				
			||||||
 | 
					  'TESTNET_BLOCK_AUDIT_START_HEIGHT': 0,
 | 
				
			||||||
 | 
					  'SIGNET_BLOCK_AUDIT_START_HEIGHT': 0,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable({
 | 
					@Injectable({
 | 
				
			||||||
 | 
				
			|||||||
@ -85,10 +85,10 @@ export const download = (href, name) => {
 | 
				
			|||||||
  document.body.removeChild(a);
 | 
					  document.body.removeChild(a);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function detectWebGL() {
 | 
					export function detectWebGL(): boolean {
 | 
				
			||||||
  const canvas = document.createElement('canvas');
 | 
					  const canvas = document.createElement('canvas');
 | 
				
			||||||
  const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
 | 
					  const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
 | 
				
			||||||
  return (gl && gl instanceof WebGLRenderingContext);
 | 
					  return !!(gl && gl instanceof WebGLRenderingContext);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 | 
				
			|||||||
@ -1842,13 +1842,13 @@ create database mempool_signet;
 | 
				
			|||||||
grant all on mempool_signet.* to '${MEMPOOL_SIGNET_USER}'@'localhost' identified by '${MEMPOOL_SIGNET_PASS}';
 | 
					grant all on mempool_signet.* to '${MEMPOOL_SIGNET_USER}'@'localhost' identified by '${MEMPOOL_SIGNET_PASS}';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
create database mempool_mainnet_lightning;
 | 
					create database mempool_mainnet_lightning;
 | 
				
			||||||
grant all on mempool_mainnet_lightning.* to '${LN_MEMPOOL_MAINNET_USER}'@'%' identified by '${LN_MEMPOOL_MAINNET_PASS}';
 | 
					grant all on mempool_mainnet_lightning.* to '${LN_MEMPOOL_MAINNET_USER}'@'localhost' identified by '${LN_MEMPOOL_MAINNET_PASS}';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
create database mempool_testnet_lightning;
 | 
					create database mempool_testnet_lightning;
 | 
				
			||||||
grant all on mempool_testnet_lightning.* to '${LN_MEMPOOL_TESTNET_USER}'@'%' identified by '${LN_MEMPOOL_TESTNET_PASS}';
 | 
					grant all on mempool_testnet_lightning.* to '${LN_MEMPOOL_TESTNET_USER}'@'localhost' identified by '${LN_MEMPOOL_TESTNET_PASS}';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
create database mempool_signet_lightning;
 | 
					create database mempool_signet_lightning;
 | 
				
			||||||
grant all on mempool_signet_lightning.* to '${LN_MEMPOOL_SIGNET_USER}'@'%' identified by '${LN_MEMPOOL_SIGNET_PASS}';
 | 
					grant all on mempool_signet_lightning.* to '${LN_MEMPOOL_SIGNET_USER}'@'localhost' identified by '${LN_MEMPOOL_SIGNET_PASS}';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
create database mempool_liquid;
 | 
					create database mempool_liquid;
 | 
				
			||||||
grant all on mempool_liquid.* to '${MEMPOOL_LIQUID_USER}'@'localhost' identified by '${MEMPOOL_LIQUID_PASS}';
 | 
					grant all on mempool_liquid.* to '${MEMPOOL_LIQUID_USER}'@'localhost' identified by '${MEMPOOL_LIQUID_PASS}';
 | 
				
			||||||
 | 
				
			|||||||
@ -10,6 +10,8 @@
 | 
				
			|||||||
    "POLL_RATE_MS": 1000,
 | 
					    "POLL_RATE_MS": 1000,
 | 
				
			||||||
    "INDEXING_BLOCKS_AMOUNT": -1,
 | 
					    "INDEXING_BLOCKS_AMOUNT": -1,
 | 
				
			||||||
    "BLOCKS_SUMMARIES_INDEXING": true,
 | 
					    "BLOCKS_SUMMARIES_INDEXING": true,
 | 
				
			||||||
 | 
					    "ADVANCED_GBT_AUDIT": true,
 | 
				
			||||||
 | 
					    "ADVANCED_GBT_MEMPOOL": false,
 | 
				
			||||||
    "USE_SECOND_NODE_FOR_MINFEE": true
 | 
					    "USE_SECOND_NODE_FOR_MINFEE": true
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "SYSLOG" : {
 | 
					  "SYSLOG" : {
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,8 @@
 | 
				
			|||||||
    "SPAWN_CLUSTER_PROCS": 0,
 | 
					    "SPAWN_CLUSTER_PROCS": 0,
 | 
				
			||||||
    "API_URL_PREFIX": "/api/v1/",
 | 
					    "API_URL_PREFIX": "/api/v1/",
 | 
				
			||||||
    "INDEXING_BLOCKS_AMOUNT": -1,
 | 
					    "INDEXING_BLOCKS_AMOUNT": -1,
 | 
				
			||||||
 | 
					    "ADVANCED_GBT_AUDIT": true,
 | 
				
			||||||
 | 
					    "ADVANCED_GBT_MEMPOOL": false,
 | 
				
			||||||
    "POLL_RATE_MS": 1000
 | 
					    "POLL_RATE_MS": 1000
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "SYSLOG" : {
 | 
					  "SYSLOG" : {
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,8 @@
 | 
				
			|||||||
    "SPAWN_CLUSTER_PROCS": 0,
 | 
					    "SPAWN_CLUSTER_PROCS": 0,
 | 
				
			||||||
    "API_URL_PREFIX": "/api/v1/",
 | 
					    "API_URL_PREFIX": "/api/v1/",
 | 
				
			||||||
    "INDEXING_BLOCKS_AMOUNT": -1,
 | 
					    "INDEXING_BLOCKS_AMOUNT": -1,
 | 
				
			||||||
 | 
					    "ADVANCED_GBT_AUDIT": true,
 | 
				
			||||||
 | 
					    "ADVANCED_GBT_MEMPOOL": false,
 | 
				
			||||||
    "POLL_RATE_MS": 1000
 | 
					    "POLL_RATE_MS": 1000
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "SYSLOG" : {
 | 
					  "SYSLOG" : {
 | 
				
			||||||
 | 
				
			|||||||
@ -35,3 +35,5 @@ gzip_types application/javascript application/json application/ld+json applicati
 | 
				
			|||||||
# limit request body size
 | 
					# limit request body size
 | 
				
			||||||
client_max_body_size 10m;
 | 
					client_max_body_size 10m;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# need to bump this up for about page sponsor images lol
 | 
				
			||||||
 | 
					http2_max_concurrent_streams 256;
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user