From e1623b92341a4b1b74784271757808d1766467f6 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 22 Mar 2022 15:16:15 +0900 Subject: [PATCH 01/68] Fix blocks list pagination on mobile --- .../src/app/components/blocks-list/blocks-list.component.html | 2 +- .../src/app/components/blocks-list/blocks-list.component.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.html b/frontend/src/app/components/blocks-list/blocks-list.component.html index d0eaa25ea..d582b32fe 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.html +++ b/frontend/src/app/components/blocks-list/blocks-list.component.html @@ -91,7 +91,7 @@ diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.ts b/frontend/src/app/components/blocks-list/blocks-list.component.ts index c04403446..9da92f158 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.ts +++ b/frontend/src/app/components/blocks-list/blocks-list.component.ts @@ -22,6 +22,7 @@ export class BlocksList implements OnInit { paginationMaxSize: number; page = 1; lastPage = 1; + maxSize = window.innerWidth <= 767.98 ? 3 : 5; blocksCount: number; fromHeightSubject: BehaviorSubject = new BehaviorSubject(this.fromBlockHeight); skeletonLines: number[] = []; From 7c1155ec93b3b28f6d9e8d8a56b21cbd9ce3b178 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 22 Mar 2022 15:43:04 +0900 Subject: [PATCH 02/68] Make sure blocks list container is at least 100vh on mobile --- .../app/components/blocks-list/blocks-list.component.html | 2 +- frontend/src/styles.scss | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.html b/frontend/src/app/components/blocks-list/blocks-list.component.html index d582b32fe..23cf9899c 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.html +++ b/frontend/src/app/components/blocks-list/blocks-list.component.html @@ -1,4 +1,4 @@ -
+

Blocks

diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index 54476206c..b56d7848d 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -66,6 +66,11 @@ body { .container-xl { padding-bottom: 60px; } +.full-height { + @media (max-width: 767.98px) { + min-height: 100vh; + } +} :focus { outline: none !important; From f1bb742341508fcadb92bcd4278be9fee822a188 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Thu, 24 Mar 2022 18:03:12 +0900 Subject: [PATCH 03/68] Fix rounding issue in reward stats --- .../app/components/reward-stats/reward-stats.component.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/reward-stats/reward-stats.component.ts b/frontend/src/app/components/reward-stats/reward-stats.component.ts index dd466985e..8116f2db7 100644 --- a/frontend/src/app/components/reward-stats/reward-stats.component.ts +++ b/frontend/src/app/components/reward-stats/reward-stats.component.ts @@ -24,10 +24,11 @@ export class RewardStatsComponent implements OnInit { return this.apiService.getRewardStats$() .pipe( map((stats) => { + const precision = 100; return { totalReward: stats.totalReward, - rewardPerTx: stats.totalReward / stats.totalTx, - feePerTx: stats.totalFee / stats.totalTx, + rewardPerTx: Math.round((stats.totalReward / stats.totalTx) * precision) / precision, + feePerTx: Math.round((stats.totalFee / stats.totalTx) * precision) / precision, }; }) ); From bb0fd78f2857e0568cfafa7fe3f9d292f4b05e68 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Thu, 24 Mar 2022 19:44:22 +0900 Subject: [PATCH 04/68] Added slug into `pools` table --- backend/src/api/database-migration.ts | 6 +++++- backend/src/api/pools-parser.ts | 20 ++++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/backend/src/api/database-migration.ts b/backend/src/api/database-migration.ts index 20519cbf2..3978a7d85 100644 --- a/backend/src/api/database-migration.ts +++ b/backend/src/api/database-migration.ts @@ -6,7 +6,7 @@ import logger from '../logger'; const sleep = (ms: number) => new Promise(res => setTimeout(res, ms)); class DatabaseMigration { - private static currentVersion = 16; + private static currentVersion = 17; private queryTimeout = 120000; private statisticsAddedIndexed = false; @@ -180,6 +180,10 @@ class DatabaseMigration { await this.$executeQuery(connection, 'TRUNCATE hashrates;'); // Need to re-index because we changed timestamps } + if (databaseSchemaVersion < 17 && isBitcoin === true) { + await this.$executeQuery(connection, 'ALTER TABLE `pools` ADD `slug` CHAR(50) NULL'); + } + connection.release(); } catch (e) { connection.release(); diff --git a/backend/src/api/pools-parser.ts b/backend/src/api/pools-parser.ts index ff70c3cb9..5428f931d 100644 --- a/backend/src/api/pools-parser.ts +++ b/backend/src/api/pools-parser.ts @@ -8,6 +8,7 @@ interface Pool { link: string; regexes: string[]; addresses: string[]; + slug: string; } class PoolsParser { @@ -42,6 +43,7 @@ class PoolsParser { 'link': (coinbaseTags[i][1]).link, 'regexes': [coinbaseTags[i][0]], 'addresses': [], + 'slug': '' }); } logger.debug('Parse payout_addresses'); @@ -52,6 +54,7 @@ class PoolsParser { 'link': (addressesTags[i][1]).link, 'regexes': [], 'addresses': [addressesTags[i][0]], + 'slug': '' }); } @@ -90,14 +93,15 @@ class PoolsParser { } const finalPoolName = poolNames[i].replace(`'`, `''`); // To support single quote in names when doing db queries + const slug = poolsJson['slugs'][poolNames[i]]; if (existingPools.find((pool) => pool.name === poolNames[i]) !== undefined) { - logger.debug(`Update '${finalPoolName}' mining pool`); finalPoolDataUpdate.push({ 'name': finalPoolName, 'link': match[0].link, 'regexes': allRegexes, 'addresses': allAddresses, + 'slug': slug }); } else { logger.debug(`Add '${finalPoolName}' mining pool`); @@ -106,6 +110,7 @@ class PoolsParser { 'link': match[0].link, 'regexes': allRegexes, 'addresses': allAddresses, + 'slug': slug }); } } @@ -126,7 +131,8 @@ class PoolsParser { updateQueries.push(` UPDATE pools SET name='${finalPoolDataUpdate[i].name}', link='${finalPoolDataUpdate[i].link}', - regexes='${JSON.stringify(finalPoolDataUpdate[i].regexes)}', addresses='${JSON.stringify(finalPoolDataUpdate[i].addresses)}' + regexes='${JSON.stringify(finalPoolDataUpdate[i].regexes)}', addresses='${JSON.stringify(finalPoolDataUpdate[i].addresses)}', + slug='${finalPoolDataUpdate[i].slug}' WHERE name='${finalPoolDataUpdate[i].name}' ;`); } @@ -156,11 +162,17 @@ class PoolsParser { try { const [rows]: any[] = await connection.query({ sql: 'SELECT name from pools where name="Unknown"', timeout: 120000 }); if (rows.length === 0) { - logger.debug('Manually inserting "Unknown" mining pool into the databse'); await connection.query({ sql: `INSERT INTO pools(name, link, regexes, addresses) - VALUES("Unknown", "https://learnmeabitcoin.com/technical/coinbase-transaction", "[]", "[]"); + VALUES("Unknown", "https://learnmeabitcoin.com/technical/coinbase-transaction", "[]", "[]", "unknown"); `}); + } else { + await connection.query(`UPDATE pools + SET name='Unknown', link='https://learnmeabitcoin.com/technical/coinbase-transaction', + regexes='[]', addresses='[]', + slug='unknown' + WHERE name='Unknown' + `) } } catch (e) { logger.err('Unable to insert "Unknown" mining pool'); From 27d2127d468fd43f0781b333b3caa75012aa5523 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Fri, 25 Mar 2022 10:08:20 +0900 Subject: [PATCH 05/68] Remove unnecessary echart init option --- .../app/components/hashrate-chart/hashrate-chart.component.ts | 2 -- .../hashrates-chart-pools/hashrate-chart-pools.component.ts | 2 -- .../src/app/components/pool-ranking/pool-ranking.component.ts | 2 -- frontend/src/app/components/pool/pool.component.ts | 2 -- 4 files changed, 8 deletions(-) diff --git a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts index 521bee3d5..c210017fa 100644 --- a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts +++ b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts @@ -33,8 +33,6 @@ export class HashrateChartComponent implements OnInit { chartOptions: EChartsOption = {}; chartInitOptions = { renderer: 'svg', - width: 'auto', - height: 'auto', }; hashrateObservable$: Observable; diff --git a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts index 280852d47..264ceb7ea 100644 --- a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts +++ b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts @@ -31,8 +31,6 @@ export class HashrateChartPoolsComponent implements OnInit { chartOptions: EChartsOption = {}; chartInitOptions = { renderer: 'svg', - width: 'auto', - height: 'auto', }; hashrateObservable$: Observable; diff --git a/frontend/src/app/components/pool-ranking/pool-ranking.component.ts b/frontend/src/app/components/pool-ranking/pool-ranking.component.ts index 0722466fc..196d4b19c 100644 --- a/frontend/src/app/components/pool-ranking/pool-ranking.component.ts +++ b/frontend/src/app/components/pool-ranking/pool-ranking.component.ts @@ -27,8 +27,6 @@ export class PoolRankingComponent implements OnInit { chartOptions: EChartsOption = {}; chartInitOptions = { renderer: 'svg', - width: 'auto', - height: 'auto', }; chartInstance: any = undefined; diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index dee4a9713..5afc70cc6 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -27,8 +27,6 @@ export class PoolComponent implements OnInit { chartOptions: EChartsOption = {}; chartInitOptions = { renderer: 'svg', - width: 'auto', - height: 'auto', }; blocks: BlockExtended[] = []; From b075fedd7c4bd9ef12dba0084f72f827ea76201c Mon Sep 17 00:00:00 2001 From: nymkappa Date: Fri, 25 Mar 2022 10:57:34 +0900 Subject: [PATCH 06/68] Use relative pipe for pie chart click event --- .../src/app/components/blocks-list/blocks-list.component.html | 2 +- .../src/app/components/pool-ranking/pool-ranking.component.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.html b/frontend/src/app/components/blocks-list/blocks-list.component.html index 22693a856..c72c3281c 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.html +++ b/frontend/src/app/components/blocks-list/blocks-list.component.html @@ -25,7 +25,7 @@
- + {{ block.extras.pool.name }} diff --git a/frontend/src/app/components/pool-ranking/pool-ranking.component.ts b/frontend/src/app/components/pool-ranking/pool-ranking.component.ts index 0722466fc..f99ee6146 100644 --- a/frontend/src/app/components/pool-ranking/pool-ranking.component.ts +++ b/frontend/src/app/components/pool-ranking/pool-ranking.component.ts @@ -10,6 +10,7 @@ import { StorageService } from '../..//services/storage.service'; import { MiningService, MiningStats } from '../../services/mining.service'; import { StateService } from '../../services/state.service'; import { chartColors, poolsColor } from 'src/app/app.constants'; +import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe'; @Component({ selector: 'app-pool-ranking', @@ -284,7 +285,8 @@ export class PoolRankingComponent implements OnInit { return; } this.zone.run(() => { - this.router.navigate(['/mining/pool/', e.data.data]); + const url = new RelativeUrlPipe(this.stateService).transform(`/mining/pool/${e.data.data}`); + this.router.navigate([url]); }); }); } From c5837ab9df1ba1424375eae3eb170f1d9748b985 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Fri, 25 Mar 2022 11:38:36 +0900 Subject: [PATCH 07/68] Round using AmountShortenerPipe --- .../app/components/reward-stats/reward-stats.component.html | 4 ++-- .../app/components/reward-stats/reward-stats.component.ts | 5 ++--- frontend/src/app/shared/pipes/amount-shortener.pipe.ts | 5 +++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/components/reward-stats/reward-stats.component.html b/frontend/src/app/components/reward-stats/reward-stats.component.html index 861921ca6..9a592ebae 100644 --- a/frontend/src/app/components/reward-stats/reward-stats.component.html +++ b/frontend/src/app/components/reward-stats/reward-stats.component.html @@ -17,7 +17,7 @@
- {{ rewardStats.rewardPerTx | amountShortener }} + {{ rewardStats.rewardPerTx | amountShortener: 2 }} sats/tx
@@ -29,7 +29,7 @@
Average Fee
-
{{ rewardStats.feePerTx | amountShortener }} +
{{ rewardStats.feePerTx | amountShortener: 2 }} sats/tx
diff --git a/frontend/src/app/components/reward-stats/reward-stats.component.ts b/frontend/src/app/components/reward-stats/reward-stats.component.ts index 8116f2db7..dd466985e 100644 --- a/frontend/src/app/components/reward-stats/reward-stats.component.ts +++ b/frontend/src/app/components/reward-stats/reward-stats.component.ts @@ -24,11 +24,10 @@ export class RewardStatsComponent implements OnInit { return this.apiService.getRewardStats$() .pipe( map((stats) => { - const precision = 100; return { totalReward: stats.totalReward, - rewardPerTx: Math.round((stats.totalReward / stats.totalTx) * precision) / precision, - feePerTx: Math.round((stats.totalFee / stats.totalTx) * precision) / precision, + rewardPerTx: stats.totalReward / stats.totalTx, + feePerTx: stats.totalFee / stats.totalTx, }; }) ); diff --git a/frontend/src/app/shared/pipes/amount-shortener.pipe.ts b/frontend/src/app/shared/pipes/amount-shortener.pipe.ts index 529c6be79..319dc2a5a 100644 --- a/frontend/src/app/shared/pipes/amount-shortener.pipe.ts +++ b/frontend/src/app/shared/pipes/amount-shortener.pipe.ts @@ -5,11 +5,12 @@ import { Pipe, PipeTransform } from '@angular/core'; }) export class AmountShortenerPipe implements PipeTransform { transform(num: number, ...args: number[]): unknown { + const digits = args[0] || 1; + if (num < 1000) { - return num; + return num.toFixed(digits); } - const digits = args[0] || 1; const lookup = [ { value: 1, symbol: '' }, { value: 1e3, symbol: 'k' }, From 810c335759cefb4127dfd4006e22e6be499a9377 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Fri, 25 Mar 2022 12:31:09 +0900 Subject: [PATCH 08/68] If pool slug does not exist, generate one on the fly, avoid crash --- backend/src/api/pools-parser.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/backend/src/api/pools-parser.ts b/backend/src/api/pools-parser.ts index 5428f931d..7243eb023 100644 --- a/backend/src/api/pools-parser.ts +++ b/backend/src/api/pools-parser.ts @@ -12,6 +12,8 @@ interface Pool { } class PoolsParser { + slugWarnFlag = false; + /** * Parse the pools.json file, consolidate the data and dump it into the database */ @@ -93,7 +95,22 @@ class PoolsParser { } const finalPoolName = poolNames[i].replace(`'`, `''`); // To support single quote in names when doing db queries - const slug = poolsJson['slugs'][poolNames[i]]; + + let slug: string | undefined; + try { + slug = poolsJson['slugs'][poolNames[i]]; + } catch (e) { + if (this.slugWarnFlag === false) { + logger.warn(`pools.json does not seem to contain the 'slugs' object`); + this.slugWarnFlag = true; + } + } + + if (slug === undefined) { + // Only keep alphanumerical + slug = poolNames[i].replace(/[^a-z0-9]/gi,'').toLowerCase(); + logger.debug(`No slug found for '${poolNames[i]}', generating it => '${slug}'`); + } if (existingPools.find((pool) => pool.name === poolNames[i]) !== undefined) { finalPoolDataUpdate.push({ From 352ea950a29de6e24d2a560c28b8391f05457265 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Fri, 25 Mar 2022 14:22:22 +0900 Subject: [PATCH 09/68] Use mining pool slug in urls --- backend/src/api/blocks.ts | 3 ++- backend/src/api/mining.ts | 11 +++++---- backend/src/index.ts | 10 ++++---- backend/src/mempool.interfaces.ts | 3 +++ backend/src/repositories/BlocksRepository.ts | 12 +++++++--- .../src/repositories/HashratesRepository.ts | 14 +++++++---- backend/src/repositories/PoolsRepository.ts | 17 ++++++------- backend/src/routes.ts | 6 ++--- backend/src/utils/blocks-utils.ts | 1 + frontend/src/app/app-routing.module.ts | 6 ++--- .../blockchain-blocks.component.html | 2 +- .../blocks-list/blocks-list.component.html | 2 +- .../pool-ranking/pool-ranking.component.html | 2 +- .../pool-ranking/pool-ranking.component.ts | 2 +- .../src/app/components/pool/pool.component.ts | 24 +++++++++---------- .../app/dashboard/dashboard.component.html | 2 +- .../src/app/interfaces/node-api.interface.ts | 2 ++ frontend/src/app/services/api.service.ts | 12 +++++----- 18 files changed, 76 insertions(+), 55 deletions(-) diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index bff73dd54..80e7a4e1f 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -137,7 +137,8 @@ class Blocks { } blockExtended.extras.pool = { id: pool.id, - name: pool.name + name: pool.name, + slug: pool.slug, }; } diff --git a/backend/src/api/mining.ts b/backend/src/api/mining.ts index 35884efb3..7e15c85d0 100644 --- a/backend/src/api/mining.ts +++ b/backend/src/api/mining.ts @@ -33,7 +33,8 @@ class Mining { link: poolInfo.link, blockCount: poolInfo.blockCount, rank: rank++, - emptyBlocks: emptyBlocksCount.length > 0 ? emptyBlocksCount[0]['count'] : 0 + emptyBlocks: emptyBlocksCount.length > 0 ? emptyBlocksCount[0]['count'] : 0, + slug: poolInfo.slug, }; poolsStats.push(poolStat); }); @@ -54,14 +55,14 @@ class Mining { /** * Get all mining pool stats for a pool */ - public async $getPoolStat(poolId: number): Promise { - const pool = await PoolsRepository.$getPool(poolId); + public async $getPoolStat(slug: string): Promise { + const pool = await PoolsRepository.$getPool(slug); if (!pool) { throw new Error(`This mining pool does not exist`); } - const blockCount: number = await BlocksRepository.$blockCount(poolId); - const emptyBlocksCount = await BlocksRepository.$countEmptyBlocks(poolId); + const blockCount: number = await BlocksRepository.$blockCount(pool.id); + const emptyBlocksCount = await BlocksRepository.$countEmptyBlocks(pool.id); return { pool: pool, diff --git a/backend/src/index.ts b/backend/src/index.ts index d5bf0e59e..62f9a8cd9 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -301,11 +301,11 @@ class Server { .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/2y', routes.$getPools.bind(routes, '2y')) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/3y', routes.$getPools.bind(routes, '3y')) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/all', routes.$getPools.bind(routes, 'all')) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/hashrate', routes.$getPoolHistoricalHashrate) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/blocks', routes.$getPoolBlocks) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/blocks/:height', routes.$getPoolBlocks) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId', routes.$getPool) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/:interval', routes.$getPool) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug/hashrate', routes.$getPoolHistoricalHashrate) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug/blocks', routes.$getPoolBlocks) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug/blocks/:height', routes.$getPoolBlocks) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug', routes.$getPool) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug/:interval', routes.$getPool) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty', routes.$getHistoricalDifficulty) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty/:interval', routes.$getHistoricalDifficulty) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate/pools', routes.$getPoolsHistoricalHashrate) diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index 15d1ad618..0081bd34f 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -6,6 +6,7 @@ export interface PoolTag { link: string; regexes: string; // JSON array addresses: string; // JSON array + slug: string; } export interface PoolInfo { @@ -13,6 +14,7 @@ export interface PoolInfo { name: string; link: string; blockCount: number; + slug: string; } export interface PoolStats extends PoolInfo { @@ -87,6 +89,7 @@ export interface BlockExtension { pool?: { id: number; name: string; + slug: string; }; avgFee?: number; avgFeeRate?: number; diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 33cb727d9..5b253d3a0 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -3,6 +3,7 @@ import { DB } from '../database'; import logger from '../logger'; import { Common } from '../api/common'; import { prepareBlock } from '../utils/blocks-utils'; +import PoolsRepository from './PoolsRepository'; class BlocksRepository { /** @@ -235,13 +236,18 @@ class BlocksRepository { /** * Get blocks mined by a specific mining pool */ - public async $getBlocksByPool(poolId: number, startHeight: number | undefined = undefined): Promise { + public async $getBlocksByPool(slug: string, startHeight: number | undefined = undefined): Promise { + const pool = await PoolsRepository.$getPool(slug); + if (!pool) { + throw new Error(`This mining pool does not exist`); + } + const params: any[] = []; let query = ` SELECT *, UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp, previous_block_hash as previousblockhash FROM blocks WHERE pool_id = ?`; - params.push(poolId); + params.push(pool.id); if (startHeight !== undefined) { query += ` AND height < ?`; @@ -277,7 +283,7 @@ class BlocksRepository { try { const [rows]: any[] = await connection.query(` SELECT *, UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp, - pools.id as pool_id, pools.name as pool_name, pools.link as pool_link, + pools.id as pool_id, pools.name as pool_name, pools.link as pool_link, pools.slug as pool_slug, pools.addresses as pool_addresses, pools.regexes as pool_regexes, previous_block_hash as previousblockhash FROM blocks diff --git a/backend/src/repositories/HashratesRepository.ts b/backend/src/repositories/HashratesRepository.ts index 5237e6cb7..5efce29fe 100644 --- a/backend/src/repositories/HashratesRepository.ts +++ b/backend/src/repositories/HashratesRepository.ts @@ -120,8 +120,11 @@ class HashratesRepository { /** * Returns a pool hashrate history */ - public async $getPoolWeeklyHashrate(poolId: number): Promise { - const connection = await DB.getConnection(); + public async $getPoolWeeklyHashrate(slug: string): Promise { + const pool = await PoolsRepository.$getPool(slug); + if (!pool) { + throw new Error(`This mining pool does not exist`); + } // Find hashrate boundaries let query = `SELECT MIN(hashrate_timestamp) as firstTimestamp, MAX(hashrate_timestamp) as lastTimestamp @@ -134,8 +137,11 @@ class HashratesRepository { firstTimestamp: '1970-01-01', lastTimestamp: '9999-01-01' }; + + let connection; try { - const [rows]: any[] = await connection.query(query, [poolId]); + connection = await DB.getConnection(); + const [rows]: any[] = await connection.query(query, [pool.id]); boundaries = rows[0]; connection.release(); } catch (e) { @@ -152,7 +158,7 @@ class HashratesRepository { ORDER by hashrate_timestamp`; try { - const [rows]: any[] = await connection.query(query, [boundaries.firstTimestamp, boundaries.lastTimestamp, poolId]); + const [rows]: any[] = await connection.query(query, [boundaries.firstTimestamp, boundaries.lastTimestamp, pool.id]); connection.release(); return rows; diff --git a/backend/src/repositories/PoolsRepository.ts b/backend/src/repositories/PoolsRepository.ts index 4c3fd67ce..d9defaaed 100644 --- a/backend/src/repositories/PoolsRepository.ts +++ b/backend/src/repositories/PoolsRepository.ts @@ -9,7 +9,7 @@ class PoolsRepository { */ public async $getPools(): Promise { const connection = await DB.getConnection(); - const [rows] = await connection.query('SELECT id, name, addresses, regexes FROM pools;'); + const [rows] = await connection.query('SELECT id, name, addresses, regexes, slug FROM pools;'); connection.release(); return rows; } @@ -19,7 +19,7 @@ class PoolsRepository { */ public async $getUnknownPool(): Promise { const connection = await DB.getConnection(); - const [rows] = await connection.query('SELECT id, name FROM pools where name = "Unknown"'); + const [rows] = await connection.query('SELECT id, name, slug FROM pools where name = "Unknown"'); connection.release(); return rows[0]; } @@ -30,7 +30,7 @@ class PoolsRepository { public async $getPoolsInfo(interval: string | null = null): Promise { interval = Common.getSqlInterval(interval); - let query = `SELECT COUNT(height) as blockCount, pool_id as poolId, pools.name as name, pools.link as link + let query = `SELECT COUNT(height) as blockCount, pool_id as poolId, pools.name as name, pools.link as link, slug FROM blocks JOIN pools on pools.id = pool_id`; @@ -80,16 +80,17 @@ class PoolsRepository { /** * Get mining pool statistics for one pool */ - public async $getPool(poolId: any): Promise { + public async $getPool(slug: string): Promise { const query = ` SELECT * FROM pools - WHERE pools.id = ?`; + WHERE pools.slug = ?`; - // logger.debug(query); - const connection = await DB.getConnection(); + let connection; try { - const [rows] = await connection.query(query, [poolId]); + connection = await DB.getConnection(); + + const [rows] = await connection.query(query, [slug]); connection.release(); rows[0].regexes = JSON.parse(rows[0].regexes); diff --git a/backend/src/routes.ts b/backend/src/routes.ts index b14ea6ac4..e98c718ef 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -539,7 +539,7 @@ class Routes { public async $getPool(req: Request, res: Response) { try { - const stats = await mining.$getPoolStat(parseInt(req.params.poolId, 10)); + const stats = await mining.$getPoolStat(req.params.slug); res.header('Pragma', 'public'); res.header('Cache-control', 'public'); res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); @@ -552,7 +552,7 @@ class Routes { public async $getPoolBlocks(req: Request, res: Response) { try { const poolBlocks = await BlocksRepository.$getBlocksByPool( - parseInt(req.params.poolId, 10), + req.params.slug, req.params.height === undefined ? undefined : parseInt(req.params.height, 10), ); res.header('Pragma', 'public'); @@ -606,7 +606,7 @@ class Routes { public async $getPoolHistoricalHashrate(req: Request, res: Response) { try { - const hashrates = await HashratesRepository.$getPoolWeeklyHashrate(parseInt(req.params.poolId, 10)); + const hashrates = await HashratesRepository.$getPoolWeeklyHashrate(req.params.slug); const oldestIndexedBlockTimestamp = await BlocksRepository.$oldestBlockTimestamp(); res.header('Pragma', 'public'); res.header('Cache-control', 'public'); diff --git a/backend/src/utils/blocks-utils.ts b/backend/src/utils/blocks-utils.ts index 107099ba3..7b5c0b23a 100644 --- a/backend/src/utils/blocks-utils.ts +++ b/backend/src/utils/blocks-utils.ts @@ -23,6 +23,7 @@ export function prepareBlock(block: any): BlockExtended { pool: block?.extras?.pool ?? (block?.pool_id ? { id: block.pool_id, name: block.pool_name, + slug: block.pool_slug, } : undefined), } }; diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 003bbcf0d..d46da5696 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -85,7 +85,7 @@ let routes: Routes = [ path: 'pool', children: [ { - path: ':poolId', + path: ':slug', component: PoolComponent, }, ] @@ -227,7 +227,7 @@ let routes: Routes = [ path: 'pool', children: [ { - path: ':poolId', + path: ':slug', component: PoolComponent, }, ] @@ -363,7 +363,7 @@ let routes: Routes = [ path: 'pool', children: [ { - path: ':poolId', + path: ':slug', component: PoolComponent, }, ] diff --git a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html index bc0025d2b..d41d34b81 100644 --- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html +++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html @@ -25,7 +25,7 @@
diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.html b/frontend/src/app/components/blocks-list/blocks-list.component.html index 22693a856..e7c93d3ab 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.html +++ b/frontend/src/app/components/blocks-list/blocks-list.component.html @@ -25,7 +25,7 @@
- + {{ block.extras.pool.name }} diff --git a/frontend/src/app/components/pool-ranking/pool-ranking.component.html b/frontend/src/app/components/pool-ranking/pool-ranking.component.html index 15eed9f22..521f60d81 100644 --- a/frontend/src/app/components/pool-ranking/pool-ranking.component.html +++ b/frontend/src/app/components/pool-ranking/pool-ranking.component.html @@ -85,7 +85,7 @@ {{ pool.rank }} - {{ pool.name }} + {{ pool.name }} {{ pool.lastEstimatedHashrate }} {{ miningStats.miningUnits.hashrateUnit }} {{ pool['blockText'] }} diff --git a/frontend/src/app/components/pool-ranking/pool-ranking.component.ts b/frontend/src/app/components/pool-ranking/pool-ranking.component.ts index 0722466fc..95156a487 100644 --- a/frontend/src/app/components/pool-ranking/pool-ranking.component.ts +++ b/frontend/src/app/components/pool-ranking/pool-ranking.component.ts @@ -159,7 +159,7 @@ export class PoolRankingComponent implements OnInit { } } }, - data: pool.poolId, + data: pool.slug, } as PieSeriesOption); }); diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index dee4a9713..82669ad26 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -32,9 +32,9 @@ export class PoolComponent implements OnInit { }; blocks: BlockExtended[] = []; - poolId: number = undefined; + slug: string = undefined; - loadMoreSubject: BehaviorSubject = new BehaviorSubject(this.poolId); + loadMoreSubject: BehaviorSubject = new BehaviorSubject(this.slug); constructor( @Inject(LOCALE_ID) public locale: string, @@ -45,23 +45,23 @@ export class PoolComponent implements OnInit { } ngOnInit(): void { - this.poolStats$ = this.route.params.pipe(map((params) => params.poolId)) + this.poolStats$ = this.route.params.pipe(map((params) => params.slug)) .pipe( - switchMap((poolId: any) => { + switchMap((slug: any) => { this.isLoading = true; - this.poolId = poolId; - this.loadMoreSubject.next(this.poolId); - return this.apiService.getPoolHashrate$(this.poolId) + this.slug = slug; + this.loadMoreSubject.next(this.slug); + return this.apiService.getPoolHashrate$(this.slug) .pipe( switchMap((data) => { this.isLoading = false; this.prepareChartOptions(data.hashrates.map(val => [val.timestamp * 1000, val.avgHashrate])); - return poolId; + return slug; }), ); }), switchMap(() => { - return this.apiService.getPoolStats$(this.poolId); + return this.apiService.getPoolStats$(this.slug); }), map((poolStats) => { let regexes = '"'; @@ -80,10 +80,10 @@ export class PoolComponent implements OnInit { this.blocks$ = this.loadMoreSubject .pipe( switchMap((flag) => { - if (this.poolId === undefined) { + if (this.slug === undefined) { return []; } - return this.apiService.getPoolBlocks$(this.poolId, this.blocks[this.blocks.length - 1]?.height); + return this.apiService.getPoolBlocks$(this.slug, this.blocks[this.blocks.length - 1]?.height); }), tap((newBlocks) => { this.blocks = this.blocks.concat(newBlocks); @@ -182,7 +182,7 @@ export class PoolComponent implements OnInit { } loadMore() { - this.loadMoreSubject.next(this.poolId); + this.loadMoreSubject.next(this.slug); } trackByBlock(index: number, block: BlockExtended) { diff --git a/frontend/src/app/dashboard/dashboard.component.html b/frontend/src/app/dashboard/dashboard.component.html index 3a028adc8..95ff4aa33 100644 --- a/frontend/src/app/dashboard/dashboard.component.html +++ b/frontend/src/app/dashboard/dashboard.component.html @@ -120,7 +120,7 @@ {{ block.height }} - + {{ block.extras.pool.name }} diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index 786fd6687..022e215d0 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -71,6 +71,7 @@ export interface SinglePoolStats { lastEstimatedHashrate: string; emptyBlockRatio: string; logo: string; + slug: string; } export interface PoolsStats { blockCount: number; @@ -107,6 +108,7 @@ export interface BlockExtension { pool?: { id: number; name: string; + slug: string; } stage?: number; // Frontend only diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index 92068c44e..7be8b944f 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -132,17 +132,17 @@ export class ApiService { ); } - getPoolStats$(poolId: number): Observable { - return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${poolId}`); + getPoolStats$(slug: string): Observable { + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${slug}`); } - getPoolHashrate$(poolId: number): Observable { - return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${poolId}/hashrate`); + getPoolHashrate$(slug: string): Observable { + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${slug}/hashrate`); } - getPoolBlocks$(poolId: number, fromHeight: number): Observable { + getPoolBlocks$(slug: string, fromHeight: number): Observable { return this.httpClient.get( - this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${poolId}/blocks` + + this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${slug}/blocks` + (fromHeight !== undefined ? `/${fromHeight}` : '') ); } From ef49457ec6af5ea2eaa560ebb18b058d1a17c42b Mon Sep 17 00:00:00 2001 From: nymkappa Date: Fri, 25 Mar 2022 17:48:24 +0900 Subject: [PATCH 10/68] Pool addresses collapse - Cleanup mobile layout --- .../app/components/pool/pool.component.html | 118 ++++++++++++++---- .../app/components/pool/pool.component.scss | 17 ++- .../src/app/components/pool/pool.component.ts | 2 + 3 files changed, 106 insertions(+), 31 deletions(-) diff --git a/frontend/src/app/components/pool/pool.component.html b/frontend/src/app/components/pool/pool.component.html index 240648e2c..3ee144f64 100644 --- a/frontend/src/app/components/pool/pool.component.html +++ b/frontend/src/app/components/pool/pool.component.html @@ -9,44 +9,104 @@
+
- - - + + + + + + + - - - + + - - - + + + + +
Tags -
+ + +
Tags + {{ poolStats.pool.regexes }} +
+ Tags +
{{ poolStats.pool.regexes }}
Addresses -
- +
Addresses + + {{ poolStats.pool.addresses[0] }} + + + ~
+ Addresses + +
+
- - + + + + - - + + + + + + + + + + + + +
Mined Blocks
Mined Blocks {{ formatNumber(poolStats.blockCount, this.locale, '1.0-0') }}
Empty Blocks
+ Mined Blocks +
{{ formatNumber(poolStats.blockCount, this.locale, '1.0-0') }}
+
Empty Blocks {{ formatNumber(poolStats.emptyBlocks, this.locale, '1.0-0') }}
+ Blocks +
{{ formatNumber(poolStats.emptyBlocks, this.locale, '1.0-0') }}
+
@@ -54,14 +114,20 @@
+ + ~ + + +
~
+
+
- +
@@ -147,7 +213,9 @@
-

+

+
+

@@ -157,13 +225,13 @@
- -
Height Timestamp
Tags +
Addresses +
@@ -176,17 +244,17 @@
- +
- - diff --git a/frontend/src/app/components/pool/pool.component.scss b/frontend/src/app/components/pool/pool.component.scss index 2a06de54a..cae0cc173 100644 --- a/frontend/src/app/components/pool/pool.component.scss +++ b/frontend/src/app/components/pool/pool.component.scss @@ -46,6 +46,9 @@ div.scrollable { .label { width: 35%; + @media (max-width: 767.98px) { + font-weight: bold; + } } .data { @@ -132,12 +135,6 @@ div.scrollable { text-align: left; } -.right-mobile { - @media (max-width: 450px) { - text-align: right; - } -} - .skeleton-loader { max-width: 200px; } @@ -151,3 +148,11 @@ div.scrollable { top: 600px; } } + +.small-button { + height: 20px; + transform: translateY(-20px); + font-size: 10px; + padding-top: 0; + padding-bottom: 0; +} \ No newline at end of file diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index dee4a9713..17b7fa029 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -19,6 +19,8 @@ export class PoolComponent implements OnInit { @Input() right: number | string = 45; @Input() left: number | string = 75; + gfg = true; + formatNumber = formatNumber; poolStats$: Observable; blocks$: Observable; From 0a57f57a93f5adbd613ed259e0ae03af24e365d8 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Fri, 25 Mar 2022 22:15:16 +0900 Subject: [PATCH 11/68] Disable angular tooltip animation globally --- .../src/app/components/app/app.component.ts | 11 +++- .../reward-stats/reward-stats.component.html | 58 +------------------ frontend/src/styles.scss | 4 -- 3 files changed, 11 insertions(+), 62 deletions(-) diff --git a/frontend/src/app/components/app/app.component.ts b/frontend/src/app/components/app/app.component.ts index 0cb6ef051..e060fae54 100644 --- a/frontend/src/app/components/app/app.component.ts +++ b/frontend/src/app/components/app/app.component.ts @@ -1,28 +1,33 @@ import { Location } from '@angular/common'; import { Component, HostListener, OnInit, Inject, LOCALE_ID, HostBinding } from '@angular/core'; import { Router, NavigationEnd } from '@angular/router'; -import { WebsocketService } from '../../services/websocket.service'; import { StateService } from 'src/app/services/state.service'; +import { NgbTooltipConfig } from '@ng-bootstrap/ng-bootstrap'; @Component({ selector: 'app-root', templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'] + styleUrls: ['./app.component.scss'], + providers: [NgbTooltipConfig] }) export class AppComponent implements OnInit { link: HTMLElement = document.getElementById('canonical'); constructor( public router: Router, - private websocketService: WebsocketService, private stateService: StateService, private location: Location, + tooltipConfig: NgbTooltipConfig, @Inject(LOCALE_ID) private locale: string, ) { if (this.locale.startsWith('ar') || this.locale.startsWith('fa') || this.locale.startsWith('he')) { this.dir = 'rtl'; this.class = 'rtl-layout'; } + + tooltipConfig.animation = false; + tooltipConfig.container = 'body'; + tooltipConfig.triggers = 'hover'; } @HostBinding('attr.dir') dir = 'ltr'; diff --git a/frontend/src/app/components/reward-stats/reward-stats.component.html b/frontend/src/app/components/reward-stats/reward-stats.component.html index 861921ca6..a42a2a132 100644 --- a/frontend/src/app/components/reward-stats/reward-stats.component.html +++ b/frontend/src/app/components/reward-stats/reward-stats.component.html @@ -2,7 +2,7 @@
Miners Reward
-
@@ -14,7 +14,7 @@
Reward Per Tx
-
{{ rewardStats.rewardPerTx | amountShortener }} @@ -27,7 +27,7 @@
Average Fee
-
{{ rewardStats.feePerTx | amountShortener }} sats/tx @@ -65,55 +65,3 @@
- - \ No newline at end of file diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index 54476206c..ae3971276 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -655,10 +655,6 @@ h1, h2, h3 { margin-top: 0.75rem !important; } -.tooltip-inner { - max-width: inherit; -} - .alert-mempool { color: #ffffff; background-color: #653b9c; From 3198feb46dede2a3058afbc8f8a3f406da0bb228 Mon Sep 17 00:00:00 2001 From: softsimon Date: Fri, 25 Mar 2022 21:52:00 +0400 Subject: [PATCH 12/68] Rearrange wallet providers on About page. --- frontend/src/app/components/about/about.component.html | 2 +- frontend/src/app/components/about/about.component.scss | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html index c03a3a00a..74203ab81 100644 --- a/frontend/src/app/components/about/about.component.html +++ b/frontend/src/app/components/about/about.component.html @@ -169,7 +169,7 @@
-
+

Self-Hosted Integrations

diff --git a/frontend/src/app/components/about/about.component.scss b/frontend/src/app/components/about/about.component.scss index 8b9466732..222c14944 100644 --- a/frontend/src/app/components/about/about.component.scss +++ b/frontend/src/app/components/about/about.component.scss @@ -43,6 +43,7 @@ .alliances, .enterprise-sponsor, .community-integrations-sponsor, + .selfhosted-integrations-sponsor, .maintainers { margin-top: 68px; margin-bottom: 68px; @@ -108,6 +109,7 @@ .contributors, .community-sponsor, .community-integrations-sponsor, + .selfhosted-integrations-sponsor, .maintainers { .wrapper { display: inline-block; @@ -181,3 +183,8 @@ .no-about-margin { height: 10px; } + +.community-integrations-sponsor { + max-width: 750px; + margin: auto; +} From 32792f4f748d0cdb8f23d25a61ebeac43ab465a6 Mon Sep 17 00:00:00 2001 From: softsimon Date: Sun, 27 Mar 2022 14:26:06 +0400 Subject: [PATCH 13/68] npm audit fix --- backend/package-lock.json | 24 +- frontend/package-lock.json | 1072 ++++++++++++++---------------------- 2 files changed, 418 insertions(+), 678 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index e427bd8ab..be9c47130 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -601,9 +601,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.14.7", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", - "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==", + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", "funding": [ { "type": "individual", @@ -902,9 +902,9 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "node_modules/mkdirp": { @@ -1980,9 +1980,9 @@ } }, "follow-redirects": { - "version": "1.14.7", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", - "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==" + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==" }, "forwarded": { "version": "0.1.2", @@ -2206,9 +2206,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "mkdirp": { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 59f2fb57d..fc01e783d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -3895,6 +3895,12 @@ "node": ">= 0.6.0" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.0.0.tgz", + "integrity": "sha512-2pTGuibAXJswAPJjaKisthqS/NOK5ypG4LYT6tEAV0S/mxW0zOIvYvGK0V8w8+SHxAm6vRMSjqSalFXeBAqs+Q==", + "dev": true + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -4416,12 +4422,6 @@ "node": ">=8.9" } }, - "node_modules/after": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", - "dev": true - }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -4670,12 +4670,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/arraybuffer.slice": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", - "dev": true - }, "node_modules/asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -5009,15 +5003,6 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, - "node_modules/base64-arraybuffer": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", - "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -5085,12 +5070,6 @@ "node": ">= 6" } }, - "node_modules/blob": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", - "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", - "dev": true - }, "node_modules/blob-util": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", @@ -5271,13 +5250,13 @@ } }, "node_modules/browser-sync": { - "version": "2.27.7", - "resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-2.27.7.tgz", - "integrity": "sha512-9ElnnA/u+s2Jd+IgY+2SImB+sAEIteHsMG0NR96m7Ph/wztpvJCUpyC2on1KqmG9iAp941j+5jfmd34tEguGbg==", + "version": "2.27.9", + "resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-2.27.9.tgz", + "integrity": "sha512-3zBtggcaZIeU9so4ja9yxk7/CZu9B3DOL6zkxFpzHCHsQmkGBPVXg61jItbeoa+WXgNLnr1sYES/2yQwyEZ2+w==", "dev": true, "dependencies": { - "browser-sync-client": "^2.27.7", - "browser-sync-ui": "^2.27.7", + "browser-sync-client": "^2.27.9", + "browser-sync-ui": "^2.27.9", "bs-recipes": "1.3.4", "bs-snippet-injector": "^2.0.1", "chokidar": "^3.5.1", @@ -5303,9 +5282,9 @@ "serve-index": "1.9.1", "serve-static": "1.13.2", "server-destroy": "1.0.1", - "socket.io": "2.4.0", + "socket.io": "^4.4.1", "ua-parser-js": "1.0.2", - "yargs": "^15.4.1" + "yargs": "^17.3.1" }, "bin": { "browser-sync": "dist/bin.js" @@ -5315,9 +5294,9 @@ } }, "node_modules/browser-sync-client": { - "version": "2.27.7", - "resolved": "https://registry.npmjs.org/browser-sync-client/-/browser-sync-client-2.27.7.tgz", - "integrity": "sha512-wKg9UP9a4sCIkBBAXUdbkdWFJzfSAQizGh+nC19W9y9zOo9s5jqeYRFUUbs7x5WKhjtspT+xetVp9AtBJ6BmWg==", + "version": "2.27.9", + "resolved": "https://registry.npmjs.org/browser-sync-client/-/browser-sync-client-2.27.9.tgz", + "integrity": "sha512-FHW8kydp7FXo6jnX3gXJCpHAHtWNLK0nx839nnK+boMfMI1n4KZd0+DmTxHBsHsF3OHud4V4jwoN8U5HExMIdQ==", "dev": true, "dependencies": { "etag": "1.8.1", @@ -5351,19 +5330,63 @@ } }, "node_modules/browser-sync-ui": { - "version": "2.27.7", - "resolved": "https://registry.npmjs.org/browser-sync-ui/-/browser-sync-ui-2.27.7.tgz", - "integrity": "sha512-Bt4OQpx9p18OIzk0KKyu7jqlvmjacasUlk8ARY3uuIyiFWSBiRgr2i6XY8dEMF14DtbooaEBOpHEu9VCYvMcCw==", + "version": "2.27.9", + "resolved": "https://registry.npmjs.org/browser-sync-ui/-/browser-sync-ui-2.27.9.tgz", + "integrity": "sha512-rsduR2bRIwFvM8CX6iY/Nu5aWub0WB9zfSYg9Le/RV5N5DEyxJYey0VxdfWCnzDOoelassTDzYQo+r0iJno3qw==", "dev": true, "dependencies": { "async-each-series": "0.1.1", "connect-history-api-fallback": "^1", "immutable": "^3", "server-destroy": "1.0.1", - "socket.io-client": "^2.4.0", + "socket.io-client": "^4.4.1", "stream-throttle": "^0.1.3" } }, + "node_modules/browser-sync/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/browser-sync/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/browser-sync/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/browser-sync/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/browser-sync/node_modules/fs-extra": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", @@ -5384,26 +5407,57 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/browser-sync/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "node_modules/browser-sync/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/browser-sync/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/browser-sync/node_modules/yargs": { + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.4.0.tgz", + "integrity": "sha512-WJudfrk81yWFSOkZYpAZx4Nt7V4xp7S/uJkX0CnxovMCt1wCE8LNftPpNuF9X/u9gN5nsD7ycYtRcDf2pL3UiA==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/browser-sync/node_modules/yargs-parser": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", + "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", + "dev": true, + "engines": { + "node": ">=12" } }, "node_modules/browser-unpack": { @@ -6248,24 +6302,12 @@ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" }, - "node_modules/component-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", - "dev": true - }, "node_modules/component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", "devOptional": true }, - "node_modules/component-inherit": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", - "dev": true - }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -7824,85 +7866,104 @@ } }, "node_modules/engine.io": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.5.0.tgz", - "integrity": "sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA==", - "dev": true, + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.3.tgz", + "integrity": "sha512-rqs60YwkvWTLLnfazqgZqLa/aKo+9cueVfEi/dZ8PyGyaf8TLOxj++4QMIgeG3Gn0AhrWiFXvghsoY9L9h25GA==", + "devOptional": true, "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.4.1", - "debug": "~4.1.0", - "engine.io-parser": "~2.2.0", - "ws": "~7.4.2" + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3" }, "engines": { - "node": ">=8.0.0" + "node": ">=10.0.0" } }, "node_modules/engine.io-client": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.2.tgz", - "integrity": "sha512-QEqIp+gJ/kMHeUun7f5Vv3bteRHppHH/FMBQX/esFj/fuYfjyUKWGMo3VCvIP/V8bE9KcjHmRZrhIz2Z9oNsDA==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.1.1.tgz", + "integrity": "sha512-V05mmDo4gjimYW+FGujoGmmmxRaDsrVr7AXA3ZIfa04MWM1jOfZfUwou0oNqhNwy/votUDvGDt4JA4QF4e0b4g==", "dev": true, "dependencies": { - "component-emitter": "~1.3.0", - "component-inherit": "0.0.3", - "debug": "~3.1.0", - "engine.io-parser": "~2.2.0", + "@socket.io/component-emitter": "~3.0.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.0", "has-cors": "1.1.0", - "indexof": "0.0.1", "parseqs": "0.0.6", "parseuri": "0.0.6", - "ws": "~7.4.2", - "xmlhttprequest-ssl": "~1.6.2", + "ws": "~8.2.3", + "xmlhttprequest-ssl": "~2.0.0", "yeast": "0.1.2" } }, - "node_modules/engine.io-client/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", "dev": true, - "dependencies": { - "ms": "2.0.0" + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/engine.io-client/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, "node_modules/engine.io-parser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.1.tgz", - "integrity": "sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==", - "dev": true, + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz", + "integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==", + "devOptional": true, "dependencies": { - "after": "0.8.2", - "arraybuffer.slice": "~0.0.7", - "base64-arraybuffer": "0.1.4", - "blob": "0.0.5", - "has-binary2": "~1.0.2" + "@socket.io/base64-arraybuffer": "~1.0.2" + }, + "engines": { + "node": ">=10.0.0" } }, "node_modules/engine.io/node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", - "dev": true, + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "devOptional": true, "engines": { "node": ">= 0.6" } }, - "node_modules/engine.io/node_modules/debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" + "node_modules/engine.io/node_modules/ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "devOptional": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/enhanced-resolve": { @@ -9506,21 +9567,6 @@ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" }, - "node_modules/has-binary2": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", - "dev": true, - "dependencies": { - "isarray": "2.0.1" - } - }, - "node_modules/has-binary2/node_modules/isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - }, "node_modules/has-cors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", @@ -9882,12 +9928,6 @@ "node": ">=8" } }, - "node_modules/indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", - "dev": true - }, "node_modules/infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", @@ -10935,48 +10975,6 @@ "ms": "2.0.0" } }, - "node_modules/karma/node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "devOptional": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/karma/node_modules/engine.io": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.3.tgz", - "integrity": "sha512-rqs60YwkvWTLLnfazqgZqLa/aKo+9cueVfEi/dZ8PyGyaf8TLOxj++4QMIgeG3Gn0AhrWiFXvghsoY9L9h25GA==", - "devOptional": true, - "dependencies": { - "@types/cookie": "^0.4.1", - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.0.3", - "ws": "~8.2.3" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/karma/node_modules/engine.io-parser": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz", - "integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==", - "devOptional": true, - "dependencies": { - "@socket.io/base64-arraybuffer": "~1.0.2" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/karma/node_modules/finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -11010,43 +11008,6 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "devOptional": true }, - "node_modules/karma/node_modules/socket.io": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.4.1.tgz", - "integrity": "sha512-s04vrBswdQBUmuWJuuNTmXUVJhP0cVky8bBDhdkf8y0Ptsu7fKU2LuLbts9g+pdmAdyMMn8F/9Mf1/wbtUN0fg==", - "devOptional": true, - "dependencies": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "debug": "~4.3.2", - "engine.io": "~6.1.0", - "socket.io-adapter": "~2.3.3", - "socket.io-parser": "~4.0.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/karma/node_modules/socket.io-adapter": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.3.tgz", - "integrity": "sha512-Qd/iwn3VskrpNO60BeRyCyr8ZWw9CPZyitW4AQwmRZ8zCiyDiL+znRnWX6tDHXnWn1sJrM1+b6Mn6wEDJJ4aYQ==", - "devOptional": true - }, - "node_modules/karma/node_modules/socket.io-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", - "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", - "devOptional": true, - "dependencies": { - "@types/component-emitter": "^1.2.10", - "component-emitter": "~1.3.0", - "debug": "~4.3.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/karma/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -11087,27 +11048,6 @@ "node": "*" } }, - "node_modules/karma/node_modules/ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "devOptional": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -12057,9 +11997,9 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "node_modules/minipass": { "version": "3.1.6", @@ -12482,9 +12422,9 @@ "optional": true }, "node_modules/node-forge": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz", - "integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz", + "integrity": "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==", "engines": { "node": ">= 6.13.0" } @@ -15125,115 +15065,70 @@ } }, "node_modules/socket.io": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.4.0.tgz", - "integrity": "sha512-9UPJ1UTvKayuQfVv2IQ3k7tCQC/fboDyIK62i99dAQIyHKaBsNdTpwHLgKJ6guRWxRtC9H+138UwpaGuQO9uWQ==", - "dev": true, + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.4.1.tgz", + "integrity": "sha512-s04vrBswdQBUmuWJuuNTmXUVJhP0cVky8bBDhdkf8y0Ptsu7fKU2LuLbts9g+pdmAdyMMn8F/9Mf1/wbtUN0fg==", + "devOptional": true, "dependencies": { - "debug": "~4.1.0", - "engine.io": "~3.5.0", - "has-binary2": "~1.0.2", - "socket.io-adapter": "~1.1.0", - "socket.io-client": "2.4.0", - "socket.io-parser": "~3.4.0" + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.1.0", + "socket.io-adapter": "~2.3.3", + "socket.io-parser": "~4.0.4" + }, + "engines": { + "node": ">=10.0.0" } }, "node_modules/socket.io-adapter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", - "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==", - "dev": true + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.3.tgz", + "integrity": "sha512-Qd/iwn3VskrpNO60BeRyCyr8ZWw9CPZyitW4AQwmRZ8zCiyDiL+znRnWX6tDHXnWn1sJrM1+b6Mn6wEDJJ4aYQ==", + "devOptional": true }, "node_modules/socket.io-client": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.4.0.tgz", - "integrity": "sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.4.1.tgz", + "integrity": "sha512-N5C/L5fLNha5Ojd7Yeb/puKcPWWcoB/A09fEjjNsg91EDVr5twk/OEyO6VT9dlLSUNY85NpW6KBhVMvaLKQ3vQ==", "dev": true, "dependencies": { - "backo2": "1.0.2", - "component-bind": "1.0.0", - "component-emitter": "~1.3.0", - "debug": "~3.1.0", - "engine.io-client": "~3.5.0", - "has-binary2": "~1.0.2", - "indexof": "0.0.1", - "parseqs": "0.0.6", + "@socket.io/component-emitter": "~3.0.0", + "backo2": "~1.0.2", + "debug": "~4.3.2", + "engine.io-client": "~6.1.1", "parseuri": "0.0.6", - "socket.io-parser": "~3.3.0", - "to-array": "0.1.4" + "socket.io-parser": "~4.1.1" + }, + "engines": { + "node": ">=10.0.0" } }, - "node_modules/socket.io-client/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/socket.io-client/node_modules/isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - }, - "node_modules/socket.io-client/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, "node_modules/socket.io-client/node_modules/socket.io-parser": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.2.tgz", - "integrity": "sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.1.2.tgz", + "integrity": "sha512-j3kk71QLJuyQ/hh5F/L2t1goqzdTL0gvDzuhTuNSwihfuFUrcSji0qFZmJJPtG6Rmug153eOPsUizeirf1IIog==", "dev": true, "dependencies": { - "component-emitter": "~1.3.0", - "debug": "~3.1.0", - "isarray": "2.0.1" + "@socket.io/component-emitter": "~3.0.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" } }, "node_modules/socket.io-parser": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz", - "integrity": "sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==", - "dev": true, + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", + "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", + "devOptional": true, "dependencies": { - "component-emitter": "1.2.1", - "debug": "~4.1.0", - "isarray": "2.0.1" - } - }, - "node_modules/socket.io-parser/node_modules/component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/socket.io-parser/node_modules/isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - }, - "node_modules/socket.io/node_modules/debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" + "@types/component-emitter": "^1.2.10", + "component-emitter": "~1.3.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" } }, "node_modules/sockjs": { @@ -15948,12 +15843,6 @@ "node": ">=0.6.0" } }, - "node_modules/to-array": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", - "dev": true - }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -17206,9 +17095,9 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" }, "node_modules/xmlhttprequest-ssl": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.2.tgz", - "integrity": "sha512-tYOaldF/0BLfKuoA39QMwD4j2m8lq4DIncqj1yuNELX4vz9+z/ieG/vwmctjJce+boFHXstqhWnHSxc4W8f4qg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", "dev": true, "engines": { "node": ">=0.4.0" @@ -20081,6 +19970,12 @@ "integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ==", "devOptional": true }, + "@socket.io/component-emitter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.0.0.tgz", + "integrity": "sha512-2pTGuibAXJswAPJjaKisthqS/NOK5ypG4LYT6tEAV0S/mxW0zOIvYvGK0V8w8+SHxAm6vRMSjqSalFXeBAqs+Q==", + "dev": true + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -20568,12 +20463,6 @@ "regex-parser": "^2.2.11" } }, - "after": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", - "dev": true - }, "agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -20780,12 +20669,6 @@ "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==" }, - "arraybuffer.slice": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", - "dev": true - }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -21055,12 +20938,6 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, - "base64-arraybuffer": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", - "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=", - "dev": true - }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -21118,12 +20995,6 @@ } } }, - "blob": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", - "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", - "dev": true - }, "blob-util": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", @@ -21290,13 +21161,13 @@ } }, "browser-sync": { - "version": "2.27.7", - "resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-2.27.7.tgz", - "integrity": "sha512-9ElnnA/u+s2Jd+IgY+2SImB+sAEIteHsMG0NR96m7Ph/wztpvJCUpyC2on1KqmG9iAp941j+5jfmd34tEguGbg==", + "version": "2.27.9", + "resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-2.27.9.tgz", + "integrity": "sha512-3zBtggcaZIeU9so4ja9yxk7/CZu9B3DOL6zkxFpzHCHsQmkGBPVXg61jItbeoa+WXgNLnr1sYES/2yQwyEZ2+w==", "dev": true, "requires": { - "browser-sync-client": "^2.27.7", - "browser-sync-ui": "^2.27.7", + "browser-sync-client": "^2.27.9", + "browser-sync-ui": "^2.27.9", "bs-recipes": "1.3.4", "bs-snippet-injector": "^2.0.1", "chokidar": "^3.5.1", @@ -21322,11 +21193,46 @@ "serve-index": "1.9.1", "serve-static": "1.13.2", "server-destroy": "1.0.1", - "socket.io": "2.4.0", + "socket.io": "^4.4.1", "ua-parser-js": "1.0.2", - "yargs": "^15.4.1" + "yargs": "^17.3.1" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "fs-extra": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", @@ -21347,31 +21253,50 @@ "graceful-fs": "^4.1.6" } }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.4.0.tgz", + "integrity": "sha512-WJudfrk81yWFSOkZYpAZx4Nt7V4xp7S/uJkX0CnxovMCt1wCE8LNftPpNuF9X/u9gN5nsD7ycYtRcDf2pL3UiA==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + } + }, + "yargs-parser": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", + "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", + "dev": true } } }, "browser-sync-client": { - "version": "2.27.7", - "resolved": "https://registry.npmjs.org/browser-sync-client/-/browser-sync-client-2.27.7.tgz", - "integrity": "sha512-wKg9UP9a4sCIkBBAXUdbkdWFJzfSAQizGh+nC19W9y9zOo9s5jqeYRFUUbs7x5WKhjtspT+xetVp9AtBJ6BmWg==", + "version": "2.27.9", + "resolved": "https://registry.npmjs.org/browser-sync-client/-/browser-sync-client-2.27.9.tgz", + "integrity": "sha512-FHW8kydp7FXo6jnX3gXJCpHAHtWNLK0nx839nnK+boMfMI1n4KZd0+DmTxHBsHsF3OHud4V4jwoN8U5HExMIdQ==", "dev": true, "requires": { "etag": "1.8.1", @@ -21398,16 +21323,16 @@ } }, "browser-sync-ui": { - "version": "2.27.7", - "resolved": "https://registry.npmjs.org/browser-sync-ui/-/browser-sync-ui-2.27.7.tgz", - "integrity": "sha512-Bt4OQpx9p18OIzk0KKyu7jqlvmjacasUlk8ARY3uuIyiFWSBiRgr2i6XY8dEMF14DtbooaEBOpHEu9VCYvMcCw==", + "version": "2.27.9", + "resolved": "https://registry.npmjs.org/browser-sync-ui/-/browser-sync-ui-2.27.9.tgz", + "integrity": "sha512-rsduR2bRIwFvM8CX6iY/Nu5aWub0WB9zfSYg9Le/RV5N5DEyxJYey0VxdfWCnzDOoelassTDzYQo+r0iJno3qw==", "dev": true, "requires": { "async-each-series": "0.1.1", "connect-history-api-fallback": "^1", "immutable": "^3", "server-destroy": "1.0.1", - "socket.io-client": "^2.4.0", + "socket.io-client": "^4.4.1", "stream-throttle": "^0.1.3" } }, @@ -22132,24 +22057,12 @@ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" }, - "component-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", - "dev": true - }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", "devOptional": true }, - "component-inherit": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", - "dev": true - }, "compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -23400,83 +23313,71 @@ } }, "engine.io": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.5.0.tgz", - "integrity": "sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA==", - "dev": true, + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.3.tgz", + "integrity": "sha512-rqs60YwkvWTLLnfazqgZqLa/aKo+9cueVfEi/dZ8PyGyaf8TLOxj++4QMIgeG3Gn0AhrWiFXvghsoY9L9h25GA==", + "devOptional": true, "requires": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.4.1", - "debug": "~4.1.0", - "engine.io-parser": "~2.2.0", - "ws": "~7.4.2" + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3" }, "dependencies": { "cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", - "dev": true + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "devOptional": true }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } + "ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "devOptional": true, + "requires": {} } } }, "engine.io-client": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.2.tgz", - "integrity": "sha512-QEqIp+gJ/kMHeUun7f5Vv3bteRHppHH/FMBQX/esFj/fuYfjyUKWGMo3VCvIP/V8bE9KcjHmRZrhIz2Z9oNsDA==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.1.1.tgz", + "integrity": "sha512-V05mmDo4gjimYW+FGujoGmmmxRaDsrVr7AXA3ZIfa04MWM1jOfZfUwou0oNqhNwy/votUDvGDt4JA4QF4e0b4g==", "dev": true, "requires": { - "component-emitter": "~1.3.0", - "component-inherit": "0.0.3", - "debug": "~3.1.0", - "engine.io-parser": "~2.2.0", + "@socket.io/component-emitter": "~3.0.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.0", "has-cors": "1.1.0", - "indexof": "0.0.1", "parseqs": "0.0.6", "parseuri": "0.0.6", - "ws": "~7.4.2", - "xmlhttprequest-ssl": "~1.6.2", + "ws": "~8.2.3", + "xmlhttprequest-ssl": "~2.0.0", "yeast": "0.1.2" }, "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "requires": {} } } }, "engine.io-parser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.1.tgz", - "integrity": "sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==", - "dev": true, + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz", + "integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==", + "devOptional": true, "requires": { - "after": "0.8.2", - "arraybuffer.slice": "~0.0.7", - "base64-arraybuffer": "0.1.4", - "blob": "0.0.5", - "has-binary2": "~1.0.2" + "@socket.io/base64-arraybuffer": "~1.0.2" } }, "enhanced-resolve": { @@ -24672,23 +24573,6 @@ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" }, - "has-binary2": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", - "dev": true, - "requires": { - "isarray": "2.0.1" - }, - "dependencies": { - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - } - } - }, "has-cors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", @@ -24979,12 +24863,6 @@ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", - "dev": true - }, "infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", @@ -25729,39 +25607,6 @@ } } }, - "cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "devOptional": true - }, - "engine.io": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.3.tgz", - "integrity": "sha512-rqs60YwkvWTLLnfazqgZqLa/aKo+9cueVfEi/dZ8PyGyaf8TLOxj++4QMIgeG3Gn0AhrWiFXvghsoY9L9h25GA==", - "devOptional": true, - "requires": { - "@types/cookie": "^0.4.1", - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.0.3", - "ws": "~8.2.3" - } - }, - "engine.io-parser": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz", - "integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==", - "devOptional": true, - "requires": { - "@socket.io/base64-arraybuffer": "~1.0.2" - } - }, "finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -25794,37 +25639,6 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "devOptional": true }, - "socket.io": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.4.1.tgz", - "integrity": "sha512-s04vrBswdQBUmuWJuuNTmXUVJhP0cVky8bBDhdkf8y0Ptsu7fKU2LuLbts9g+pdmAdyMMn8F/9Mf1/wbtUN0fg==", - "devOptional": true, - "requires": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "debug": "~4.3.2", - "engine.io": "~6.1.0", - "socket.io-adapter": "~2.3.3", - "socket.io-parser": "~4.0.4" - } - }, - "socket.io-adapter": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.3.tgz", - "integrity": "sha512-Qd/iwn3VskrpNO60BeRyCyr8ZWw9CPZyitW4AQwmRZ8zCiyDiL+znRnWX6tDHXnWn1sJrM1+b6Mn6wEDJJ4aYQ==", - "devOptional": true - }, - "socket.io-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", - "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", - "devOptional": true, - "requires": { - "@types/component-emitter": "^1.2.10", - "component-emitter": "~1.3.0", - "debug": "~4.3.1" - } - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -25845,13 +25659,6 @@ "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", "devOptional": true - }, - "ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "devOptional": true, - "requires": {} } } }, @@ -26645,9 +26452,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "minipass": { "version": "3.1.6", @@ -26996,9 +26803,9 @@ "optional": true }, "node-forge": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz", - "integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz", + "integrity": "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==" }, "node-gyp": { "version": "8.4.1", @@ -28998,121 +28805,60 @@ "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" }, "socket.io": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.4.0.tgz", - "integrity": "sha512-9UPJ1UTvKayuQfVv2IQ3k7tCQC/fboDyIK62i99dAQIyHKaBsNdTpwHLgKJ6guRWxRtC9H+138UwpaGuQO9uWQ==", - "dev": true, + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.4.1.tgz", + "integrity": "sha512-s04vrBswdQBUmuWJuuNTmXUVJhP0cVky8bBDhdkf8y0Ptsu7fKU2LuLbts9g+pdmAdyMMn8F/9Mf1/wbtUN0fg==", + "devOptional": true, "requires": { - "debug": "~4.1.0", - "engine.io": "~3.5.0", - "has-binary2": "~1.0.2", - "socket.io-adapter": "~1.1.0", - "socket.io-client": "2.4.0", - "socket.io-parser": "~3.4.0" - }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.1.0", + "socket.io-adapter": "~2.3.3", + "socket.io-parser": "~4.0.4" } }, "socket.io-adapter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", - "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==", - "dev": true + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.3.tgz", + "integrity": "sha512-Qd/iwn3VskrpNO60BeRyCyr8ZWw9CPZyitW4AQwmRZ8zCiyDiL+znRnWX6tDHXnWn1sJrM1+b6Mn6wEDJJ4aYQ==", + "devOptional": true }, "socket.io-client": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.4.0.tgz", - "integrity": "sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.4.1.tgz", + "integrity": "sha512-N5C/L5fLNha5Ojd7Yeb/puKcPWWcoB/A09fEjjNsg91EDVr5twk/OEyO6VT9dlLSUNY85NpW6KBhVMvaLKQ3vQ==", "dev": true, "requires": { - "backo2": "1.0.2", - "component-bind": "1.0.0", - "component-emitter": "~1.3.0", - "debug": "~3.1.0", - "engine.io-client": "~3.5.0", - "has-binary2": "~1.0.2", - "indexof": "0.0.1", - "parseqs": "0.0.6", + "@socket.io/component-emitter": "~3.0.0", + "backo2": "~1.0.2", + "debug": "~4.3.2", + "engine.io-client": "~6.1.1", "parseuri": "0.0.6", - "socket.io-parser": "~3.3.0", - "to-array": "0.1.4" + "socket.io-parser": "~4.1.1" }, "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, "socket.io-parser": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.2.tgz", - "integrity": "sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.1.2.tgz", + "integrity": "sha512-j3kk71QLJuyQ/hh5F/L2t1goqzdTL0gvDzuhTuNSwihfuFUrcSji0qFZmJJPtG6Rmug153eOPsUizeirf1IIog==", "dev": true, "requires": { - "component-emitter": "~1.3.0", - "debug": "~3.1.0", - "isarray": "2.0.1" + "@socket.io/component-emitter": "~3.0.0", + "debug": "~4.3.1" } } } }, "socket.io-parser": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz", - "integrity": "sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==", - "dev": true, + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", + "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", + "devOptional": true, "requires": { - "component-emitter": "1.2.1", - "debug": "~4.1.0", - "isarray": "2.0.1" - }, - "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - } + "@types/component-emitter": "^1.2.10", + "component-emitter": "~1.3.0", + "debug": "~4.3.1" } }, "sockjs": { @@ -29679,12 +29425,6 @@ "os-tmpdir": "~1.0.2" } }, - "to-array": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", - "dev": true - }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -30622,9 +30362,9 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" }, "xmlhttprequest-ssl": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.2.tgz", - "integrity": "sha512-tYOaldF/0BLfKuoA39QMwD4j2m8lq4DIncqj1yuNELX4vz9+z/ieG/vwmctjJce+boFHXstqhWnHSxc4W8f4qg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", "dev": true }, "xtend": { From 56656839b3ef6f04530ffdf938e0ebbd2cf9939c Mon Sep 17 00:00:00 2001 From: Antoni Spaanderman <56turtle56@gmail.com> Date: Sun, 27 Mar 2022 16:13:48 +0200 Subject: [PATCH 14/68] Detect more lightning scripts: - expired htlc - anchor - swept anchor Fix indentation what i messed up in 7565aa7 Add explanation --- .../address-labels.component.ts | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/frontend/src/app/components/address-labels/address-labels.component.ts b/frontend/src/app/components/address-labels/address-labels.component.ts index 75bbe7cba..2ce0ae5a3 100644 --- a/frontend/src/app/components/address-labels/address-labels.component.ts +++ b/frontend/src/app/components/address-labels/address-labels.component.ts @@ -46,19 +46,36 @@ export class AddressLabelsComponent implements OnInit { return; } + const topElement = this.vin.witness[this.vin.witness.length - 2]; // https://github.com/lightning/bolts/blob/master/03-transactions.md#commitment-transaction-outputs if (/^OP_IF OP_PUSHBYTES_33 \w{66} OP_ELSE OP_PUSHBYTES_(1 \w{2}|2 \w{4}) OP_CSV OP_DROP OP_PUSHBYTES_33 \w{66} OP_ENDIF OP_CHECKSIG$/.test(this.vin.inner_witnessscript_asm)) { - if (this.vin.witness[this.vin.witness.length - 2] == '01') { - this.lightning = 'Revoked Force Close'; - } else { - this.lightning = 'Force Close'; - } + if (topElement === '01') { + // top element is '01' to get in the revocation path + this.lightning = 'Revoked Force Close'; + } else { + // top element is '', this is a delayed to_local output + this.lightning = 'Force Close'; + } // https://github.com/lightning/bolts/blob/master/03-transactions.md#offered-htlc-outputs } else if (/^OP_DUP OP_HASH160 OP_PUSHBYTES_20 \w{40} OP_EQUAL OP_IF OP_CHECKSIG OP_ELSE OP_PUSHBYTES_33 \w{66} OP_SWAP OP_SIZE OP_PUSHBYTES_1 20 OP_EQUAL OP_NOTIF OP_DROP OP_PUSHNUM_2 OP_SWAP OP_PUSHBYTES_33 \w{66} OP_PUSHNUM_2 OP_CHECKMULTISIG OP_ELSE OP_HASH160 OP_PUSHBYTES_20 \w{40} OP_EQUALVERIFY OP_CHECKSIG OP_ENDIF (OP_PUSHNUM_1 OP_CHECKSEQUENCEVERIFY OP_DROP |)OP_ENDIF$/.test(this.vin.inner_witnessscript_asm)) { - if (this.vin.witness[this.vin.witness.length - 2].length == 66) { - this.lightning = 'Revoked HTLC'; + if (topElement.length === 66) { + // top element is a public key + this.lightning = 'Revoked HTLC'; + } else if (topElement) { + // top element is a preimage + this.lightning = 'HTLC'; } else { - this.lightning = 'HTLC'; + // top element is '' to get in the multisig path of the script + this.lightning = 'Expired HTLC'; + } + // https://github.com/lightning/bolts/blob/master/03-transactions.md#to_local_anchor-and-to_remote_anchor-output-option_anchors + } else if (/^OP_PUSHBYTES_33 \w{66} OP_CHECKSIG OP_IFDUP OP_NOTIF OP_PUSHNUM_16 OP_CSV OP_ENDIF$/.test(this.vin.inner_witnessscript_asm)) { + if (topElement) { + // top element is a signature + this.lightning = 'Anchor'; + } else { + // top element is '', it has been swept after 16 blocks + this.lightning = 'Swept Anchor'; } } @@ -77,19 +94,19 @@ export class AddressLabelsComponent implements OnInit { return; } const ops = script.split(' '); - if (ops.length < 3 || ops.pop() != 'OP_CHECKMULTISIG') { + if (ops.length < 3 || ops.pop() !== 'OP_CHECKMULTISIG') { return; } const opN = ops.pop(); if (!opN.startsWith('OP_PUSHNUM_')) { return; } - const n = parseInt(opN.match(/[0-9]+/)[0]); + const n = parseInt(opN.match(/[0-9]+/)[0], 10); if (ops.length < n * 2 + 1) { return; } // pop n public keys - for (var i = 0; i < n; i++) { + for (let i = 0; i < n; i++) { if (!/^0((2|3)\w{64}|4\w{128})$/.test(ops.pop())) { return; } @@ -101,7 +118,7 @@ export class AddressLabelsComponent implements OnInit { if (!opM.startsWith('OP_PUSHNUM_')) { return; } - const m = parseInt(opM.match(/[0-9]+/)[0]); + const m = parseInt(opM.match(/[0-9]+/)[0], 10); this.multisig = true; this.multisigM = m; From 5d9e8d0177d53ba2f77fc193630f4a9e614fa98d Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 29 Mar 2022 10:52:50 +0900 Subject: [PATCH 15/68] Fix pool page skeleton --- .../app/components/pool/pool.component.html | 77 +++++++++++++++---- .../app/components/pool/pool.component.scss | 7 +- 2 files changed, 66 insertions(+), 18 deletions(-) diff --git a/frontend/src/app/components/pool/pool.component.html b/frontend/src/app/components/pool/pool.component.html index 3ee144f64..9750e503c 100644 --- a/frontend/src/app/components/pool/pool.component.html +++ b/frontend/src/app/components/pool/pool.component.html @@ -35,14 +35,14 @@
- @@ -102,7 +102,7 @@ @@ -298,7 +298,7 @@ +
~
+
+
Mined Blocks +
Empty Blocks +
Addresses + {{ poolStats.pool.addresses[0] }}
- Show {{ poolStats.pool.addresses.length }} + Show all ({{ poolStats.pool.addresses.length }}) {{ poolStats.pool.addresses[0] | shortenString: 40 }} @@ -223,41 +223,88 @@
- - + + + + - - - + + + + + + + + + + + + +
Tags
Tags
Addresses -
+ + +
+ Tags +
Addresses +
+
~
+ Addresses +
+
+
+
+
- - - + + - - - + + + + + + + + + + + +
Mined Blocks + + +
Mined Blocks
Empty Blocks + +
+ Mined Blocks +
+
+
+
Empty Blocks
+ Blocks +
+
+
+
diff --git a/frontend/src/app/components/pool/pool.component.scss b/frontend/src/app/components/pool/pool.component.scss index cae0cc173..211469c1b 100644 --- a/frontend/src/app/components/pool/pool.component.scss +++ b/frontend/src/app/components/pool/pool.component.scss @@ -45,16 +45,17 @@ div.scrollable { } .label { - width: 35%; + width: 30%; @media (max-width: 767.98px) { font-weight: bold; } } .data { - text-align: left; + text-align: right; padding-left: 25%; - @media (max-width: 991px) { + @media (max-width: 992px) { + text-align: left; padding-left: 12px; } @media (max-width: 450px) { From cc27b963d3de9bbd26adb8807ffcc688ee930f03 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 29 Mar 2022 12:50:57 +0900 Subject: [PATCH 16/68] Use slug instead of id in mining blocks list component --- .../src/app/components/blocks-list/blocks-list.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.html b/frontend/src/app/components/blocks-list/blocks-list.component.html index c9017a2f3..9c2f964e2 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.html +++ b/frontend/src/app/components/blocks-list/blocks-list.component.html @@ -25,7 +25,7 @@
- + {{ block.extras.pool.name }} From 8fba45003339a76e4b3356e03f0a7d3c0b897827 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 29 Mar 2022 13:34:25 +0900 Subject: [PATCH 17/68] Use mining pool slug in block component --- .../app/components/miner/miner.component.html | 2 +- .../app/components/miner/miner.component.ts | 23 ++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/components/miner/miner.component.html b/frontend/src/app/components/miner/miner.component.html index 4a54fb4d0..f4798d07d 100644 --- a/frontend/src/app/components/miner/miner.component.html +++ b/frontend/src/app/components/miner/miner.component.html @@ -4,7 +4,7 @@ - {{ miner }} + {{ miner }} Unknown diff --git a/frontend/src/app/components/miner/miner.component.ts b/frontend/src/app/components/miner/miner.component.ts index c022526fb..babda3dad 100644 --- a/frontend/src/app/components/miner/miner.component.ts +++ b/frontend/src/app/components/miner/miner.component.ts @@ -1,6 +1,8 @@ import { Component, Input, OnChanges, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; import { AssetsService } from 'src/app/services/assets.service'; import { Transaction } from 'src/app/interfaces/electrs.interface'; +import { StateService } from 'src/app/services/state.service'; +import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe'; @Component({ selector: 'app-miner', @@ -13,11 +15,14 @@ export class MinerComponent implements OnChanges { miner = ''; title = ''; url = ''; + target = '_blank'; loading = true; constructor( private assetsService: AssetsService, private cd: ChangeDetectorRef, + public stateService: StateService, + private relativeUrlPipe: RelativeUrlPipe, ) { } ngOnChanges() { @@ -40,7 +45,13 @@ export class MinerComponent implements OnChanges { if (pools.payout_addresses[vout.scriptpubkey_address]) { this.miner = pools.payout_addresses[vout.scriptpubkey_address].name; this.title = $localize`:@@miner-identified-by-payout:Identified by payout address: '${vout.scriptpubkey_address}:PAYOUT_ADDRESS:'`; - this.url = pools.payout_addresses[vout.scriptpubkey_address].link; + const pool = pools.payout_addresses[vout.scriptpubkey_address]; + if (this.stateService.env.MINING_DASHBOARD && pools.slugs[pool.name] !== undefined) { + this.url = this.relativeUrlPipe.transform(`/mining/pool/${pools.slugs[pool.name]}`); + this.target = ''; + } else { + this.url = pool.link; + } break; } @@ -48,9 +59,15 @@ export class MinerComponent implements OnChanges { if (pools.coinbase_tags.hasOwnProperty(tag)) { const coinbaseAscii = this.hex2ascii(this.coinbaseTransaction.vin[0].scriptsig); if (coinbaseAscii.indexOf(tag) > -1) { - this.miner = pools.coinbase_tags[tag].name; + const pool = pools.coinbase_tags[tag]; + this.miner = pool.name; this.title = $localize`:@@miner-identified-by-coinbase:Identified by coinbase tag: '${tag}:TAG:'`; - this.url = pools.coinbase_tags[tag].link; + if (this.stateService.env.MINING_DASHBOARD && pools.slugs[pool.name] !== undefined) { + this.url = this.relativeUrlPipe.transform(`/mining/pool/${pools.slugs[pool.name]}`); + this.target = ''; + } else { + this.url = pool.link; + } break; } } From f51ea5b537439a81adef8e662f4651a75301e14f Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 29 Mar 2022 14:37:17 +0900 Subject: [PATCH 18/68] Fix query to insert unknown mining pool --- backend/src/api/pools-parser.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/api/pools-parser.ts b/backend/src/api/pools-parser.ts index 7243eb023..9629916e3 100644 --- a/backend/src/api/pools-parser.ts +++ b/backend/src/api/pools-parser.ts @@ -108,7 +108,7 @@ class PoolsParser { if (slug === undefined) { // Only keep alphanumerical - slug = poolNames[i].replace(/[^a-z0-9]/gi,'').toLowerCase(); + slug = poolNames[i].replace(/[^a-z0-9]/gi, '').toLowerCase(); logger.debug(`No slug found for '${poolNames[i]}', generating it => '${slug}'`); } @@ -180,7 +180,7 @@ class PoolsParser { const [rows]: any[] = await connection.query({ sql: 'SELECT name from pools where name="Unknown"', timeout: 120000 }); if (rows.length === 0) { await connection.query({ - sql: `INSERT INTO pools(name, link, regexes, addresses) + sql: `INSERT INTO pools(name, link, regexes, addresses, slug) VALUES("Unknown", "https://learnmeabitcoin.com/technical/coinbase-transaction", "[]", "[]", "unknown"); `}); } else { @@ -189,7 +189,7 @@ class PoolsParser { regexes='[]', addresses='[]', slug='unknown' WHERE name='Unknown' - `) + `); } } catch (e) { logger.err('Unable to insert "Unknown" mining pool'); From 7ab950d03c4d6a83a3021616ce4c15ccbaa8964d Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 29 Mar 2022 16:31:26 +0900 Subject: [PATCH 19/68] Add slug when we insert a mining pool for the first time --- backend/src/api/pools-parser.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/src/api/pools-parser.ts b/backend/src/api/pools-parser.ts index 9629916e3..005806c1d 100644 --- a/backend/src/api/pools-parser.ts +++ b/backend/src/api/pools-parser.ts @@ -135,10 +135,11 @@ class PoolsParser { logger.debug(`Update pools table now`); // Add new mining pools into the database - let queryAdd: string = 'INSERT INTO pools(name, link, regexes, addresses) VALUES '; + let queryAdd: string = 'INSERT INTO pools(name, link, regexes, addresses, slug) VALUES '; for (let i = 0; i < finalPoolDataAdd.length; ++i) { queryAdd += `('${finalPoolDataAdd[i].name}', '${finalPoolDataAdd[i].link}', - '${JSON.stringify(finalPoolDataAdd[i].regexes)}', '${JSON.stringify(finalPoolDataAdd[i].addresses)}'),`; + '${JSON.stringify(finalPoolDataAdd[i].regexes)}', '${JSON.stringify(finalPoolDataAdd[i].addresses)}', + ${JSON.stringify(finalPoolDataAdd[i].slug)}),`; } queryAdd = queryAdd.slice(0, -1) + ';'; From b465b7abba726d2f1095b19e5d92f85f0ad0267b Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 29 Mar 2022 18:20:00 +0900 Subject: [PATCH 20/68] Add data zoom on pool hashrate chart --- .../app/components/pool/pool.component.scss | 7 +++++ .../src/app/components/pool/pool.component.ts | 28 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/frontend/src/app/components/pool/pool.component.scss b/frontend/src/app/components/pool/pool.component.scss index 2a06de54a..3bf3745c8 100644 --- a/frontend/src/app/components/pool/pool.component.scss +++ b/frontend/src/app/components/pool/pool.component.scss @@ -31,6 +31,13 @@ } } +.chart { + margin-bottom: 20px; + @media (max-width: 768px) { + margin-bottom: 10px; + } +} + div.scrollable { width: 100%; height: 100%; diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index 023d3dcdb..8a887c85f 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -172,6 +172,34 @@ export class PoolComponent implements OnInit { }, }, ], + dataZoom: [{ + type: 'inside', + realtime: true, + zoomLock: true, + maxSpan: 100, + minSpan: 10, + moveOnMouseMove: false, + }, { + fillerColor: '#aaaaff15', + borderColor: '#ffffff88', + showDetail: false, + show: true, + type: 'slider', + brushSelect: false, + realtime: true, + bottom: 0, + left: 20, + right: 15, + selectedDataBackground: { + lineStyle: { + color: '#fff', + opacity: 0.45, + }, + areaStyle: { + opacity: 0, + }, + }, + }], }; } From b5ef148b822eadf6871b1c140a6cf8e6555587ef Mon Sep 17 00:00:00 2001 From: Antoni Spaanderman <56turtle56@gmail.com> Date: Tue, 29 Mar 2022 15:47:48 +0200 Subject: [PATCH 21/68] replace 3 seperate labels with one `AddressLabelsComponent.label?: string` + consistency: move comments in the `if` blocks --- .../address-labels.component.html | 16 +------- .../address-labels.component.ts | 40 ++++++++----------- 2 files changed, 18 insertions(+), 38 deletions(-) diff --git a/frontend/src/app/components/address-labels/address-labels.component.html b/frontend/src/app/components/address-labels/address-labels.component.html index 9abfe32da..2f673a8a9 100644 --- a/frontend/src/app/components/address-labels/address-labels.component.html +++ b/frontend/src/app/components/address-labels/address-labels.component.html @@ -1,17 +1,5 @@ multisig {{ multisigM }} of {{ multisigN }} - -Lightning {{ lightning }} - -Liquid {{ liquid }} +>{{ label }} diff --git a/frontend/src/app/components/address-labels/address-labels.component.ts b/frontend/src/app/components/address-labels/address-labels.component.ts index 2ce0ae5a3..4909c4f29 100644 --- a/frontend/src/app/components/address-labels/address-labels.component.ts +++ b/frontend/src/app/components/address-labels/address-labels.component.ts @@ -14,12 +14,7 @@ export class AddressLabelsComponent implements OnInit { @Input() vin: Vin; @Input() vout: Vout; - multisig = false; - multisigM: number; - multisigN: number; - - lightning = null; - liquid = null; + label?: string; constructor( stateService: StateService, @@ -39,47 +34,46 @@ export class AddressLabelsComponent implements OnInit { if (this.vin.inner_witnessscript_asm) { if (this.vin.inner_witnessscript_asm.indexOf('OP_DEPTH OP_PUSHNUM_12 OP_EQUAL OP_IF OP_PUSHNUM_11') === 0) { if (this.vin.witness.length > 11) { - this.liquid = 'Peg Out'; + this.label = 'Liquid Peg Out'; } else { - this.liquid = 'Emergency Peg Out'; + this.label = 'Emergency Liquid Peg Out'; } return; } const topElement = this.vin.witness[this.vin.witness.length - 2]; - // https://github.com/lightning/bolts/blob/master/03-transactions.md#commitment-transaction-outputs if (/^OP_IF OP_PUSHBYTES_33 \w{66} OP_ELSE OP_PUSHBYTES_(1 \w{2}|2 \w{4}) OP_CSV OP_DROP OP_PUSHBYTES_33 \w{66} OP_ENDIF OP_CHECKSIG$/.test(this.vin.inner_witnessscript_asm)) { + // https://github.com/lightning/bolts/blob/master/03-transactions.md#commitment-transaction-outputs if (topElement === '01') { // top element is '01' to get in the revocation path - this.lightning = 'Revoked Force Close'; + this.label = 'Revoked Lightning Force Close'; } else { // top element is '', this is a delayed to_local output - this.lightning = 'Force Close'; + this.label = 'Lightning Force Close'; } - // https://github.com/lightning/bolts/blob/master/03-transactions.md#offered-htlc-outputs + return; } else if (/^OP_DUP OP_HASH160 OP_PUSHBYTES_20 \w{40} OP_EQUAL OP_IF OP_CHECKSIG OP_ELSE OP_PUSHBYTES_33 \w{66} OP_SWAP OP_SIZE OP_PUSHBYTES_1 20 OP_EQUAL OP_NOTIF OP_DROP OP_PUSHNUM_2 OP_SWAP OP_PUSHBYTES_33 \w{66} OP_PUSHNUM_2 OP_CHECKMULTISIG OP_ELSE OP_HASH160 OP_PUSHBYTES_20 \w{40} OP_EQUALVERIFY OP_CHECKSIG OP_ENDIF (OP_PUSHNUM_1 OP_CHECKSEQUENCEVERIFY OP_DROP |)OP_ENDIF$/.test(this.vin.inner_witnessscript_asm)) { + // https://github.com/lightning/bolts/blob/master/03-transactions.md#offered-htlc-outputs if (topElement.length === 66) { // top element is a public key - this.lightning = 'Revoked HTLC'; + this.label = 'Revoked Lightning HTLC'; } else if (topElement) { // top element is a preimage - this.lightning = 'HTLC'; + this.label = 'Lightning HTLC'; } else { // top element is '' to get in the multisig path of the script - this.lightning = 'Expired HTLC'; + this.label = 'Expired Lightning HTLC'; } - // https://github.com/lightning/bolts/blob/master/03-transactions.md#to_local_anchor-and-to_remote_anchor-output-option_anchors + return; } else if (/^OP_PUSHBYTES_33 \w{66} OP_CHECKSIG OP_IFDUP OP_NOTIF OP_PUSHNUM_16 OP_CSV OP_ENDIF$/.test(this.vin.inner_witnessscript_asm)) { + // https://github.com/lightning/bolts/blob/master/03-transactions.md#to_local_anchor-and-to_remote_anchor-output-option_anchors if (topElement) { // top element is a signature - this.lightning = 'Anchor'; + this.label = 'Lightning Anchor'; } else { // top element is '', it has been swept after 16 blocks - this.lightning = 'Swept Anchor'; + this.label = 'Swept Lightning Anchor'; } - } - - if (this.lightning) { return; } @@ -120,9 +114,7 @@ export class AddressLabelsComponent implements OnInit { } const m = parseInt(opM.match(/[0-9]+/)[0], 10); - this.multisig = true; - this.multisigM = m; - this.multisigN = n; + this.label = `multisig ${m} of ${n}`; } handleVout() { From 493b44d4b9bb894134aa166b51834487b1f40cc8 Mon Sep 17 00:00:00 2001 From: Antoni Spaanderman <56turtle56@gmail.com> Date: Tue, 29 Mar 2022 16:16:20 +0200 Subject: [PATCH 22/68] detect bare multisigs with handleVout --- .../app/components/address-labels/address-labels.component.ts | 1 + .../transactions-list/transactions-list.component.html | 3 +++ 2 files changed, 4 insertions(+) diff --git a/frontend/src/app/components/address-labels/address-labels.component.ts b/frontend/src/app/components/address-labels/address-labels.component.ts index 4909c4f29..ee8e26de6 100644 --- a/frontend/src/app/components/address-labels/address-labels.component.ts +++ b/frontend/src/app/components/address-labels/address-labels.component.ts @@ -118,5 +118,6 @@ export class AddressLabelsComponent implements OnInit { } handleVout() { + this.detectMultisig(this.vout.scriptpubkey_asm); } } diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.html b/frontend/src/app/components/transactions-list/transactions-list.component.html index 3d9c67b17..72407a405 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.html +++ b/frontend/src/app/components/transactions-list/transactions-list.component.html @@ -155,6 +155,9 @@ {{ vout.scriptpubkey_address | shortenString : 16 }} {{ vout.scriptpubkey_address | shortenString : 35 }} +
+ +
Peg-out to From 1088655b1f442fc860875b862eb86ca8d6ced0f8 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Wed, 30 Mar 2022 01:06:42 +0900 Subject: [PATCH 23/68] Use slugs in cache warmer --- production/nginx-cache-warmer | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/production/nginx-cache-warmer b/production/nginx-cache-warmer index f9b89b7fa..a8fde4511 100755 --- a/production/nginx-cache-warmer +++ b/production/nginx-cache-warmer @@ -39,12 +39,11 @@ do for url in / \ curl -s "https://${hostname}${url}" >/dev/null done - counter=1 - while [ $counter -le 134 ] + slugs=$(cat pools.json | jq -r .slugs[]) + for slug in $slugs do - curl -s "https://${hostname}/api/v1/mining/pool/${counter}/hashrate" >/dev/null - curl -s "https://${hostname}/api/v1/mining/pool/${counter}" >/dev/null - ((counter++)) + curl -s "https://${hostname}/api/v1/mining/pool/${slug}/hashrate" >/dev/null + curl -s "https://${hostname}/api/v1/mining/pool/${slug}" >/dev/null done sleep 10 From 0561a207d93b1b4f3129540bf4414b722266657a Mon Sep 17 00:00:00 2001 From: wiz Date: Tue, 29 Mar 2022 11:25:25 -0500 Subject: [PATCH 24/68] Add jq to production deps in install script --- production/install | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/production/install b/production/install index e4d3f14b8..4db418693 100755 --- a/production/install +++ b/production/install @@ -321,7 +321,7 @@ LIQUIDTESTNET_ASSET_REGISTRY_DB_NAME=asset_registry_testnet_db # packages needed for mempool ecosystem DEBIAN_PKG=() DEBIAN_PKG+=(zsh vim curl screen openssl python3) -DEBIAN_PKG+=(build-essential git git-lfs clang cmake) +DEBIAN_PKG+=(build-essential git git-lfs clang cmake jq) DEBIAN_PKG+=(autotools-dev autoconf automake pkg-config bsdmainutils) DEBIAN_PKG+=(libevent-dev libdb-dev libssl-dev libtool-dev autotools-dev) DEBIAN_PKG+=(libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev) @@ -330,7 +330,7 @@ DEBIAN_PKG+=(nodejs npm mariadb-server nginx-core python-certbot-nginx rsync ufw # packages needed for mempool ecosystem FREEBSD_PKG=() FREEBSD_PKG+=(zsh sudo git screen curl wget calc neovim) -FREEBSD_PKG+=(openssh-portable py38-pip rust llvm90) +FREEBSD_PKG+=(openssh-portable py38-pip rust llvm90 jq) FREEBSD_PKG+=(boost-libs autoconf automake gmake gcc libevent libtool pkgconf) FREEBSD_PKG+=(nginx rsync py38-certbot-nginx mariadb105-server keybase) From a33d55829460c0225f0295e60509d08f6f6f00b5 Mon Sep 17 00:00:00 2001 From: wiz Date: Tue, 29 Mar 2022 11:34:49 -0500 Subject: [PATCH 25/68] Fix nginx cache warmer script for url slugs --- production/nginx-cache-warmer | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/production/nginx-cache-warmer b/production/nginx-cache-warmer index a8fde4511..7aa055778 100755 --- a/production/nginx-cache-warmer +++ b/production/nginx-cache-warmer @@ -1,5 +1,7 @@ #!/usr/bin/env zsh hostname=$(hostname) +slugs=(`curl -sSL https://raw.githubusercontent.com/mempool/mining-pools/master/pools.json | jq -r '.slugs[]'`) + while true do for url in / \ '/api/v1/statistics/2h' \ @@ -39,7 +41,6 @@ do for url in / \ curl -s "https://${hostname}${url}" >/dev/null done - slugs=$(cat pools.json | jq -r .slugs[]) for slug in $slugs do curl -s "https://${hostname}/api/v1/mining/pool/${slug}/hashrate" >/dev/null From b1749ee6b6226b65994a176aab99a335cf5a906f Mon Sep 17 00:00:00 2001 From: wiz Date: Tue, 29 Mar 2022 12:07:16 -0500 Subject: [PATCH 26/68] Enable nginx warm cache for all /api/v1/mining API endpoints --- production/nginx/location-api.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/production/nginx/location-api.conf b/production/nginx/location-api.conf index 0a40ddc36..253033206 100644 --- a/production/nginx/location-api.conf +++ b/production/nginx/location-api.conf @@ -1,7 +1,7 @@ location /api/v1/statistics { try_files /dev/null @mempool-api-v1-warmcache; } -location /api/v1/mining/pools { +location /api/v1/mining { try_files /dev/null @mempool-api-v1-warmcache; } location /api/v1 { From 1f20a56ae7ab58805a8f96985f3cae763282b8a9 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Wed, 30 Mar 2022 15:47:47 +0900 Subject: [PATCH 27/68] Show block reward on blockchain blocks for all Bitcoin networks --- .../components/blockchain-blocks/blockchain-blocks.component.ts | 2 +- .../app/components/mempool-blocks/mempool-blocks.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts index 4352944c6..4fd7d7ada 100644 --- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts +++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts @@ -51,7 +51,7 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy { } enabledMiningInfoIfNeeded(url) { - this.showMiningInfo = url === '/mining'; + this.showMiningInfo = url.indexOf('/mining') !== -1; this.cd.markForCheck(); // Need to update the view asap } diff --git a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts index db5f06b57..a4bd584f3 100644 --- a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts +++ b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts @@ -62,7 +62,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy { ) { } enabledMiningInfoIfNeeded(url) { - this.showMiningInfo = url === '/mining'; + this.showMiningInfo = url.indexOf('/mining') !== -1; this.cd.markForCheck(); // Need to update the view asap } From f3847e483d02f24aef1387cf7d316d6a3ee2483b Mon Sep 17 00:00:00 2001 From: nymkappa Date: Wed, 30 Mar 2022 16:11:18 +0900 Subject: [PATCH 28/68] Update pool detail page label - Fix no data text --- frontend/src/app/components/pool/pool.component.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/components/pool/pool.component.html b/frontend/src/app/components/pool/pool.component.html index 9750e503c..962a3ba9f 100644 --- a/frontend/src/app/components/pool/pool.component.html +++ b/frontend/src/app/components/pool/pool.component.html @@ -26,7 +26,7 @@
Tags -
+
{{ poolStats.pool.regexes }}
- Blocks + Empty Blocks
{{ formatNumber(poolStats.emptyBlocks, this.locale, '1.0-0') }}
- Blocks + Empty Blocks
From 9a4d3817c5b8d6fd57400c11018070de79c6a1a2 Mon Sep 17 00:00:00 2001 From: softsimon Date: Wed, 30 Mar 2022 11:12:55 +0400 Subject: [PATCH 29/68] Rounding bitcoin api satoshis fixes #1466 --- backend/src/api/bitcoin/bitcoin-api.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/api/bitcoin/bitcoin-api.ts b/backend/src/api/bitcoin/bitcoin-api.ts index 27b021af0..bbcb65211 100644 --- a/backend/src/api/bitcoin/bitcoin-api.ts +++ b/backend/src/api/bitcoin/bitcoin-api.ts @@ -25,7 +25,7 @@ class BitcoinApi implements AbstractBitcoinApi { .then((transaction: IBitcoinApi.Transaction) => { if (skipConversion) { transaction.vout.forEach((vout) => { - vout.value = vout.value * 100000000; + vout.value = Math.round(vout.value * 100000000); }); return transaction; } @@ -143,7 +143,7 @@ class BitcoinApi implements AbstractBitcoinApi { esploraTransaction.vout = transaction.vout.map((vout) => { return { - value: vout.value * 100000000, + value: Math.round(vout.value * 100000000), scriptpubkey: vout.scriptPubKey.hex, scriptpubkey_address: vout.scriptPubKey && vout.scriptPubKey.address ? vout.scriptPubKey.address : vout.scriptPubKey.addresses ? vout.scriptPubKey.addresses[0] : '', @@ -235,7 +235,7 @@ class BitcoinApi implements AbstractBitcoinApi { } else { mempoolEntry = await this.$getMempoolEntry(transaction.txid); } - transaction.fee = mempoolEntry.fees.base * 100000000; + transaction.fee = Math.round(mempoolEntry.fees.base * 100000000); return transaction; } From ccafe4a06632ddbbb31f72bd271588f013f700ee Mon Sep 17 00:00:00 2001 From: nymkappa Date: Wed, 30 Mar 2022 16:27:17 +0900 Subject: [PATCH 30/68] Send 404 when accessing non existing mining pool --- backend/src/repositories/PoolsRepository.ts | 7 ++++++- backend/src/routes.ts | 18 +++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/backend/src/repositories/PoolsRepository.ts b/backend/src/repositories/PoolsRepository.ts index d9defaaed..2b6dd5657 100644 --- a/backend/src/repositories/PoolsRepository.ts +++ b/backend/src/repositories/PoolsRepository.ts @@ -80,7 +80,7 @@ class PoolsRepository { /** * Get mining pool statistics for one pool */ - public async $getPool(slug: string): Promise { + public async $getPool(slug: string): Promise { const query = ` SELECT * FROM pools @@ -93,6 +93,11 @@ class PoolsRepository { const [rows] = await connection.query(query, [slug]); connection.release(); + if (rows.length < 1) { + logger.debug(`$getPool(): slug ${slug} does not match any known pool`); + return null; + } + rows[0].regexes = JSON.parse(rows[0].regexes); rows[0].addresses = JSON.parse(rows[0].addresses); diff --git a/backend/src/routes.ts b/backend/src/routes.ts index e98c718ef..af31d29ed 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -545,7 +545,11 @@ class Routes { res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); res.json(stats); } catch (e) { - res.status(500).send(e instanceof Error ? e.message : e); + if (e instanceof Error && e.message.indexOf('This mining pool does not exist') > -1) { + res.status(404).send(e.message); + } else { + res.status(500).send(e instanceof Error ? e.message : e); + } } } @@ -560,7 +564,11 @@ class Routes { res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); res.json(poolBlocks); } catch (e) { - res.status(500).send(e instanceof Error ? e.message : e); + if (e instanceof Error && e.message.indexOf('This mining pool does not exist') > -1) { + res.status(404).send(e.message); + } else { + res.status(500).send(e instanceof Error ? e.message : e); + } } } @@ -616,7 +624,11 @@ class Routes { hashrates: hashrates, }); } catch (e) { - res.status(500).send(e instanceof Error ? e.message : e); + if (e instanceof Error && e.message.indexOf('This mining pool does not exist') > -1) { + res.status(404).send(e.message); + } else { + res.status(500).send(e instanceof Error ? e.message : e); + } } } From 84394e13fa2039ba5e4f9aa6c89b347496673f26 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Wed, 30 Mar 2022 16:49:28 +0900 Subject: [PATCH 31/68] Return empty pool addresses on testnet and signet --- backend/src/repositories/PoolsRepository.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/src/repositories/PoolsRepository.ts b/backend/src/repositories/PoolsRepository.ts index d9defaaed..274f3c6e0 100644 --- a/backend/src/repositories/PoolsRepository.ts +++ b/backend/src/repositories/PoolsRepository.ts @@ -1,4 +1,5 @@ import { Common } from '../api/common'; +import config from '../config'; import { DB } from '../database'; import logger from '../logger'; import { PoolInfo, PoolTag } from '../mempool.interfaces'; @@ -94,7 +95,11 @@ class PoolsRepository { connection.release(); rows[0].regexes = JSON.parse(rows[0].regexes); - rows[0].addresses = JSON.parse(rows[0].addresses); + if (['testnet', 'signet'].includes(config.MEMPOOL.NETWORK)) { + rows[0].addresses = []; // pools.json only contains mainnet addresses + } else { + rows[0].addresses = JSON.parse(rows[0].addresses); + } return rows[0]; } catch (e) { From a20c401c83d937b9f5889fa0fc7dd9ab5d3b49d5 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Wed, 30 Mar 2022 18:43:01 +0900 Subject: [PATCH 32/68] Fix spam call to `/api/v1/mining/pool/{slug}` --- frontend/src/app/components/pool/pool.component.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index 15e4a41fa..4f600d22b 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -20,7 +20,7 @@ export class PoolComponent implements OnInit { @Input() left: number | string = 75; gfg = true; - + formatNumber = formatNumber; poolStats$: Observable; blocks$: Observable; @@ -56,12 +56,12 @@ export class PoolComponent implements OnInit { switchMap((data) => { this.isLoading = false; this.prepareChartOptions(data.hashrates.map(val => [val.timestamp * 1000, val.avgHashrate])); - return slug; + return [slug]; }), ); }), - switchMap(() => { - return this.apiService.getPoolStats$(this.slug); + switchMap((slug) => { + return this.apiService.getPoolStats$(slug); }), map((poolStats) => { let regexes = '"'; @@ -124,7 +124,7 @@ export class PoolComponent implements OnInit { align: 'left', }, borderColor: '#000', - formatter: function(ticks: any[]) { + formatter: function (ticks: any[]) { let hashratePowerOfTen: any = selectPowerOfTen(1); let hashrate = ticks[0].data[1]; @@ -192,14 +192,14 @@ export class PoolComponent implements OnInit { bottom: 0, left: 20, right: 15, - selectedDataBackground: { + selectedDataBackground: { lineStyle: { color: '#fff', opacity: 0.45, }, areaStyle: { opacity: 0, - }, + }, }, }], }; From 4b2698eee652b5f0a980f0fc187949f923edb629 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Wed, 30 Mar 2022 19:02:05 +0900 Subject: [PATCH 33/68] Use local block buffer to trigger pagination api call --- frontend/src/app/components/pool/pool.component.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index 4f600d22b..c41cb4971 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit } import { ActivatedRoute } from '@angular/router'; import { EChartsOption, graphic } from 'echarts'; import { BehaviorSubject, Observable, timer } from 'rxjs'; -import { map, switchMap, tap } from 'rxjs/operators'; +import { distinctUntilChanged, map, share, switchMap, tap } from 'rxjs/operators'; import { BlockExtended, PoolStat } from 'src/app/interfaces/node-api.interface'; import { ApiService } from 'src/app/services/api.service'; import { StateService } from 'src/app/services/state.service'; @@ -34,7 +34,7 @@ export class PoolComponent implements OnInit { blocks: BlockExtended[] = []; slug: string = undefined; - loadMoreSubject: BehaviorSubject = new BehaviorSubject(this.slug); + loadMoreSubject: BehaviorSubject = new BehaviorSubject(this.blocks[this.blocks.length - 1]?.height); constructor( @Inject(LOCALE_ID) public locale: string, @@ -50,7 +50,6 @@ export class PoolComponent implements OnInit { switchMap((slug: any) => { this.isLoading = true; this.slug = slug; - this.loadMoreSubject.next(this.slug); return this.apiService.getPoolHashrate$(this.slug) .pipe( switchMap((data) => { @@ -63,6 +62,9 @@ export class PoolComponent implements OnInit { switchMap((slug) => { return this.apiService.getPoolStats$(slug); }), + tap(() => { + this.loadMoreSubject.next(this.blocks[this.blocks.length - 1]?.height); + }), map((poolStats) => { let regexes = '"'; for (const regex of poolStats.pool.regexes) { @@ -79,6 +81,7 @@ export class PoolComponent implements OnInit { this.blocks$ = this.loadMoreSubject .pipe( + distinctUntilChanged(), switchMap((flag) => { if (this.slug === undefined) { return []; @@ -88,7 +91,8 @@ export class PoolComponent implements OnInit { tap((newBlocks) => { this.blocks = this.blocks.concat(newBlocks); }), - map(() => this.blocks) + map(() => this.blocks), + share(), ); } @@ -210,7 +214,7 @@ export class PoolComponent implements OnInit { } loadMore() { - this.loadMoreSubject.next(this.slug); + this.loadMoreSubject.next(this.blocks[this.blocks.length - 1]?.height); } trackByBlock(index: number, block: BlockExtended) { From 12ec6bbf6759a22fc9e45747ce8d3934297c91c4 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Wed, 30 Mar 2022 12:10:20 +0900 Subject: [PATCH 34/68] `/api/v1/mining/difficulty/{interval}` is not used --- backend/src/index.ts | 2 -- backend/src/routes.ts | 12 ------------ frontend/src/app/services/api.service.ts | 7 ------- 3 files changed, 21 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 62f9a8cd9..008d987eb 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -306,8 +306,6 @@ class Server { .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug/blocks/:height', routes.$getPoolBlocks) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug', routes.$getPool) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug/:interval', routes.$getPool) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty', routes.$getHistoricalDifficulty) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty/:interval', routes.$getHistoricalDifficulty) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate/pools', routes.$getPoolsHistoricalHashrate) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate/pools/:interval', routes.$getPoolsHistoricalHashrate) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate', routes.$getHistoricalHashrate) diff --git a/backend/src/routes.ts b/backend/src/routes.ts index e98c718ef..14942fd85 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -576,18 +576,6 @@ class Routes { } } - public async $getHistoricalDifficulty(req: Request, res: Response) { - try { - const stats = await BlocksRepository.$getBlocksDifficulty(req.params.interval ?? null); - res.header('Pragma', 'public'); - res.header('Cache-control', 'public'); - res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString()); - res.json(stats); - } catch (e) { - res.status(500).send(e instanceof Error ? e.message : e); - } - } - public async $getPoolsHistoricalHashrate(req: Request, res: Response) { try { const hashrates = await HashratesRepository.$getPoolsWeeklyHashrate(req.params.interval ?? null); diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index 7be8b944f..9efe9f782 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -154,13 +154,6 @@ export class ApiService { ); } - getHistoricalDifficulty$(interval: string | undefined): Observable { - return this.httpClient.get( - this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/difficulty` + - (interval !== undefined ? `/${interval}` : '') - ); - } - getHistoricalHashrate$(interval: string | undefined): Observable { return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/hashrate` + From d70e183236b2995b685fce70101be0fe101ba9b9 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Wed, 30 Mar 2022 19:52:06 +0900 Subject: [PATCH 35/68] Add missing endpoints to cache warmer --- production/nginx-cache-warmer | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/production/nginx-cache-warmer b/production/nginx-cache-warmer index 7aa055778..1720c3604 100755 --- a/production/nginx-cache-warmer +++ b/production/nginx-cache-warmer @@ -36,6 +36,7 @@ do for url in / \ '/api/v1/mining/hashrate/pools/3y' \ '/api/v1/mining/hashrate/pools/all' \ '/api/v1/mining/reward-stats/144' \ + '/api/v1/mining/blocks-extras' \ do curl -s "https://${hostname}${url}" >/dev/null @@ -43,8 +44,9 @@ do for url in / \ for slug in $slugs do - curl -s "https://${hostname}/api/v1/mining/pool/${slug}/hashrate" >/dev/null curl -s "https://${hostname}/api/v1/mining/pool/${slug}" >/dev/null + curl -s "https://${hostname}/api/v1/mining/pool/${slug}/hashrate" >/dev/null + curl -s "https://${hostname}/api/v1/mining/pool/${slug}/blocks" >/dev/null done sleep 10 From 68f3022420101bec78c4378c8ed73575ea380d99 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Wed, 30 Mar 2022 22:18:03 +0900 Subject: [PATCH 36/68] Don't use `slugs` if it's not available in pools.json frontend --- frontend/src/app/components/miner/miner.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/miner/miner.component.ts b/frontend/src/app/components/miner/miner.component.ts index babda3dad..dd3bc86d4 100644 --- a/frontend/src/app/components/miner/miner.component.ts +++ b/frontend/src/app/components/miner/miner.component.ts @@ -46,7 +46,7 @@ export class MinerComponent implements OnChanges { this.miner = pools.payout_addresses[vout.scriptpubkey_address].name; this.title = $localize`:@@miner-identified-by-payout:Identified by payout address: '${vout.scriptpubkey_address}:PAYOUT_ADDRESS:'`; const pool = pools.payout_addresses[vout.scriptpubkey_address]; - if (this.stateService.env.MINING_DASHBOARD && pools.slugs[pool.name] !== undefined) { + if (this.stateService.env.MINING_DASHBOARD && pools.slugs && pools.slugs[pool.name] !== undefined) { this.url = this.relativeUrlPipe.transform(`/mining/pool/${pools.slugs[pool.name]}`); this.target = ''; } else { @@ -62,7 +62,7 @@ export class MinerComponent implements OnChanges { const pool = pools.coinbase_tags[tag]; this.miner = pool.name; this.title = $localize`:@@miner-identified-by-coinbase:Identified by coinbase tag: '${tag}:TAG:'`; - if (this.stateService.env.MINING_DASHBOARD && pools.slugs[pool.name] !== undefined) { + if (this.stateService.env.MINING_DASHBOARD && pools.slugs && pools.slugs[pool.name] !== undefined) { this.url = this.relativeUrlPipe.transform(`/mining/pool/${pools.slugs[pool.name]}`); this.target = ''; } else { From 315b7593bf93508855b0343f06e157837a121c91 Mon Sep 17 00:00:00 2001 From: hunicus <93150691+hunicus@users.noreply.github.com> Date: Wed, 30 Mar 2022 09:44:41 -0400 Subject: [PATCH 37/68] Apply smooth scrolling to docs only --- frontend/src/app/components/docs/docs.component.ts | 5 +++++ frontend/src/styles.scss | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/components/docs/docs.component.ts b/frontend/src/app/components/docs/docs.component.ts index 605a453ab..7ef6cade6 100644 --- a/frontend/src/app/components/docs/docs.component.ts +++ b/frontend/src/app/components/docs/docs.component.ts @@ -23,5 +23,10 @@ export class DocsComponent implements OnInit { this.activeTab = ( url[2].path === "rest" ) ? 0 : 1; this.env = this.stateService.env; this.showWebSocketTab = ( ! ( ( this.env.BASE_MODULE === "bisq" ) || ( this.stateService.network === "bisq" ) || ( this.stateService.network === "liquidtestnet" ) ) ); + document.querySelector( "html" ).style.scrollBehavior = "smooth"; + } + + ngOnDestroy(): void { + document.querySelector( "html" ).style.scrollBehavior = "auto"; } } diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index 3c38b5557..09c885987 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -50,7 +50,6 @@ $dropdown-link-active-bg: #11131f; html, body { height: 100%; - scroll-behavior: smooth; } body { From 0b5cba15d64f3e046b2342081e9903a2e02e750d Mon Sep 17 00:00:00 2001 From: nymkappa Date: Thu, 31 Mar 2022 00:14:12 +0900 Subject: [PATCH 38/68] If mining dashboard is enabled, set block miner to "Unknown" by default --- frontend/src/app/components/miner/miner.component.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frontend/src/app/components/miner/miner.component.ts b/frontend/src/app/components/miner/miner.component.ts index dd3bc86d4..733204120 100644 --- a/frontend/src/app/components/miner/miner.component.ts +++ b/frontend/src/app/components/miner/miner.component.ts @@ -27,6 +27,11 @@ export class MinerComponent implements OnChanges { ngOnChanges() { this.miner = ''; + if (this.stateService.env.MINING_DASHBOARD) { + this.miner = 'Unknown'; + this.url = this.relativeUrlPipe.transform(`/mining/pool/unknown`); + this.target = ''; + } this.loading = true; this.findMinerFromCoinbase(); } From 2521661c69091021a16e924e7f459120fc27fbb5 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Thu, 31 Mar 2022 18:35:03 +0900 Subject: [PATCH 39/68] Remove unfiltered using input from log --- backend/src/repositories/PoolsRepository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/repositories/PoolsRepository.ts b/backend/src/repositories/PoolsRepository.ts index 2b6dd5657..9e66634b8 100644 --- a/backend/src/repositories/PoolsRepository.ts +++ b/backend/src/repositories/PoolsRepository.ts @@ -94,7 +94,7 @@ class PoolsRepository { connection.release(); if (rows.length < 1) { - logger.debug(`$getPool(): slug ${slug} does not match any known pool`); + logger.debug(`$getPool(): slug does not match any known pool`); return null; } From 52735553dd1a35d2616c8704b587d27fb8eb522d Mon Sep 17 00:00:00 2001 From: nymkappa Date: Fri, 1 Apr 2022 00:25:46 +0900 Subject: [PATCH 40/68] Subscribe to websocket blocks update for all graphs components --- frontend/src/app/components/graphs/graphs.component.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/graphs/graphs.component.ts b/frontend/src/app/components/graphs/graphs.component.ts index 58bf58db1..e172d2206 100644 --- a/frontend/src/app/components/graphs/graphs.component.ts +++ b/frontend/src/app/components/graphs/graphs.component.ts @@ -1,5 +1,6 @@ import { Component, OnInit } from "@angular/core"; import { StateService } from "src/app/services/state.service"; +import { WebsocketService } from "src/app/services/websocket.service"; @Component({ selector: 'app-graphs', @@ -7,9 +8,12 @@ import { StateService } from "src/app/services/state.service"; styleUrls: ['./graphs.component.scss'], }) export class GraphsComponent implements OnInit { - constructor(public stateService: StateService) { } + constructor( + public stateService: StateService, + private websocketService: WebsocketService + ) { } ngOnInit(): void { - + this.websocketService.want(['blocks']); } } From c3a3289fcfdb05ab19230eb3058c351449a89cc6 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Fri, 1 Apr 2022 12:41:25 +0900 Subject: [PATCH 41/68] Mining stats does not depends on the websocket blocks number anymore --- backend/src/repositories/BlocksRepository.ts | 10 ++-- .../reward-stats/reward-stats.component.ts | 46 ++++++++++++------- .../src/app/interfaces/node-api.interface.ts | 2 + 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 5b253d3a0..b426e77d2 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -386,10 +386,14 @@ class BlocksRepository { connection = await DB.getConnection(); // We need to use a subquery - const query = `SELECT SUM(reward) as totalReward, SUM(fees) as totalFee, SUM(tx_count) as totalTx - FROM (SELECT reward, fees, tx_count FROM blocks ORDER by height DESC LIMIT ${blockCount}) as sub`; + const query = ` + SELECT MIN(height) as startBlock, MAX(height) as endBlock, SUM(reward) as totalReward, SUM(fees) as totalFee, SUM(tx_count) as totalTx + FROM + (SELECT height, reward, fees, tx_count FROM blocks + ORDER by height DESC + LIMIT ?) as sub`; - const [rows]: any = await connection.query(query); + const [rows]: any = await connection.query(query, [blockCount]); connection.release(); return rows[0]; diff --git a/frontend/src/app/components/reward-stats/reward-stats.component.ts b/frontend/src/app/components/reward-stats/reward-stats.component.ts index dd466985e..582796fa1 100644 --- a/frontend/src/app/components/reward-stats/reward-stats.component.ts +++ b/frontend/src/app/components/reward-stats/reward-stats.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; -import { Observable } from 'rxjs'; -import { map, skip, switchMap } from 'rxjs/operators'; +import { concat, Observable } from 'rxjs'; +import { map, switchMap, tap } from 'rxjs/operators'; import { ApiService } from 'src/app/services/api.service'; import { StateService } from 'src/app/services/state.service'; @@ -12,25 +12,39 @@ import { StateService } from 'src/app/services/state.service'; }) export class RewardStatsComponent implements OnInit { public $rewardStats: Observable; + private lastBlockHeight: number; constructor(private apiService: ApiService, private stateService: StateService) { } ngOnInit(): void { - this.$rewardStats = this.stateService.blocks$ + this.$rewardStats = concat( + // We fetch the latest reward stats when the page load and + // wait for the API response before listening to websocket blocks + this.apiService.getRewardStats$() + .pipe( + tap((stats) => { + this.lastBlockHeight = stats.endBlock; + }) + ), + // Or when we receive a newer block, newer than the latest reward stats api call + this.stateService.blocks$ + .pipe( + switchMap((block) => { + if (block[0].height <= this.lastBlockHeight) { + return []; // Return an empty stream so the last pipe is not executed + } + this.lastBlockHeight = block[0].height; + return this.apiService.getRewardStats$(); + }) + ) + ) .pipe( - // (we always receives some blocks at start so only trigger for the last one) - skip(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT - 1), - switchMap(() => { - return this.apiService.getRewardStats$() - .pipe( - map((stats) => { - return { - totalReward: stats.totalReward, - rewardPerTx: stats.totalReward / stats.totalTx, - feePerTx: stats.totalFee / stats.totalTx, - }; - }) - ); + map((stats) => { + return { + totalReward: stats.totalReward, + rewardPerTx: stats.totalReward / stats.totalTx, + feePerTx: stats.totalFee / stats.totalTx, + }; }) ); } diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index 022e215d0..bcda5ff4c 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -119,6 +119,8 @@ export interface BlockExtended extends Block { } export interface RewardStats { + startBlock: number; + endBlock: number; totalReward: number; totalFee: number; totalTx: number; From 2b79d6c935a2bb87514e80e7cf73b46aac9a586d Mon Sep 17 00:00:00 2001 From: nymkappa Date: Thu, 31 Mar 2022 18:14:07 +0900 Subject: [PATCH 42/68] Fix some mobile UI issues on mining dashboard --- .../blocks-list/blocks-list.component.html | 6 ++-- .../blocks-list/blocks-list.component.scss | 7 ++-- ...ifficulty-adjustments-table.component.html | 2 +- ...ifficulty-adjustments-table.component.scss | 32 ++----------------- .../hashrate-chart.component.html | 6 ++-- .../hashrate-chart.component.scss | 20 +++--------- .../hashrate-chart.component.ts | 15 ++++----- .../hashrate-chart-pools.component.html | 2 +- .../hashrate-chart-pools.component.scss | 19 ++++------- .../hashrate-chart-pools.component.ts | 3 +- .../mining-dashboard.component.html | 10 +++--- .../pool-ranking/pool-ranking.component.html | 10 ++++-- .../pool-ranking/pool-ranking.component.scss | 13 ++------ .../pool-ranking/pool-ranking.component.ts | 28 +--------------- 14 files changed, 50 insertions(+), 123 deletions(-) diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.html b/frontend/src/app/components/blocks-list/blocks-list.component.html index 9c2f964e2..f052b7fd6 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.html +++ b/frontend/src/app/components/blocks-list/blocks-list.component.html @@ -26,7 +26,7 @@
- + @@ -96,4 +96,4 @@ - \ No newline at end of file + diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.scss b/frontend/src/app/components/blocks-list/blocks-list.component.scss index 354c403af..abf337821 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.scss +++ b/frontend/src/app/components/blocks-list/blocks-list.component.scss @@ -14,6 +14,10 @@ td { padding-top: 0.7rem !important; padding-bottom: 0.7rem !important; + @media (max-width: 376px) { + padding-top: 0.73rem !important; + padding-bottom: 0.73rem !important; + } } .clear-link { @@ -35,8 +39,7 @@ td { .pool.widget { width: 40%; padding-left: 30px; - @media (max-width: 576px) { - padding-left: 40px; + @media (max-width: 376px) { width: 60%; } } diff --git a/frontend/src/app/components/difficulty-adjustments-table/difficulty-adjustments-table.component.html b/frontend/src/app/components/difficulty-adjustments-table/difficulty-adjustments-table.component.html index 51872c932..787058d91 100644 --- a/frontend/src/app/components/difficulty-adjustments-table/difficulty-adjustments-table.component.html +++ b/frontend/src/app/components/difficulty-adjustments-table/difficulty-adjustments-table.component.html @@ -1,5 +1,5 @@
- +
diff --git a/frontend/src/app/components/difficulty-adjustments-table/difficulty-adjustments-table.component.scss b/frontend/src/app/components/difficulty-adjustments-table/difficulty-adjustments-table.component.scss index c4a81f804..a0d8e115e 100644 --- a/frontend/src/app/components/difficulty-adjustments-table/difficulty-adjustments-table.component.scss +++ b/frontend/src/app/components/difficulty-adjustments-table/difficulty-adjustments-table.component.scss @@ -1,4 +1,4 @@ -.latest-transactions { +.latest-adjustments { width: 100%; text-align: left; table-layout:fixed; @@ -7,34 +7,8 @@ } td { width: 25%; - } - .table-cell-satoshis { - display: none; - text-align: right; - @media (min-width: 576px) { - display: table-cell; + @media (max-width: 376px) { + padding: 0.85rem; } - @media (min-width: 768px) { - display: none; - } - @media (min-width: 1100px) { - display: table-cell; - } - } - .table-cell-fiat { - display: none; - text-align: right; - @media (min-width: 485px) { - display: table-cell; - } - @media (min-width: 768px) { - display: none; - } - @media (min-width: 992px) { - display: table-cell; - } - } - .table-cell-fees { - text-align: right; } } diff --git a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.html b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.html index e3d2f6213..4e9c66495 100644 --- a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.html +++ b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.html @@ -44,12 +44,12 @@ -
+
+
- + diff --git a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.scss b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.scss index 62903d4f4..54dbe5fad 100644 --- a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.scss +++ b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.scss @@ -20,12 +20,11 @@ .full-container { padding: 0px 15px; width: 100%; - height: calc(100% - 170px); + min-height: 500px; + height: calc(100% - 150px); @media (max-width: 992px) { - height: calc(100% - 220px); - }; - @media (max-width: 575px) { - height: calc(100% - 260px); + height: 100%; + padding-bottom: 100px; }; } @@ -93,17 +92,8 @@ } .item { width: 50%; - margin: 0px auto 10px; display: inline-block; - @media (min-width: 485px) { - margin: 0px auto 10px; - } - @media (min-width: 785px) { - margin: 0px auto 0px; - } - &:last-child { - margin: 0px auto 0px; - } + margin: 0px auto 20px; &:nth-child(2) { order: 2; @media (min-width: 485px) { diff --git a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts index c210017fa..fd2a52b5e 100644 --- a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts +++ b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts @@ -155,10 +155,10 @@ export class HashrateChartComponent implements OnInit { '#D81B60', ], grid: { - top: 30, + top: 20, + bottom: this.widget ? 30 : 70, right: this.right, left: this.left, - bottom: this.widget ? 30 : this.isMobile() ? 90 : 60, }, tooltip: { show: !this.isMobile() || !this.widget, @@ -174,7 +174,7 @@ export class HashrateChartComponent implements OnInit { align: 'left', }, borderColor: '#000', - formatter: function (ticks) { + formatter: (ticks) => { let hashrateString = ''; let difficultyString = ''; let hashratePowerOfTen: any = selectPowerOfTen(1); @@ -205,7 +205,7 @@ export class HashrateChartComponent implements OnInit { ${hashrateString}
${difficultyString} `; - }.bind(this) + } }, xAxis: data.hashrates.length === 0 ? undefined : { type: 'time', @@ -239,7 +239,7 @@ export class HashrateChartComponent implements OnInit { }, yAxis: data.hashrates.length === 0 ? undefined : [ { - min: function (value) { + min: (value) => { return value.min * 0.9; }, type: 'value', @@ -256,7 +256,7 @@ export class HashrateChartComponent implements OnInit { } }, { - min: function (value) { + min: (value) => { return value.min * 0.9; }, type: 'value', @@ -266,7 +266,7 @@ export class HashrateChartComponent implements OnInit { formatter: (val) => { const selectedPowerOfTen: any = selectPowerOfTen(val); const newVal = Math.round(val / selectedPowerOfTen.divider); - return `${newVal} ${selectedPowerOfTen.unit}` + return `${newVal} ${selectedPowerOfTen.unit}`; } }, splitLine: { @@ -310,7 +310,6 @@ export class HashrateChartComponent implements OnInit { type: 'slider', brushSelect: false, realtime: true, - bottom: this.isMobile() ? 30 : 0, left: 20, right: 15, selectedDataBackground: { diff --git a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.html b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.html index 32f6a7b25..1ee088c7e 100644 --- a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.html +++ b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.html @@ -1,6 +1,6 @@
-
+
Mining pools dominance
diff --git a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.scss b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.scss index 095d33583..e89c8f173 100644 --- a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.scss +++ b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.scss @@ -20,19 +20,18 @@ .full-container { padding: 0px 15px; width: 100%; - height: calc(100% - 140px); - @media (max-width: 991px) { - height: calc(100% - 190px); - }; - @media (max-width: 575px) { - height: calc(100% - 235px); + min-height: 500px; + height: calc(100% - 150px); + @media (max-width: 992px) { + height: 100%; + padding-bottom: 100px; }; } .chart { width: 100%; height: 100%; - padding-bottom: 25px; + padding-bottom: 20px; padding-right: 10px; @media (max-width: 992px) { padding-bottom: 25px; @@ -43,12 +42,6 @@ @media (max-width: 767px) { padding-bottom: 50px; } - @media (max-width: 629px) { - padding-bottom: 85px; - } - @media (max-width: 567px) { - padding-bottom: 85px; - } } .chart-widget { width: 100%; diff --git a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts index 264ceb7ea..abfa8f61d 100644 --- a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts +++ b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts @@ -155,7 +155,7 @@ export class HashrateChartPoolsComponent implements OnInit { grid: { right: this.right, left: this.left, - bottom: this.widget ? 30 : 60, + bottom: this.widget ? 30 : 70, top: this.widget || this.isMobile() ? 10 : 50, }, tooltip: { @@ -218,7 +218,6 @@ export class HashrateChartPoolsComponent implements OnInit { type: 'slider', brushSelect: false, realtime: true, - bottom: 0, left: 20, right: 15, selectedDataBackground: { diff --git a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html index 674d0bc44..3b32408c8 100644 --- a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html +++ b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html @@ -27,7 +27,7 @@
-
+
View more @@ -38,7 +38,7 @@
-
+
View more @@ -49,7 +49,7 @@
-
+
Latest blocks @@ -63,13 +63,13 @@
- - + \ No newline at end of file diff --git a/frontend/src/app/components/pool-ranking/pool-ranking.component.scss b/frontend/src/app/components/pool-ranking/pool-ranking.component.scss index 5dddabe80..2d253eef6 100644 --- a/frontend/src/app/components/pool-ranking/pool-ranking.component.scss +++ b/frontend/src/app/components/pool-ranking/pool-ranking.component.scss @@ -28,7 +28,7 @@ width: 100%; height: 100%; max-height: 270px; - @media (max-width: 767.98px) { + @media (max-width: 485px) { max-height: 200px; } } @@ -93,17 +93,8 @@ } .item { width: 50%; - margin: 0px auto 10px; display: inline-block; - @media (min-width: 485px) { - margin: 0px auto 10px; - } - @media (min-width: 785px) { - margin: 0px auto 0px; - } - &:last-child { - margin: 0px auto 0px; - } + margin: 0px auto 20px; &:nth-child(2) { order: 2; @media (min-width: 485px) { diff --git a/frontend/src/app/components/pool-ranking/pool-ranking.component.ts b/frontend/src/app/components/pool-ranking/pool-ranking.component.ts index 4be0f340d..236ac3b2d 100644 --- a/frontend/src/app/components/pool-ranking/pool-ranking.component.ts +++ b/frontend/src/app/components/pool-ranking/pool-ranking.component.ts @@ -201,30 +201,6 @@ export class PoolRankingComponent implements OnInit { } prepareChartOptions(miningStats) { - let network = this.stateService.network; - if (network === '') { - network = 'bitcoin'; - } - network = network.charAt(0).toUpperCase() + network.slice(1); - - let radius: any[] = ['20%', '80%']; - let top: number = 0; let height = undefined; - if (this.isMobile() && this.widget) { - top = -30; - height = 270; - radius = ['10%', '50%']; - } else if (this.isMobile() && !this.widget) { - top = -40; - height = 300; - radius = ['10%', '50%']; - } else if (this.widget) { - radius = ['15%', '60%']; - top = -20; - height = 330; - } else { - top = 0; - } - this.chartOptions = { animation: false, color: chartColors, @@ -237,11 +213,9 @@ export class PoolRankingComponent implements OnInit { series: [ { minShowLabelAngle: 3.6, - top: top, - height: height, name: 'Mining pool', type: 'pie', - radius: radius, + radius: ['20%', '80%'], data: this.generatePoolsChartSerieData(miningStats), labelLine: { lineStyle: { From 2ef2a34766a031c71b24d690b3d9be186b5827fa Mon Sep 17 00:00:00 2001 From: nymkappa Date: Sat, 2 Apr 2022 23:29:37 +0900 Subject: [PATCH 43/68] Avoid parralel hashrate indexing when initial query is too slow --- backend/src/api/mining.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/src/api/mining.ts b/backend/src/api/mining.ts index 7e15c85d0..a3246f36f 100644 --- a/backend/src/api/mining.ts +++ b/backend/src/api/mining.ts @@ -86,16 +86,17 @@ class Mining { return; } + this.weeklyHashrateIndexingStarted = true; + // We only run this once a week const latestTimestamp = await HashratesRepository.$getLatestRunTimestamp('last_weekly_hashrates_indexing') * 1000; const now = new Date(); if (now.getTime() - latestTimestamp < 604800000) { + this.weeklyHashrateIndexingStarted = false; return; } try { - this.weeklyHashrateIndexingStarted = true; - logger.info(`Indexing mining pools weekly hashrates`); const indexedTimestamp = await HashratesRepository.$getWeeklyHashrateTimestamps(); @@ -186,16 +187,17 @@ class Mining { return; } + this.hashrateIndexingStarted = true; + // We only run this once a day const latestTimestamp = await HashratesRepository.$getLatestRunTimestamp('last_hashrates_indexing') * 1000; const now = new Date().getTime(); if (now - latestTimestamp < 86400000) { + this.hashrateIndexingStarted = false; return; } try { - this.hashrateIndexingStarted = true; - logger.info(`Indexing network daily hashrate`); const indexedTimestamp = (await HashratesRepository.$getNetworkDailyHashrate(null)).map(hashrate => hashrate.timestamp); From f393cb08390b955d6edb2f1b3b80d16de93d0669 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Sat, 2 Apr 2022 23:44:07 +0900 Subject: [PATCH 44/68] Wrap initial query in try/catch to reset the flag upon error --- backend/src/api/mining.ts | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/backend/src/api/mining.ts b/backend/src/api/mining.ts index a3246f36f..db69b7621 100644 --- a/backend/src/api/mining.ts +++ b/backend/src/api/mining.ts @@ -86,14 +86,20 @@ class Mining { return; } - this.weeklyHashrateIndexingStarted = true; - - // We only run this once a week - const latestTimestamp = await HashratesRepository.$getLatestRunTimestamp('last_weekly_hashrates_indexing') * 1000; const now = new Date(); - if (now.getTime() - latestTimestamp < 604800000) { + + try { + this.weeklyHashrateIndexingStarted = true; + + // We only run this once a week + const latestTimestamp = await HashratesRepository.$getLatestRunTimestamp('last_weekly_hashrates_indexing') * 1000; + if (now.getTime() - latestTimestamp < 604800000) { + this.weeklyHashrateIndexingStarted = false; + return; + } + } catch (e) { this.weeklyHashrateIndexingStarted = false; - return; + throw e; } try { @@ -187,14 +193,20 @@ class Mining { return; } - this.hashrateIndexingStarted = true; - - // We only run this once a day - const latestTimestamp = await HashratesRepository.$getLatestRunTimestamp('last_hashrates_indexing') * 1000; const now = new Date().getTime(); - if (now - latestTimestamp < 86400000) { + + try { + this.hashrateIndexingStarted = true; + + // We only run this once a day + const latestTimestamp = await HashratesRepository.$getLatestRunTimestamp('last_hashrates_indexing') * 1000; + if (now - latestTimestamp < 86400000) { + this.hashrateIndexingStarted = false; + return; + } + } catch (e) { this.hashrateIndexingStarted = false; - return; + throw e; } try { From 3115dcbe521216600e404b61324628e36265f5df Mon Sep 17 00:00:00 2001 From: softsimon Date: Sun, 3 Apr 2022 12:03:58 +0400 Subject: [PATCH 45/68] Correcting wrong or missing op_codes display fixes #1210 --- backend/src/api/bitcoin/bitcoin-api.ts | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/backend/src/api/bitcoin/bitcoin-api.ts b/backend/src/api/bitcoin/bitcoin-api.ts index 27b021af0..061b3ced1 100644 --- a/backend/src/api/bitcoin/bitcoin-api.ts +++ b/backend/src/api/bitcoin/bitcoin-api.ts @@ -212,6 +212,7 @@ class BitcoinApi implements AbstractBitcoinApi { 'witness_v0_scripthash': 'v0_p2wsh', 'witness_v1_taproot': 'v1_p2tr', 'nonstandard': 'nonstandard', + 'multisig': 'multisig', 'nulldata': 'op_return' }; @@ -294,15 +295,30 @@ class BitcoinApi implements AbstractBitcoinApi { const b: string[] = []; a.forEach((chunk) => { if (chunk.substr(0, 3) === 'OP_') { - chunk = chunk.replace(/^OP_(\d+)/, 'OP_PUSHNUM_$1'); + chunk = chunk.replace(/^OP_(\d+)$/, 'OP_PUSHNUM_$1'); chunk = chunk.replace('OP_CHECKSEQUENCEVERIFY', 'OP_CSV'); + chunk = chunk.replace('OP_CHECKLOCKTIMEVERIFY', 'OP_CLTV'); b.push(chunk); } else { chunk = chunk.replace('[ALL]', '01'); if (chunk === '0') { b.push('OP_0'); + } else if (chunk.match(/^[^0]\d*$/)) { + const chunkInt = parseInt(chunk, 10); + if (chunkInt < 0) { + b.push('OP_PUSHNUM_NEG' + -chunkInt); + } else { + b.push('OP_PUSHNUM_' + chunk); + } } else { - b.push('OP_PUSHBYTES_' + Math.round(chunk.length / 2) + ' ' + chunk); + const dataLength = Math.round(chunk.length / 2); + if (dataLength > 255) { + b.push('OP_PUSHDATA2' + ' ' + chunk); + } else if (dataLength > 75) { + b.push('OP_PUSHDATA1' + ' ' + chunk); + } else { + b.push('OP_PUSHBYTES_' + dataLength + ' ' + chunk); + } } } }); From 4bd3030322342b45c0540791fa6de92ee1b5dc76 Mon Sep 17 00:00:00 2001 From: softsimon Date: Sun, 3 Apr 2022 15:22:35 +0400 Subject: [PATCH 46/68] Correcting op_code coloring --- .../src/app/shared/pipes/asm-styler/asm-styler.pipe.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/shared/pipes/asm-styler/asm-styler.pipe.ts b/frontend/src/app/shared/pipes/asm-styler/asm-styler.pipe.ts index 14626a1de..f6f1a59ad 100644 --- a/frontend/src/app/shared/pipes/asm-styler/asm-styler.pipe.ts +++ b/frontend/src/app/shared/pipes/asm-styler/asm-styler.pipe.ts @@ -264,6 +264,7 @@ export class AsmStylerPipe implements PipeTransform { case 'LESSTHAN': case 'GREATERTHAN': case 'LESSTHANOREQUAL': + case 'GREATERTHANOREQUAL': case 'MIN': case 'MAX': case 'WITHIN': @@ -279,12 +280,12 @@ export class AsmStylerPipe implements PipeTransform { case 'CHECKSIG': case 'CHECKSIGVERIFY': case 'CHECKMULTISIG': - case 'CHCEKMULTISIGVERIFY': + case 'CHECKMULTISIGVERIFY': style = 'crypto'; break; - case 'CHECKLOCKTIMEVERIFY': - case 'CHECKSEQUENCEVERIFY': + case 'CLTV': + case 'CSV': style = 'locktime'; break; From dfb5ba5c3609905277956364ad081a40a46d3d04 Mon Sep 17 00:00:00 2001 From: Antoni Spaanderman <56turtle56@gmail.com> Date: Sun, 3 Apr 2022 21:41:12 +0200 Subject: [PATCH 47/68] Completely rewrote convertScriptSigAsm it now gives identical output to esplora, tested with the following TXs (testnet): 88710a9a6751827490f260e307757543f533c0f18bcd6865794713d263d5f5a4 446b2aad074de94efa28a1e82d2e6016dcb8a8ca38aca1a5a8eac6ef54e56a2e 4cfc410092e9514c14f48b61e20d2d3baf540ae7e981a821dd8c05dd4b7cd591 4b55dde38173174ab09e5571ebffffca798ba11143d28b9770600ff376dc778a --- backend/src/api/bitcoin/bitcoin-api.ts | 85 ++++++++++++++++---------- 1 file changed, 54 insertions(+), 31 deletions(-) diff --git a/backend/src/api/bitcoin/bitcoin-api.ts b/backend/src/api/bitcoin/bitcoin-api.ts index 061b3ced1..ec786593e 100644 --- a/backend/src/api/bitcoin/bitcoin-api.ts +++ b/backend/src/api/bitcoin/bitcoin-api.ts @@ -147,7 +147,7 @@ class BitcoinApi implements AbstractBitcoinApi { scriptpubkey: vout.scriptPubKey.hex, scriptpubkey_address: vout.scriptPubKey && vout.scriptPubKey.address ? vout.scriptPubKey.address : vout.scriptPubKey.addresses ? vout.scriptPubKey.addresses[0] : '', - scriptpubkey_asm: vout.scriptPubKey.asm ? this.convertScriptSigAsm(vout.scriptPubKey.asm) : '', + scriptpubkey_asm: vout.scriptPubKey.asm ? this.convertScriptSigAsm(vout.scriptPubKey.hex) : '', scriptpubkey_type: this.translateScriptPubKeyType(vout.scriptPubKey.type), }; }); @@ -157,7 +157,7 @@ class BitcoinApi implements AbstractBitcoinApi { is_coinbase: !!vin.coinbase, prevout: null, scriptsig: vin.scriptSig && vin.scriptSig.hex || vin.coinbase || '', - scriptsig_asm: vin.scriptSig && this.convertScriptSigAsm(vin.scriptSig.asm) || '', + scriptsig_asm: vin.scriptSig && this.convertScriptSigAsm(vin.scriptSig.hex) || '', sequence: vin.sequence, txid: vin.txid || '', vout: vin.vout || 0, @@ -290,38 +290,61 @@ class BitcoinApi implements AbstractBitcoinApi { return transaction; } - private convertScriptSigAsm(str: string): string { - const a = str.split(' '); + private convertScriptSigAsm(hex: string): string { + const buf = Buffer.from(hex, 'hex'); + const b: string[] = []; - a.forEach((chunk) => { - if (chunk.substr(0, 3) === 'OP_') { - chunk = chunk.replace(/^OP_(\d+)$/, 'OP_PUSHNUM_$1'); - chunk = chunk.replace('OP_CHECKSEQUENCEVERIFY', 'OP_CSV'); - chunk = chunk.replace('OP_CHECKLOCKTIMEVERIFY', 'OP_CLTV'); - b.push(chunk); + + let i = 0; + while (i < buf.length) { + const op = buf[i]; + if (op >= 0x01 && op <= 0x4e) { + i++; + let push: number; + if (op === 0x4c) { + push = buf.readUInt8(i); + b.push('OP_PUSHDATA1'); + i += 1; + } else if (op === 0x4d) { + push = buf.readUInt16LE(i); + b.push('OP_PUSHDATA2'); + i += 2; + } else if (op === 0x4e) { + push = buf.readUInt32LE(i); + b.push('OP_PUSHDATA4'); + i += 4; + } else { + push = op; + b.push('OP_PUSHBYTES_' + push); + } + + const data = buf.slice(i, i + push); + if (data.length !== push) { + break; + } + + b.push(data.toString('hex')); + i += data.length; } else { - chunk = chunk.replace('[ALL]', '01'); - if (chunk === '0') { - b.push('OP_0'); - } else if (chunk.match(/^[^0]\d*$/)) { - const chunkInt = parseInt(chunk, 10); - if (chunkInt < 0) { - b.push('OP_PUSHNUM_NEG' + -chunkInt); + const opcode = bitcoinjs.script.toASM([ op ]); + if (opcode && op < 0xfd) { + if (opcode === 'OP_1NEGATE') { + b.push('OP_PUSHNUM_NEG1'); + } else if (/^OP_(\d+)$/.test(opcode) && opcode !== 'OP_0') { + b.push(opcode.replace(/^OP_(\d+)$/, 'OP_PUSHNUM_$1')); } else { - b.push('OP_PUSHNUM_' + chunk); + b.push(opcode + .replace('OP_CHECKSEQUENCEVERIFY', 'OP_CSV') + .replace('OP_CHECKLOCKTIMEVERIFY', 'OP_CLTV') + ); } } else { - const dataLength = Math.round(chunk.length / 2); - if (dataLength > 255) { - b.push('OP_PUSHDATA2' + ' ' + chunk); - } else if (dataLength > 75) { - b.push('OP_PUSHDATA1' + ' ' + chunk); - } else { - b.push('OP_PUSHBYTES_' + dataLength + ' ' + chunk); - } + b.push('OP_RETURN_' + op); } + i += 1; } - }); + } + return b.join(' '); } @@ -332,21 +355,21 @@ class BitcoinApi implements AbstractBitcoinApi { if (vin.prevout.scriptpubkey_type === 'p2sh') { const redeemScript = vin.scriptsig_asm.split(' ').reverse()[0]; - vin.inner_redeemscript_asm = this.convertScriptSigAsm(bitcoinjs.script.toASM(Buffer.from(redeemScript, 'hex'))); + vin.inner_redeemscript_asm = this.convertScriptSigAsm(redeemScript); if (vin.witness && vin.witness.length > 2) { const witnessScript = vin.witness[vin.witness.length - 1]; - vin.inner_witnessscript_asm = this.convertScriptSigAsm(bitcoinjs.script.toASM(Buffer.from(witnessScript, 'hex'))); + vin.inner_witnessscript_asm = this.convertScriptSigAsm(witnessScript); } } if (vin.prevout.scriptpubkey_type === 'v0_p2wsh' && vin.witness) { const witnessScript = vin.witness[vin.witness.length - 1]; - vin.inner_witnessscript_asm = this.convertScriptSigAsm(bitcoinjs.script.toASM(Buffer.from(witnessScript, 'hex'))); + vin.inner_witnessscript_asm = this.convertScriptSigAsm(witnessScript); } if (vin.prevout.scriptpubkey_type === 'v1_p2tr' && vin.witness && vin.witness.length > 1) { const witnessScript = vin.witness[vin.witness.length - 2]; - vin.inner_witnessscript_asm = this.convertScriptSigAsm(bitcoinjs.script.toASM(Buffer.from(witnessScript, 'hex'))); + vin.inner_witnessscript_asm = this.convertScriptSigAsm(witnessScript); } } From 096f2172c6a93473ce55e94f93368f281db4ad5e Mon Sep 17 00:00:00 2001 From: Antoni Spaanderman <56turtle56@gmail.com> Date: Sun, 3 Apr 2022 21:58:53 +0200 Subject: [PATCH 48/68] more direct opcode comparison --- backend/src/api/bitcoin/bitcoin-api.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/backend/src/api/bitcoin/bitcoin-api.ts b/backend/src/api/bitcoin/bitcoin-api.ts index ec786593e..17808d8bc 100644 --- a/backend/src/api/bitcoin/bitcoin-api.ts +++ b/backend/src/api/bitcoin/bitcoin-api.ts @@ -328,15 +328,16 @@ class BitcoinApi implements AbstractBitcoinApi { } else { const opcode = bitcoinjs.script.toASM([ op ]); if (opcode && op < 0xfd) { - if (opcode === 'OP_1NEGATE') { + if (op === 0x4f) { b.push('OP_PUSHNUM_NEG1'); - } else if (/^OP_(\d+)$/.test(opcode) && opcode !== 'OP_0') { + } else if (/^OP_(\d+)$/.test(opcode) && op !== 0x00) { b.push(opcode.replace(/^OP_(\d+)$/, 'OP_PUSHNUM_$1')); + } else if (op === 0xb1) { + b.push('OP_CLTV'); + } else if (op === 0xb2) { + b.push('OP_CSV'); } else { - b.push(opcode - .replace('OP_CHECKSEQUENCEVERIFY', 'OP_CSV') - .replace('OP_CHECKLOCKTIMEVERIFY', 'OP_CLTV') - ); + b.push(opcode); } } else { b.push('OP_RETURN_' + op); From 53d68a3571e09fa777b651a03eb476b01d71b06e Mon Sep 17 00:00:00 2001 From: Antoni Spaanderman <56turtle56@gmail.com> Date: Mon, 4 Apr 2022 17:16:34 +0200 Subject: [PATCH 49/68] name tapscript by its name + OP_CHECKSIGADD tapscript opcode detection --- backend/src/api/bitcoin/bitcoin-api.ts | 34 +++++++++++-------- .../transactions-list.component.html | 5 ++- .../pipes/asm-styler/asm-styler.pipe.ts | 1 + 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/backend/src/api/bitcoin/bitcoin-api.ts b/backend/src/api/bitcoin/bitcoin-api.ts index 17808d8bc..7b1fc161d 100644 --- a/backend/src/api/bitcoin/bitcoin-api.ts +++ b/backend/src/api/bitcoin/bitcoin-api.ts @@ -326,21 +326,27 @@ class BitcoinApi implements AbstractBitcoinApi { b.push(data.toString('hex')); i += data.length; } else { - const opcode = bitcoinjs.script.toASM([ op ]); - if (opcode && op < 0xfd) { - if (op === 0x4f) { - b.push('OP_PUSHNUM_NEG1'); - } else if (/^OP_(\d+)$/.test(opcode) && op !== 0x00) { - b.push(opcode.replace(/^OP_(\d+)$/, 'OP_PUSHNUM_$1')); - } else if (op === 0xb1) { - b.push('OP_CLTV'); - } else if (op === 0xb2) { - b.push('OP_CSV'); - } else { - b.push(opcode); - } + if (op === 0x00) { + b.push('OP_0'); + } else if (op === 0x4f) { + b.push('OP_PUSHNUM_NEG1'); + } else if (op === 0xb1) { + b.push('OP_CLTV'); + } else if (op === 0xb2) { + b.push('OP_CSV'); + } else if (op === 0xba) { + b.push('OP_CHECKSIGADD'); } else { - b.push('OP_RETURN_' + op); + const opcode = bitcoinjs.script.toASM([ op ]); + if (opcode && op < 0xfd) { + if (/^OP_(\d+)$/.test(opcode)) { + b.push(opcode.replace(/^OP_(\d+)$/, 'OP_PUSHNUM_$1')); + } else { + b.push(opcode); + } + } else { + b.push('OP_RETURN_' + op); + } } i += 1; } diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.html b/frontend/src/app/components/transactions-list/transactions-list.component.html index 72407a405..eded208bd 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.html +++ b/frontend/src/app/components/transactions-list/transactions-list.component.html @@ -111,7 +111,10 @@
- + + + + diff --git a/frontend/src/app/shared/pipes/asm-styler/asm-styler.pipe.ts b/frontend/src/app/shared/pipes/asm-styler/asm-styler.pipe.ts index f6f1a59ad..54a02e405 100644 --- a/frontend/src/app/shared/pipes/asm-styler/asm-styler.pipe.ts +++ b/frontend/src/app/shared/pipes/asm-styler/asm-styler.pipe.ts @@ -281,6 +281,7 @@ export class AsmStylerPipe implements PipeTransform { case 'CHECKSIGVERIFY': case 'CHECKMULTISIG': case 'CHECKMULTISIGVERIFY': + case 'CHECKSIGADD': style = 'crypto'; break; From c4db7ec5f684aded5a465b0eb34d6c3774dc652d Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 5 Apr 2022 00:36:00 +0900 Subject: [PATCH 50/68] Updated pool summary page to display more info on hashrate and blocks --- backend/src/api/mining.ts | 28 +- backend/src/repositories/BlocksRepository.ts | 17 - .../app/components/pool/pool.component.html | 393 +++++++++++++----- .../app/components/pool/pool.component.scss | 48 ++- .../src/app/components/pool/pool.component.ts | 23 +- .../src/app/interfaces/node-api.interface.ts | 15 +- .../app/shared/pipes/amount-shortener.pipe.ts | 12 +- 7 files changed, 387 insertions(+), 149 deletions(-) diff --git a/backend/src/api/mining.ts b/backend/src/api/mining.ts index db69b7621..482a34511 100644 --- a/backend/src/api/mining.ts +++ b/backend/src/api/mining.ts @@ -45,8 +45,8 @@ class Mining { const blockCount: number = await BlocksRepository.$blockCount(null, interval); poolsStatistics['blockCount'] = blockCount; - const blockHeightTip = await bitcoinClient.getBlockCount(); - const lastBlockHashrate = await bitcoinClient.getNetworkHashPs(144, blockHeightTip); + const totalBlock24h: number = await BlocksRepository.$blockCount(null, '24h'); + const lastBlockHashrate = await bitcoinClient.getNetworkHashPs(totalBlock24h); poolsStatistics['lastEstimatedHashrate'] = lastBlockHashrate; return poolsStatistics; @@ -62,12 +62,30 @@ class Mining { } const blockCount: number = await BlocksRepository.$blockCount(pool.id); - const emptyBlocksCount = await BlocksRepository.$countEmptyBlocks(pool.id); + const totalBlock: number = await BlocksRepository.$blockCount(null, null); + + const blockCount24h: number = await BlocksRepository.$blockCount(pool.id, '24h'); + const totalBlock24h: number = await BlocksRepository.$blockCount(null, '24h'); + + const blockCount1w: number = await BlocksRepository.$blockCount(pool.id, '1w'); + const totalBlock1w: number = await BlocksRepository.$blockCount(null, '1w'); + + const currentEstimatedkHashrate = await bitcoinClient.getNetworkHashPs(totalBlock24h); return { pool: pool, - blockCount: blockCount, - emptyBlocks: emptyBlocksCount.length > 0 ? emptyBlocksCount[0]['count'] : 0, + blockCount: { + 'all': blockCount, + '24h': blockCount24h, + '1w': blockCount1w, + }, + blockShare: { + 'all': blockCount / totalBlock, + '24h': blockCount24h / totalBlock24h, + '1w': blockCount1w / totalBlock1w, + }, + estimatedHashrate: currentEstimatedkHashrate * (blockCount24h / totalBlock24h), + reportedHashrate: null, }; } diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index b426e77d2..d7c0e501d 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -360,23 +360,6 @@ class BlocksRepository { } } - /** - * Return oldest blocks height - */ - public async $getOldestIndexedBlockHeight(): Promise { - const connection = await DB.getConnection(); - try { - const [rows]: any[] = await connection.query(`SELECT MIN(height) as minHeight FROM blocks`); - connection.release(); - - return rows[0].minHeight; - } catch (e) { - connection.release(); - logger.err('$getOldestIndexedBlockHeight() error' + (e instanceof Error ? e.message : e)); - throw e; - } - } - /** * Get general block stats */ diff --git a/frontend/src/app/components/pool/pool.component.html b/frontend/src/app/components/pool/pool.component.html index 962a3ba9f..04d87df74 100644 --- a/frontend/src/app/components/pool/pool.component.html +++ b/frontend/src/app/components/pool/pool.component.html @@ -1,5 +1,6 @@
+
-
-
Height
P2WSH witness scriptP2TR tapscriptP2WSH witness script
+
+
- + - - - - - + -
Tags - {{ poolStats.pool.regexes }} + +
{{ poolStats.pool.regexes }}
- Tags + Tags
{{ poolStats.pool.regexes }}
@@ -33,17 +33,17 @@
Addresses - - {{ poolStats.pool.addresses[0] }} - - Addresses + + + {{ poolStats.pool.addresses[0] }} +
@@ -77,105 +76,192 @@
-
+
- - - - + + + + - + - - - - - + + + + + + + + + - + -
Mined Blocks{{ formatNumber(poolStats.blockCount, this.locale, '1.0-0') }}
Hashrate (24h) + + + + + + + + + + + + + + + +
Estimated + Reported + Luck
{{ poolStats.estimatedHashrate | amountShortener : 1 : 'H/s' }}{{ poolStats.reportedHashrate | amountShortener : 1 : 'H/s' }}{{ formatNumber(poolStats.luck, this.locale, '1.2-2') }}%
+
- Mined Blocks -
{{ formatNumber(poolStats.blockCount, this.locale, '1.0-0') }}
+
+ Hashrate (24h) + + + + + + + + + + + + + + + +
Estimated + Reported + Luck
{{ poolStats.estimatedHashrate | amountShortener : 1 : 'H/s' }}{{ poolStats.reportedHashrate | amountShortener : 1 : 'H/s' }}{{ formatNumber(poolStats.luck, this.locale, '1.2-2') }}%
Empty Blocks{{ formatNumber(poolStats.emptyBlocks, this.locale, '1.0-0') }}~~
Mined Blocks + + + + + + + + + + + + + +
24h1wAll
{{ formatNumber(poolStats.blockCount['24h'], this.locale, '1.0-0') }} ({{ formatNumber(100 * poolStats.blockShare['24h'], this.locale, '1.0-0') }}%){{ formatNumber(poolStats.blockCount['1w'], this.locale, '1.0-0') }} ({{ formatNumber(100 * poolStats.blockShare['1w'], this.locale, '1.0-0') }}%){{ formatNumber(poolStats.blockCount['all'], this.locale, '1.0-0') }} ({{ formatNumber(100 * poolStats.blockShare['all'], this.locale, '1.0-0') }}%)
+
- Empty Blocks -
{{ formatNumber(poolStats.emptyBlocks, this.locale, '1.0-0') }}
+
+ Mined Blocks + + + + + + + + + + + + + +
24h1wAll
{{ formatNumber(poolStats.blockCount['24h'], this.locale, '1.0-0') }} ({{ formatNumber(100 * poolStats.blockShare['24h'], this.locale, '1.0-0') }}%){{ formatNumber(poolStats.blockCount['1w'], this.locale, '1.0-0') }} ({{ formatNumber(100 * poolStats.blockShare['1w'], this.locale, '1.0-0') }}%){{ formatNumber(poolStats.blockCount['all'], this.locale, '1.0-0') }} ({{ formatNumber(100 * poolStats.blockShare['all'], this.locale, '1.0-0') }}%)
+
-
~~
- - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -57,7 +61,7 @@ Addresses
@@ -147,9 +151,12 @@
- - - + + +
HeightTimestampMined - Coinbase Tag - RewardFeesTxsSize
- {{ block.height - }} - - ‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }} - - - - - {{ block.extras.coinbaseRaw | hex2ascii }} - - - - - - - {{ block.tx_count | number }} - -
-
-
-
-
HeightTimestampMined + Coinbase Tag + RewardFeesTxsSize
+ {{ block.height + }} + + ‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }} + + + + + {{ block.extras.coinbaseRaw | hex2ascii }} + + + + + + + {{ block.tx_count | number }} + +
+
+
+
+
HeightTimestampMined + Coinbase Tag + RewardFeesTxsSize
@@ -209,6 +295,7 @@ +
@@ -220,18 +307,18 @@
-
- + +
+
- + - - - + - - - - - -
Tags +
@@ -243,71 +330,149 @@
Addresses +
+
+
+
~
Addresses
+
+
+
-
+
- - - + + + - + - - - - + + + - + -
Mined Blocks
Hashrate (24h) -
+ + + + + + + + + + + + + +
Estimated + Reported + Luck
+
+
+
+
+
+
- Mined Blocks -
-
-
+
+ Hashrate (24h) + + + + + + + + + + + + + +
Estimated + Reported + Luck
+
+
+
+
+
+
Empty Blocks
Mined Blocks -
+ + + + + + + + + + + + + +
24h1wAll
+
+
+
+
+
+
- Empty Blocks -
-
-
+
+ Mined Blocks + + + + + + + + + + + + + +
24h1wAll
+
+
+
+
+
+
+
diff --git a/frontend/src/app/components/pool/pool.component.scss b/frontend/src/app/components/pool/pool.component.scss index 60bc4ab7d..14d5146b1 100644 --- a/frontend/src/app/components/pool/pool.component.scss +++ b/frontend/src/app/components/pool/pool.component.scss @@ -36,6 +36,7 @@ @media (max-width: 768px) { margin-bottom: 10px; } + height: 400px; } div.scrollable { @@ -52,15 +53,22 @@ div.scrollable { } .label { - width: 30%; + width: 25%; + @media (min-width: 767.98px) { + vertical-align: middle; + } @media (max-width: 767.98px) { font-weight: bold; } } +.label.addresses { + vertical-align: top; + padding-top: 25px; +} .data { text-align: right; - padding-left: 25%; + padding-left: 5%; @media (max-width: 992px) { text-align: left; padding-left: 12px; @@ -114,10 +122,6 @@ div.scrollable { } } -.fees { - width: 0%; -} - .size { width: 12%; @media (max-width: 1000px) { @@ -146,6 +150,10 @@ div.scrollable { .skeleton-loader { max-width: 200px; } +.skeleton-loader.data { + max-width: 70px; +} + .loadingGraphs { position: absolute; @@ -159,8 +167,34 @@ div.scrollable { .small-button { height: 20px; - transform: translateY(-20px); font-size: 10px; padding-top: 0; padding-bottom: 0; + transform: translateY(-20px); + @media (min-width: 767.98px) { + transform: translateY(-17px); + } +} + +.block-count-title { + color: #4a68b9; + font-size: 14px; + text-align: left; + @media (max-width: 767.98px) { + text-align: center; + } +} + +.table-data tr { + background-color: transparent; +} +.table-data td { + text-align: left; + @media (max-width: 767.98px) { + text-align: center; + } +} + +.taller-row { + height: 75px; } \ No newline at end of file diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index c41cb4971..764601a64 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -8,6 +8,7 @@ import { ApiService } from 'src/app/services/api.service'; import { StateService } from 'src/app/services/state.service'; import { selectPowerOfTen } from 'src/app/bitcoin.utils'; import { formatNumber } from '@angular/common'; +import { SeoService } from 'src/app/services/seo.service'; @Component({ selector: 'app-pool', @@ -41,6 +42,7 @@ export class PoolComponent implements OnInit { private apiService: ApiService, private route: ActivatedRoute, public stateService: StateService, + private seoService: SeoService, ) { } @@ -66,6 +68,7 @@ export class PoolComponent implements OnInit { this.loadMoreSubject.next(this.blocks[this.blocks.length - 1]?.height); }), map((poolStats) => { + this.seoService.setTitle(poolStats.pool.name); let regexes = '"'; for (const regex of poolStats.pool.regexes) { regexes += regex + '", "'; @@ -73,6 +76,10 @@ export class PoolComponent implements OnInit { poolStats.pool.regexes = regexes.slice(0, -3); poolStats.pool.addresses = poolStats.pool.addresses; + if (poolStats.reportedHashrate) { + poolStats.luck = poolStats.estimatedHashrate / poolStats.reportedHashrate * 100; + } + return Object.assign({ logo: `./resources/mining-pools/` + poolStats.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg' }, poolStats); @@ -97,7 +104,21 @@ export class PoolComponent implements OnInit { } prepareChartOptions(data) { + let title: object; + if (data.length === 0) { + title = { + textStyle: { + color: 'grey', + fontSize: 15 + }, + text: `No data`, + left: 'center', + top: 'center' + }; + } + this.chartOptions = { + title: title, animation: false, color: [ new graphic.LinearGradient(0, 0, 0, 0.65, [ @@ -178,7 +199,7 @@ export class PoolComponent implements OnInit { }, }, ], - dataZoom: [{ + dataZoom: data.length === 0 ? undefined : [{ type: 'inside', realtime: true, zoomLock: true, diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index bcda5ff4c..4998a0d70 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -93,8 +93,19 @@ export interface PoolInfo { } export interface PoolStat { pool: PoolInfo; - blockCount: number; - emptyBlocks: number; + blockCount: { + all: number, + '24h': number, + '1w': number, + }; + blockShare: { + all: number, + '24h': number, + '1w': number, + }; + estimatedHashrate: number; + reportedHashrate: number; + luck?: number; } export interface BlockExtension { diff --git a/frontend/src/app/shared/pipes/amount-shortener.pipe.ts b/frontend/src/app/shared/pipes/amount-shortener.pipe.ts index 319dc2a5a..a31a5712e 100644 --- a/frontend/src/app/shared/pipes/amount-shortener.pipe.ts +++ b/frontend/src/app/shared/pipes/amount-shortener.pipe.ts @@ -4,8 +4,9 @@ import { Pipe, PipeTransform } from '@angular/core'; name: 'amountShortener' }) export class AmountShortenerPipe implements PipeTransform { - transform(num: number, ...args: number[]): unknown { + transform(num: number, ...args: any[]): unknown { const digits = args[0] || 1; + const unit = args[1] || undefined; if (num < 1000) { return num.toFixed(digits); @@ -21,7 +22,12 @@ export class AmountShortenerPipe implements PipeTransform { { value: 1e18, symbol: 'E' } ]; const rx = /\.0+$|(\.[0-9]*[1-9])0+$/; - var item = lookup.slice().reverse().find((item) => num >= item.value); - return item ? (num / item.value).toFixed(digits).replace(rx, '$1') + item.symbol : '0'; + const item = lookup.slice().reverse().find((item) => num >= item.value); + + if (unit !== undefined) { + return item ? (num / item.value).toFixed(digits).replace(rx, '$1') + ' ' + item.symbol + unit : '0'; + } else { + return item ? (num / item.value).toFixed(digits).replace(rx, '$1') + item.symbol : '0'; + } } } \ No newline at end of file From c733782d04781aa76d4f836e57dff81c61b6e483 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 5 Apr 2022 01:57:45 +0900 Subject: [PATCH 51/68] Update AS142052 link --- .../app/components/privacy-policy/privacy-policy.component.html | 2 +- .../components/terms-of-service/terms-of-service.component.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/privacy-policy/privacy-policy.component.html b/frontend/src/app/components/privacy-policy/privacy-policy.component.html index de2ec69ba..7df3db8c1 100644 --- a/frontend/src/app/components/privacy-policy/privacy-policy.component.html +++ b/frontend/src/app/components/privacy-policy/privacy-policy.component.html @@ -11,7 +11,7 @@
-

The mempool.space website, the liquid.network website, the bisq.markets website, their associated API services, and related network and server infrastructure (collectively, the "Website") are operated by Mempool Space K.K. in Japan ("Mempool", "We", or "Us") and self-hosted from AS142052.

+

The mempool.space website, the liquid.network website, the bisq.markets website, their associated API services, and related network and server infrastructure (collectively, the "Website") are operated by Mempool Space K.K. in Japan ("Mempool", "We", or "Us") and self-hosted from AS142052.

This website and its API service (collectively, the "Website") are operated by a member of the Bitcoin community ("We" or "Us"). Mempool Space K.K. in Japan ("Mempool") has no affiliation with the operator of this Website, and does not sponsor or endorse the information provided herein.

diff --git a/frontend/src/app/components/terms-of-service/terms-of-service.component.html b/frontend/src/app/components/terms-of-service/terms-of-service.component.html index 44643c855..35a6413bd 100644 --- a/frontend/src/app/components/terms-of-service/terms-of-service.component.html +++ b/frontend/src/app/components/terms-of-service/terms-of-service.component.html @@ -11,7 +11,7 @@
-

The mempool.space website, the liquid.network website, the bisq.markets website, their associated API services, and related network and server infrastructure (collectively, the "Website") are operated by Mempool Space K.K. in Japan ("Mempool", "We", or "Us") and self-hosted from AS142052.

+

The mempool.space website, the liquid.network website, the bisq.markets website, their associated API services, and related network and server infrastructure (collectively, the "Website") are operated by Mempool Space K.K. in Japan ("Mempool", "We", or "Us") and self-hosted from AS142052.

This website and its API service (collectively, the "Website") are operated by a member of the Bitcoin community ("We" or "Us"). Mempool Space K.K. in Japan ("Mempool") has no affiliation with the operator of this Website, and does not sponsor or endorse the information provided herein.

From 0c3f9c895e35b4a3f6f2eeffe39a5b6ba21a49bd Mon Sep 17 00:00:00 2001 From: TechMiX Date: Tue, 5 Apr 2022 20:37:18 +0200 Subject: [PATCH 52/68] fix RTL layout issues --- .../components/address/address.component.html | 2 +- .../app/components/block/block.component.html | 2 +- .../src/app/components/docs/docs.component.ts | 4 +- .../hashrate-chart.component.ts | 4 +- .../hashrate-chart-pools.component.ts | 4 +- .../pool-ranking/pool-ranking.component.ts | 4 +- .../transaction/transaction.component.html | 2 +- frontend/src/styles.scss | 39 ++++++++++++++++++- 8 files changed, 52 insertions(+), 9 deletions(-) diff --git a/frontend/src/app/components/address/address.component.html b/frontend/src/app/components/address/address.component.html index 0c030f5de..0ac64f86d 100644 --- a/frontend/src/app/components/address/address.component.html +++ b/frontend/src/app/components/address/address.component.html @@ -55,7 +55,7 @@
-

+

  {{ (transactions?.length | number) || '?' }} of {{ txCount | number }} transaction {{ (transactions?.length | number) || '?' }} of {{ txCount | number }} transactions diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html index 8970bd372..8b511b30c 100644 --- a/frontend/src/app/components/block/block.component.html +++ b/frontend/src/app/components/block/block.component.html @@ -163,7 +163,7 @@

-

+

{{ i }} transaction {{ i }} transactions diff --git a/frontend/src/app/components/docs/docs.component.ts b/frontend/src/app/components/docs/docs.component.ts index 7ef6cade6..e2de9113d 100644 --- a/frontend/src/app/components/docs/docs.component.ts +++ b/frontend/src/app/components/docs/docs.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, HostBinding } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { Env, StateService } from 'src/app/services/state.service'; @@ -13,6 +13,8 @@ export class DocsComponent implements OnInit { env: Env; showWebSocketTab = true; + @HostBinding('attr.dir') dir = 'ltr'; + constructor( private route: ActivatedRoute, private stateService: StateService, diff --git a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts index fd2a52b5e..de62989bf 100644 --- a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts +++ b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, OnInit, HostBinding } from '@angular/core'; import { EChartsOption, graphic } from 'echarts'; import { Observable } from 'rxjs'; import { delay, map, retryWhen, share, startWith, switchMap, tap } from 'rxjs/operators'; @@ -35,6 +35,8 @@ export class HashrateChartComponent implements OnInit { renderer: 'svg', }; + @HostBinding('attr.dir') dir = 'ltr'; + hashrateObservable$: Observable; isLoading = true; formatNumber = formatNumber; diff --git a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts index abfa8f61d..d664650d0 100644 --- a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts +++ b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, OnInit, HostBinding } from '@angular/core'; import { EChartsOption } from 'echarts'; import { Observable } from 'rxjs'; import { delay, map, retryWhen, share, startWith, switchMap, tap } from 'rxjs/operators'; @@ -33,6 +33,8 @@ export class HashrateChartPoolsComponent implements OnInit { renderer: 'svg', }; + @HostBinding('attr.dir') dir = 'ltr'; + hashrateObservable$: Observable; isLoading = true; diff --git a/frontend/src/app/components/pool-ranking/pool-ranking.component.ts b/frontend/src/app/components/pool-ranking/pool-ranking.component.ts index 236ac3b2d..bf78266e0 100644 --- a/frontend/src/app/components/pool-ranking/pool-ranking.component.ts +++ b/frontend/src/app/components/pool-ranking/pool-ranking.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Input, NgZone, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, NgZone, OnInit, HostBinding } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; import { Router } from '@angular/router'; import { EChartsOption, PieSeriesOption } from 'echarts'; @@ -31,6 +31,8 @@ export class PoolRankingComponent implements OnInit { }; chartInstance: any = undefined; + @HostBinding('attr.dir') dir = 'ltr'; + miningStatsObservable$: Observable; constructor( diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index a0c92cbb4..1b6844cda 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -200,7 +200,7 @@ -
+

Details

diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index 09c885987..aee1456e4 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -698,6 +698,16 @@ th { margin-right: 0px; text-align: right; } + + .nav-pills { + @extend .nav-pills; + display: inline-block; + } + + .description { + direction: rtl; + } + .dropdown { margin-right: 1rem; margin-left: 0; @@ -712,12 +722,29 @@ th { left: 0px; right: auto; } - .fa-arrow-alt-circle-right { - @extend .fa-arrow-alt-circle-right; + .fa-circle-right { + @extend .fa-circle-right; -webkit-transform: scaleX(-1); transform: scaleX(-1); } + .btn.ml-2 { + margin-right: 0.5rem !important; + } + + .pool-name { + @extend .pool-name; + padding-right: 10px; + } + + .endpoint-container { + @extend .endpoint-container; + .section-header { + @extend .section-header; + text-align: left; + } + } + .table td { text-align: right; .fiat { @@ -809,6 +836,14 @@ th { } } + .full-container { + @extend .full-container; + .formRadioGroup { + @extend .formRadioGroup; + direction: ltr; + } + } + .mempool-graph { @extend .mempool-graph; direction: ltr; From 9a389cc9cdd4cda21af918a1e97ebd3143b5a869 Mon Sep 17 00:00:00 2001 From: TechMiX Date: Tue, 5 Apr 2022 21:26:17 +0200 Subject: [PATCH 53/68] add contributer signiture for TechMiX --- contributors/TechMiX.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 contributors/TechMiX.txt diff --git a/contributors/TechMiX.txt b/contributors/TechMiX.txt new file mode 100644 index 000000000..e6a382eae --- /dev/null +++ b/contributors/TechMiX.txt @@ -0,0 +1,3 @@ +I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of January 25, 2022. + +Signed: TechMiX From 6d876ad219860f5628e2883068ea0b832260ca63 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Wed, 6 Apr 2022 15:02:24 +0900 Subject: [PATCH 54/68] Update addresses button --- frontend/package.json | 2 +- .../app/components/pool/pool.component.html | 42 ++++++++++++------- .../app/components/pool/pool.component.scss | 7 ++++ 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 72aa8db69..5d1e56fbc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -28,7 +28,7 @@ "serve:stg": "npm run generate-config && ng serve -c staging", "serve:local-prod": "npm run generate-config && ng serve -c local-prod", "serve:local-staging": "npm run generate-config && ng serve -c local-staging", - "start": "npm run generate-config && npm run sync-assets-dev && ng serve -c local", + "start": "npm run generate-config && npm run sync-assets-dev && ng serve -c local --host 192.168.0.110", "start:stg": "npm run generate-config && npm run sync-assets-dev && ng serve -c staging", "start:local-prod": "npm run generate-config && npm run sync-assets-dev && ng serve -c local-prod", "start:local-staging": "npm run generate-config && npm run sync-assets-dev && ng serve -c local-staging", diff --git a/frontend/src/app/components/pool/pool.component.html b/frontend/src/app/components/pool/pool.component.html index 04d87df74..c51360a2d 100644 --- a/frontend/src/app/components/pool/pool.component.html +++ b/frontend/src/app/components/pool/pool.component.html @@ -36,18 +36,22 @@

Addresses - {{ poolStats.pool.addresses[0] }} -
- {{ - address }}
+
+ +
{{ formatNumber(poolStats.blockCount['24h'], this.locale, '1.0-0') }} ({{ formatNumber(100 * poolStats.blockShare['24h'], this.locale, '1.0-0') }}%){{ formatNumber(poolStats.blockCount['1w'], this.locale, '1.0-0') }} ({{ formatNumber(100 * poolStats.blockShare['1w'], this.locale, '1.0-0') }}%){{ formatNumber(poolStats.blockCount['all'], this.locale, '1.0-0') }} ({{ formatNumber(100 * poolStats.blockShare['all'], this.locale, '1.0-0') }}%){{ formatNumber(poolStats.blockCount['24h'], this.locale, '1.0-0') }} ({{ formatNumber(100 * + poolStats.blockShare['24h'], this.locale, '1.0-0') }}%){{ formatNumber(poolStats.blockCount['1w'], this.locale, '1.0-0') }} ({{ formatNumber(100 * + poolStats.blockShare['1w'], this.locale, '1.0-0') }}%){{ formatNumber(poolStats.blockCount['all'], this.locale, '1.0-0') }} ({{ formatNumber(100 * + poolStats.blockShare['all'], this.locale, '1.0-0') }}%)
@@ -167,9 +174,12 @@ - {{ formatNumber(poolStats.blockCount['24h'], this.locale, '1.0-0') }} ({{ formatNumber(100 * poolStats.blockShare['24h'], this.locale, '1.0-0') }}%) - {{ formatNumber(poolStats.blockCount['1w'], this.locale, '1.0-0') }} ({{ formatNumber(100 * poolStats.blockShare['1w'], this.locale, '1.0-0') }}%) - {{ formatNumber(poolStats.blockCount['all'], this.locale, '1.0-0') }} ({{ formatNumber(100 * poolStats.blockShare['all'], this.locale, '1.0-0') }}%) + {{ formatNumber(poolStats.blockCount['24h'], this.locale, '1.0-0') }} ({{ formatNumber(100 * + poolStats.blockShare['24h'], this.locale, '1.0-0') }}%) + {{ formatNumber(poolStats.blockCount['1w'], this.locale, '1.0-0') }} ({{ formatNumber(100 * + poolStats.blockShare['1w'], this.locale, '1.0-0') }}%) + {{ formatNumber(poolStats.blockCount['all'], this.locale, '1.0-0') }} ({{ formatNumber(100 * + poolStats.blockShare['all'], this.locale, '1.0-0') }}%) diff --git a/frontend/src/app/components/pool/pool.component.scss b/frontend/src/app/components/pool/pool.component.scss index 14d5146b1..9103f38f5 100644 --- a/frontend/src/app/components/pool/pool.component.scss +++ b/frontend/src/app/components/pool/pool.component.scss @@ -50,6 +50,9 @@ div.scrollable { .box { padding-bottom: 5px; + @media (min-width: 767.98px) { + min-height: 187px; + } } .label { @@ -170,6 +173,10 @@ div.scrollable { font-size: 10px; padding-top: 0; padding-bottom: 0; + outline: none; + box-shadow: none; +} +.small-button.mobile { transform: translateY(-20px); @media (min-width: 767.98px) { transform: translateY(-17px); From bc9063e490ca0a1f03ae5643e29bbdde4b47ec01 Mon Sep 17 00:00:00 2001 From: softsimon Date: Wed, 6 Apr 2022 15:40:26 +0400 Subject: [PATCH 55/68] Npm run start broke --- frontend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index 5d1e56fbc..72aa8db69 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -28,7 +28,7 @@ "serve:stg": "npm run generate-config && ng serve -c staging", "serve:local-prod": "npm run generate-config && ng serve -c local-prod", "serve:local-staging": "npm run generate-config && ng serve -c local-staging", - "start": "npm run generate-config && npm run sync-assets-dev && ng serve -c local --host 192.168.0.110", + "start": "npm run generate-config && npm run sync-assets-dev && ng serve -c local", "start:stg": "npm run generate-config && npm run sync-assets-dev && ng serve -c staging", "start:local-prod": "npm run generate-config && npm run sync-assets-dev && ng serve -c local-prod", "start:local-staging": "npm run generate-config && npm run sync-assets-dev && ng serve -c local-staging", From 1969f2a275e59183348200fb4bc78af7bc02d4ff Mon Sep 17 00:00:00 2001 From: nymkappa Date: Thu, 7 Apr 2022 14:37:16 +0900 Subject: [PATCH 56/68] Use github api to fetch and update the pools database, once a week --- backend/src/api/pools-parser.ts | 14 +-- backend/src/index.ts | 4 +- backend/src/tasks/pools-updater.ts | 139 +++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 15 deletions(-) create mode 100644 backend/src/tasks/pools-updater.ts diff --git a/backend/src/api/pools-parser.ts b/backend/src/api/pools-parser.ts index 005806c1d..dee95912a 100644 --- a/backend/src/api/pools-parser.ts +++ b/backend/src/api/pools-parser.ts @@ -17,23 +17,11 @@ class PoolsParser { /** * Parse the pools.json file, consolidate the data and dump it into the database */ - public async migratePoolsJson() { + public async migratePoolsJson(poolsJson: object) { if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === false) { return; } - logger.debug('Importing pools.json to the database, open ./pools.json'); - - let poolsJson: object = {}; - try { - const fileContent: string = readFileSync('./pools.json', 'utf8'); - poolsJson = JSON.parse(fileContent); - } catch (e) { - logger.err('Unable to open ./pools.json, does the file exist?'); - await this.insertUnknownPool(); - return; - } - // First we save every entries without paying attention to pool duplication const poolsDuplicated: Pool[] = []; diff --git a/backend/src/index.ts b/backend/src/index.ts index 008d987eb..20f6fb69c 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -22,12 +22,12 @@ import loadingIndicators from './api/loading-indicators'; import mempool from './api/mempool'; import elementsParser from './api/liquid/elements-parser'; import databaseMigration from './api/database-migration'; -import poolsParser from './api/pools-parser'; import syncAssets from './sync-assets'; import icons from './api/liquid/icons'; import { Common } from './api/common'; import mining from './api/mining'; import HashratesRepository from './repositories/HashratesRepository'; +import poolsUpdater from './tasks/pools-updater'; class Server { private wss: WebSocket.Server | undefined; @@ -99,7 +99,6 @@ class Server { await databaseMigration.$initializeOrMigrateDatabase(); if (Common.indexingEnabled()) { await this.$resetHashratesIndexingState(); - await poolsParser.migratePoolsJson(); } } catch (e) { throw new Error(e instanceof Error ? e.message : 'Error'); @@ -179,6 +178,7 @@ class Server { } try { + await poolsUpdater.updatePoolsJson(); blocks.$generateBlockDatabase(); await mining.$generateNetworkHashrateHistory(); await mining.$generatePoolHashrateHistory(); diff --git a/backend/src/tasks/pools-updater.ts b/backend/src/tasks/pools-updater.ts new file mode 100644 index 000000000..a70e8cb5d --- /dev/null +++ b/backend/src/tasks/pools-updater.ts @@ -0,0 +1,139 @@ +const https = require('https'); +import poolsParser from "../api/pools-parser"; +import config from "../config"; +import { DB } from "../database"; +import logger from "../logger"; + +/** + * Maintain the most recent version of pools.json + */ +class PoolsUpdater { + lastRun: number = 0; + + constructor() { + } + + public async updatePoolsJson() { + if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === false) { + return; + } + + const now = new Date().getTime() / 1000; + if (now - this.lastRun < 604800) { // Execute the PoolsUpdate only once a week, or upon restart + return; + } + + this.lastRun = now; + + try { + const dbSha = await this.getShaFromDb(); + const githubSha = await this.fetchPoolsSha(); // Fetch pools.json sha from github + if (githubSha === undefined) { + return; + } + + logger.debug(`Pools.json sha | Current: ${dbSha} | Github: ${githubSha}`); + if (dbSha !== undefined && dbSha === githubSha) { + return; + } + + logger.warn('Pools.json is outdated, fetch latest from github'); + const poolsJson = await this.fetchPools(); + await poolsParser.migratePoolsJson(poolsJson); + await this.updateDBSha(githubSha); + logger.notice('PoolsUpdater completed'); + + } catch (e) { + logger.err('PoolsUpdater failed. Error: ' + e); + } + } + + /** + * Fetch pools.json from github repo + */ + private async fetchPools(): Promise { + const response = await this.query('/repos/mempool/mining-pools/contents/pools.json'); + return JSON.parse(Buffer.from(response['content'], 'base64').toString('utf8')); + } + + /** + * Fetch our latest pools.json sha from the db + */ + private async updateDBSha(githubSha: string) { + let connection; + try { + connection = await DB.getConnection(); + await connection.query('DELETE FROM state where name="pools_json_sha"'); + await connection.query(`INSERT INTO state VALUES('pools_json_sha', NULL, '${githubSha}')`); + connection.release(); + } catch (e) { + logger.err('Unable save github pools.json sha into the DB, error: ' + e); + connection.release(); + return undefined; + } + } + + /** + * Fetch our latest pools.json sha from the db + */ + private async getShaFromDb(): Promise { + let connection; + try { + connection = await DB.getConnection(); + const [rows] = await connection.query('SELECT string FROM state WHERE name="pools_json_sha"'); + connection.release(); + return (rows.length > 0 ? rows[0].string : undefined); + } catch (e) { + logger.err('Unable fetch pools.json sha from DB, error: ' + e); + connection.release(); + return undefined; + } + } + + /** + * Fetch our latest pools.json sha from github + */ + private async fetchPoolsSha(): Promise { + const response = await this.query('/repos/mempool/mining-pools/git/trees/master'); + + for (const file of response['tree']) { + if (file['path'] === 'pools.json') { + return file['sha']; + } + } + + logger.err('Unable to find latest pools.json sha from github'); + return undefined; + } + + /** + * Http request wrapper + */ + private async query(path): Promise { + return new Promise((resolve, reject) => { + const options = { + host: 'api.github.com', + path: path, + method: 'GET', + headers: { 'user-agent': 'node.js' } + }; + + logger.debug('Querying: api.github.com' + path); + + https.get(options, (response) => { + const chunks_of_data: any[] = []; + response.on('data', (fragments) => { + chunks_of_data.push(fragments); + }); + response.on('end', () => { + resolve(JSON.parse(Buffer.concat(chunks_of_data).toString())); + }); + response.on('error', (error) => { + reject(error); + }); + }); + }); + } +} + +export default new PoolsUpdater(); From 2d29b9ef89ef8a989898b86f90ff1cbb1b7ea8e8 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Thu, 7 Apr 2022 14:51:23 +0900 Subject: [PATCH 57/68] Upon error, re-run the PoolsUpdater within 24h instead of 7d --- backend/src/tasks/pools-updater.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/src/tasks/pools-updater.ts b/backend/src/tasks/pools-updater.ts index a70e8cb5d..e6883ed07 100644 --- a/backend/src/tasks/pools-updater.ts +++ b/backend/src/tasks/pools-updater.ts @@ -18,8 +18,11 @@ class PoolsUpdater { return; } + const oneWeek = 604800; + const oneDay = 86400; + const now = new Date().getTime() / 1000; - if (now - this.lastRun < 604800) { // Execute the PoolsUpdate only once a week, or upon restart + if (now - this.lastRun < oneWeek) { // Execute the PoolsUpdate only once a week, or upon restart return; } @@ -44,7 +47,8 @@ class PoolsUpdater { logger.notice('PoolsUpdater completed'); } catch (e) { - logger.err('PoolsUpdater failed. Error: ' + e); + this.lastRun = now - oneWeek - oneDay; // Try again in 24h + logger.err('PoolsUpdater failed. Will try again in 24h. Error: ' + e); } } From e451b40084b2527a534bbe9af21f7e31d4ed1d34 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Thu, 7 Apr 2022 16:14:43 +0900 Subject: [PATCH 58/68] Catch http request error - Fix 24h retry period --- backend/src/tasks/pools-updater.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/backend/src/tasks/pools-updater.ts b/backend/src/tasks/pools-updater.ts index e6883ed07..b3838244a 100644 --- a/backend/src/tasks/pools-updater.ts +++ b/backend/src/tasks/pools-updater.ts @@ -47,7 +47,7 @@ class PoolsUpdater { logger.notice('PoolsUpdater completed'); } catch (e) { - this.lastRun = now - oneWeek - oneDay; // Try again in 24h + this.lastRun = now - (oneWeek - oneDay); // Try again in 24h instead of waiting next week logger.err('PoolsUpdater failed. Will try again in 24h. Error: ' + e); } } @@ -113,7 +113,7 @@ class PoolsUpdater { /** * Http request wrapper */ - private async query(path): Promise { + private query(path): Promise { return new Promise((resolve, reject) => { const options = { host: 'api.github.com', @@ -124,7 +124,7 @@ class PoolsUpdater { logger.debug('Querying: api.github.com' + path); - https.get(options, (response) => { + const request = https.get(options, (response) => { const chunks_of_data: any[] = []; response.on('data', (fragments) => { chunks_of_data.push(fragments); @@ -136,6 +136,11 @@ class PoolsUpdater { reject(error); }); }); + + request.on('error', (error) => { + logger.err('Query failed with error: ' + error); + reject(error); + }) }); } } From ba12b10f9d520acbee7458e7321a1161e97c416e Mon Sep 17 00:00:00 2001 From: nymkappa Date: Thu, 7 Apr 2022 18:14:28 +0900 Subject: [PATCH 59/68] Handle empty pools table error --- backend/src/api/blocks.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index 80e7a4e1f..b1cfca8bd 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -135,6 +135,12 @@ class Blocks { } else { pool = await poolsRepository.$getUnknownPool(); } + + if (!pool) { // Something is wrong with the pools table, ignore pool indexing + logger.err('Unable to find pool, nor getting the unknown pool. Is the "pools" table empty?'); + return blockExtended; + } + blockExtended.extras.pool = { id: pool.id, name: pool.name, From b4fce5cb0035749b6e00a5d291d5ab7d952dc417 Mon Sep 17 00:00:00 2001 From: Antoni Spaanderman <56turtle56@gmail.com> Date: Sun, 10 Apr 2022 00:20:19 +0200 Subject: [PATCH 60/68] Fix Lightning HTLC detection with options_anchors rename `OP_CHECKSEQUENCEVERIFY` to `OP_CSV` in regex --- .../app/components/address-labels/address-labels.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/components/address-labels/address-labels.component.ts b/frontend/src/app/components/address-labels/address-labels.component.ts index ee8e26de6..b22d66e42 100644 --- a/frontend/src/app/components/address-labels/address-labels.component.ts +++ b/frontend/src/app/components/address-labels/address-labels.component.ts @@ -52,7 +52,7 @@ export class AddressLabelsComponent implements OnInit { this.label = 'Lightning Force Close'; } return; - } else if (/^OP_DUP OP_HASH160 OP_PUSHBYTES_20 \w{40} OP_EQUAL OP_IF OP_CHECKSIG OP_ELSE OP_PUSHBYTES_33 \w{66} OP_SWAP OP_SIZE OP_PUSHBYTES_1 20 OP_EQUAL OP_NOTIF OP_DROP OP_PUSHNUM_2 OP_SWAP OP_PUSHBYTES_33 \w{66} OP_PUSHNUM_2 OP_CHECKMULTISIG OP_ELSE OP_HASH160 OP_PUSHBYTES_20 \w{40} OP_EQUALVERIFY OP_CHECKSIG OP_ENDIF (OP_PUSHNUM_1 OP_CHECKSEQUENCEVERIFY OP_DROP |)OP_ENDIF$/.test(this.vin.inner_witnessscript_asm)) { + } else if (/^OP_DUP OP_HASH160 OP_PUSHBYTES_20 \w{40} OP_EQUAL OP_IF OP_CHECKSIG OP_ELSE OP_PUSHBYTES_33 \w{66} OP_SWAP OP_SIZE OP_PUSHBYTES_1 20 OP_EQUAL OP_NOTIF OP_DROP OP_PUSHNUM_2 OP_SWAP OP_PUSHBYTES_33 \w{66} OP_PUSHNUM_2 OP_CHECKMULTISIG OP_ELSE OP_HASH160 OP_PUSHBYTES_20 \w{40} OP_EQUALVERIFY OP_CHECKSIG OP_ENDIF (OP_PUSHNUM_1 OP_CSV OP_DROP |)OP_ENDIF$/.test(this.vin.inner_witnessscript_asm)) { // https://github.com/lightning/bolts/blob/master/03-transactions.md#offered-htlc-outputs if (topElement.length === 66) { // top element is a public key From 90ca668bcb73811c3d2f721c2e1eace2eedd94e1 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 15 Mar 2022 13:07:06 +0100 Subject: [PATCH 61/68] [Indexing] - Support 10 blocks depth reorgs --- backend/src/api/blocks.ts | 12 ++- backend/src/index.ts | 5 ++ backend/src/repositories/BlocksRepository.ts | 75 ++++++++++++++++--- .../src/repositories/HashratesRepository.ts | 29 +++++++ 4 files changed, 109 insertions(+), 12 deletions(-) diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index b1cfca8bd..1024107d0 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -23,6 +23,7 @@ class Blocks { private newBlockCallbacks: ((block: BlockExtended, txIds: string[], transactions: TransactionExtended[]) => void)[] = []; private blockIndexingStarted = false; public blockIndexingCompleted = false; + public reindexFlag = true; // Always re-index the latest indexed data in case the node went offline with an invalid block tip (reorg) constructor() { } @@ -189,16 +190,19 @@ class Blocks { * [INDEXING] Index all blocks metadata for the mining dashboard */ public async $generateBlockDatabase() { - if (this.blockIndexingStarted) { + if (this.blockIndexingStarted && !this.reindexFlag) { return; } + this.reindexFlag = false; + const blockchainInfo = await bitcoinClient.getBlockchainInfo(); if (blockchainInfo.blocks !== blockchainInfo.headers) { // Wait for node to sync return; } this.blockIndexingStarted = true; + this.blockIndexingCompleted = false; try { let currentBlockHeight = blockchainInfo.blocks; @@ -316,6 +320,12 @@ class Blocks { if (Common.indexingEnabled()) { await blocksRepository.$saveBlockInDatabase(blockExtended); + + // If the last 10 blocks chain is not valid, re-index them (reorg) + const chainValid = await blocksRepository.$validateRecentBlocks(); + if (!chainValid) { + this.reindexFlag = true; + } } if (block.height % 2016 === 0) { diff --git a/backend/src/index.ts b/backend/src/index.ts index 20f6fb69c..9f0e80bd0 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -27,6 +27,7 @@ import icons from './api/liquid/icons'; import { Common } from './api/common'; import mining from './api/mining'; import HashratesRepository from './repositories/HashratesRepository'; +import BlocksRepository from './repositories/BlocksRepository'; import poolsUpdater from './tasks/pools-updater'; class Server { @@ -179,6 +180,10 @@ class Server { try { await poolsUpdater.updatePoolsJson(); + if (blocks.reindexFlag) { + await BlocksRepository.$deleteBlocks(10); + await HashratesRepository.$deleteLastEntries(); + } blocks.$generateBlockDatabase(); await mining.$generateNetworkHashrateHistory(); await mining.$generatePoolHashrateHistory(); diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index d7c0e501d..ff40414a2 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -10,9 +10,11 @@ class BlocksRepository { * Save indexed block data in the database */ public async $saveBlockInDatabase(block: BlockExtended) { - const connection = await DB.getConnection(); + let connection; try { + connection = await DB.getConnection(); + const query = `INSERT INTO blocks( height, hash, blockTimestamp, size, weight, tx_count, coinbase_raw, difficulty, @@ -72,8 +74,9 @@ class BlocksRepository { return []; } - const connection = await DB.getConnection(); + let connection; try { + connection = await DB.getConnection(); const [rows]: any[] = await connection.query(` SELECT height FROM blocks @@ -118,8 +121,9 @@ class BlocksRepository { query += ` GROUP by pools.id`; - const connection = await DB.getConnection(); + let connection; try { + connection = await DB.getConnection(); const [rows] = await connection.query(query, params); connection.release(); @@ -155,8 +159,9 @@ class BlocksRepository { query += ` blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; } - const connection = await DB.getConnection(); + let connection; try { + connection = await DB.getConnection(); const [rows] = await connection.query(query, params); connection.release(); @@ -194,8 +199,9 @@ class BlocksRepository { } query += ` blockTimestamp BETWEEN FROM_UNIXTIME('${from}') AND FROM_UNIXTIME('${to}')`; - const connection = await DB.getConnection(); + let connection; try { + connection = await DB.getConnection(); const [rows] = await connection.query(query, params); connection.release(); @@ -216,8 +222,9 @@ class BlocksRepository { ORDER BY height LIMIT 1;`; - const connection = await DB.getConnection(); + let connection; try { + connection = await DB.getConnection(); const [rows]: any[] = await connection.query(query); connection.release(); @@ -257,8 +264,9 @@ class BlocksRepository { query += ` ORDER BY height DESC LIMIT 10`; - const connection = await DB.getConnection(); + let connection; try { + connection = await DB.getConnection(); const [rows] = await connection.query(query, params); connection.release(); @@ -279,8 +287,9 @@ class BlocksRepository { * Get one block by height */ public async $getBlockByHeight(height: number): Promise { - const connection = await DB.getConnection(); + let connection; try { + connection = await DB.getConnection(); const [rows]: any[] = await connection.query(` SELECT *, UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp, pools.id as pool_id, pools.name as pool_name, pools.link as pool_link, pools.slug as pool_slug, @@ -310,8 +319,6 @@ class BlocksRepository { public async $getBlocksDifficulty(interval: string | null): Promise { interval = Common.getSqlInterval(interval); - const connection = await DB.getConnection(); - // :D ... Yeah don't ask me about this one https://stackoverflow.com/a/40303162 // Basically, using temporary user defined fields, we are able to extract all // difficulty adjustments from the blocks tables. @@ -344,14 +351,17 @@ class BlocksRepository { ORDER BY t.height `; + let connection; try { + connection = await DB.getConnection(); const [rows]: any[] = await connection.query(query); connection.release(); - for (let row of rows) { + for (const row of rows) { delete row['rn']; } + connection.release(); return rows; } catch (e) { connection.release(); @@ -386,6 +396,49 @@ class BlocksRepository { throw e; } } + + /* + * Check if the last 10 blocks chain is valid + */ + public async $validateRecentBlocks(): Promise { + let connection; + + try { + connection = await DB.getConnection(); + const [lastBlocks] = await connection.query(`SELECT height, hash, previous_block_hash FROM blocks ORDER BY height DESC LIMIT 10`); + connection.release(); + + for (let i = 0; i < lastBlocks.length - 1; ++i) { + if (lastBlocks[i].previous_block_hash !== lastBlocks[i + 1].hash) { + logger.notice(`Chain divergence detected at block ${lastBlocks[i].height}, re-indexing most recent data`); + return false; + } + } + + return true; + } catch (e) { + connection.release(); + + return true; // Don't do anything if there is a db error + } + } + + /** + * Delete $count blocks from the database + */ + public async $deleteBlocks(count: number) { + let connection; + + try { + connection = await DB.getConnection(); + logger.debug(`Delete ${count} most recent indexed blocks from the database`); + await connection.query(`DELETE FROM blocks ORDER BY height DESC LIMIT ${count};`); + } catch (e) { + logger.err('$deleteBlocks() error' + (e instanceof Error ? e.message : e)); + } + + connection.release(); + } } export default new BlocksRepository(); diff --git a/backend/src/repositories/HashratesRepository.ts b/backend/src/repositories/HashratesRepository.ts index 5efce29fe..6f994342a 100644 --- a/backend/src/repositories/HashratesRepository.ts +++ b/backend/src/repositories/HashratesRepository.ts @@ -169,6 +169,9 @@ class HashratesRepository { } } + /** + * Set latest run timestamp + */ public async $setLatestRunTimestamp(key: string, val: any = null) { const connection = await DB.getConnection(); const query = `UPDATE state SET number = ? WHERE name = ?`; @@ -181,6 +184,9 @@ class HashratesRepository { } } + /** + * Get latest run timestamp + */ public async $getLatestRunTimestamp(key: string): Promise { const connection = await DB.getConnection(); const query = `SELECT number FROM state WHERE name = ?`; @@ -199,6 +205,29 @@ class HashratesRepository { throw e; } } + + /** + * Delete most recent data points for re-indexing + */ + public async $deleteLastEntries() { + logger.debug(`Delete latest hashrates data points from the database`); + + let connection; + try { + connection = await DB.getConnection(); + const [rows] = await connection.query(`SELECT MAX(hashrate_timestamp) as timestamp FROM hashrates GROUP BY type`); + for (const row of rows) { + await connection.query(`DELETE FROM hashrates WHERE hashrate_timestamp = ?`, [row.timestamp]); + } + // Re-run the hashrate indexing to fill up missing data + await this.$setLatestRunTimestamp('last_hashrates_indexing', 0); + await this.$setLatestRunTimestamp('last_weekly_hashrates_indexing', 0); + } catch (e) { + logger.err('$deleteLastEntries() error' + (e instanceof Error ? e.message : e)); + } + + connection.release(); + } } export default new HashratesRepository(); From 15cc50338760f838db45e91bb52735b3ceb02f11 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Fri, 8 Apr 2022 16:36:58 +0900 Subject: [PATCH 62/68] Move graph mining chart link into dropdown --- .../components/graphs/graphs.component.html | 30 +++++++++---------- .../components/graphs/graphs.component.scss | 9 ++---- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/frontend/src/app/components/graphs/graphs.component.html b/frontend/src/app/components/graphs/graphs.component.html index d5cc61e91..55397bec7 100644 --- a/frontend/src/app/components/graphs/graphs.component.html +++ b/frontend/src/app/components/graphs/graphs.component.html @@ -1,25 +1,23 @@ - + \ No newline at end of file diff --git a/frontend/src/app/components/graphs/graphs.component.scss b/frontend/src/app/components/graphs/graphs.component.scss index c4ca483bd..b952137b9 100644 --- a/frontend/src/app/components/graphs/graphs.component.scss +++ b/frontend/src/app/components/graphs/graphs.component.scss @@ -1,9 +1,6 @@ .menu { flex-grow: 1; - max-width: 600px; -} - -.menu-li { - flex-grow: 1; - text-align: center; + @media (min-width: 576px) { + max-width: 400px; + } } From 08e19a612cdb7fa28d5a5896a0236b235f5943a1 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Sat, 9 Apr 2022 01:07:13 +0900 Subject: [PATCH 63/68] Add block fees graph --- backend/src/api/mining.ts | 23 +++ backend/src/index.ts | 1 + backend/src/repositories/BlocksRepository.ts | 29 +++ backend/src/routes.ts | 16 ++ frontend/src/app/app-routing.module.ts | 65 ++---- frontend/src/app/app.module.ts | 2 + .../block-fees-graph.component.html | 61 ++++++ .../block-fees-graph.component.scss | 135 ++++++++++++ .../block-fees-graph.component.ts | 195 ++++++++++++++++++ .../components/graphs/graphs.component.html | 4 + .../hashrate-chart.component.html | 2 +- frontend/src/app/services/api.service.ts | 7 + 12 files changed, 489 insertions(+), 51 deletions(-) create mode 100644 frontend/src/app/components/block-fees-graph/block-fees-graph.component.html create mode 100644 frontend/src/app/components/block-fees-graph/block-fees-graph.component.scss create mode 100644 frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts diff --git a/backend/src/api/mining.ts b/backend/src/api/mining.ts index 482a34511..e88f21983 100644 --- a/backend/src/api/mining.ts +++ b/backend/src/api/mining.ts @@ -5,6 +5,7 @@ import HashratesRepository from '../repositories/HashratesRepository'; import bitcoinClient from './bitcoin/bitcoin-client'; import logger from '../logger'; import blocks from './blocks'; +import { Common } from './common'; class Mining { hashrateIndexingStarted = false; @@ -13,6 +14,28 @@ class Mining { constructor() { } + /** + * Get historical block reward and total fee + */ + public async $getHistoricalBlockFees(interval: string | null = null): Promise { + let timeRange: number; + switch (interval) { + case '3y': timeRange = 43200; break; // 12h + case '2y': timeRange = 28800; break; // 8h + case '1y': timeRange = 28800; break; // 8h + case '6m': timeRange = 10800; break; // 3h + case '3m': timeRange = 7200; break; // 2h + case '1m': timeRange = 1800; break; // 30min + case '1w': timeRange = 300; break; // 5min + case '24h': timeRange = 1; break; + default: timeRange = 86400; break; // 24h + } + + interval = Common.getSqlInterval(interval); + + return await BlocksRepository.$getHistoricalBlockFees(timeRange, interval); + } + /** * Generate high level overview of the pool ranks and general stats */ diff --git a/backend/src/index.ts b/backend/src/index.ts index 9f0e80bd0..591afcfb4 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -316,6 +316,7 @@ class Server { .get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate', routes.$getHistoricalHashrate) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate/:interval', routes.$getHistoricalHashrate) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/reward-stats/:blockCount', routes.$getRewardStats) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fees/:interval', routes.$getHistoricalBlockFees) ; } diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index ff40414a2..a58d689e9 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -439,6 +439,35 @@ class BlocksRepository { connection.release(); } + + /** + * Get the historical averaged block reward and total fees + */ + public async $getHistoricalBlockFees(div: number, interval: string | null): Promise { + let connection; + try { + connection = await DB.getConnection(); + + let query = `SELECT CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp, + CAST(AVG(fees) as INT) as avg_fees + FROM blocks`; + + if (interval !== null) { + query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; + } + + query += ` GROUP BY UNIX_TIMESTAMP(blockTimestamp) DIV ${div}`; + + const [rows]: any = await connection.query(query); + connection.release(); + + return rows; + } catch (e) { + connection.release(); + logger.err('$getHistoricalBlockFees() error: ' + (e instanceof Error ? e.message : e)); + throw e; + } + } } export default new BlocksRepository(); diff --git a/backend/src/routes.ts b/backend/src/routes.ts index d558e3061..c2ddac72c 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -638,6 +638,22 @@ class Routes { } } + public async $getHistoricalBlockFees(req: Request, res: Response) { + try { + const blockFees = await mining.$getHistoricalBlockFees(req.params.interval ?? null); + const oldestIndexedBlockTimestamp = await BlocksRepository.$oldestBlockTimestamp(); + res.header('Pragma', 'public'); + res.header('Cache-control', 'public'); + res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString()); + res.json({ + oldestIndexedBlockTimestamp: oldestIndexedBlockTimestamp, + blockFees: blockFees, + }); + } catch (e) { + res.status(500).send(e instanceof Error ? e.message : e); + } + } + public async getBlock(req: Request, res: Response) { try { const result = await bitcoinApi.$getBlock(req.params.hash); diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index d46da5696..0ff1ee006 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -33,6 +33,7 @@ import { HashrateChartPoolsComponent } from './components/hashrates-chart-pools/ import { MiningStartComponent } from './components/mining-start/mining-start.component'; import { GraphsComponent } from './components/graphs/graphs.component'; import { BlocksList } from './components/blocks-list/blocks-list.component'; +import { BlockFeesGraphComponent } from './components/block-fees-graph/block-fees-graph.component'; let routes: Routes = [ { @@ -117,6 +118,10 @@ let routes: Routes = [ path: 'mining/pools', component: PoolRankingComponent, }, + { + path: 'mining/block-fees', + component: BlockFeesGraphComponent, + } ], }, { @@ -211,18 +216,6 @@ let routes: Routes = [ path: 'blocks', component: BlocksList, }, - { - path: 'hashrate', - component: HashrateChartComponent, - }, - { - path: 'hashrate/pools', - component: HashrateChartPoolsComponent, - }, - { - path: 'pools', - component: PoolRankingComponent, - }, { path: 'pool', children: [ @@ -259,6 +252,10 @@ let routes: Routes = [ path: 'mining/pools', component: PoolRankingComponent, }, + { + path: 'mining/block-fees', + component: BlockFeesGraphComponent, + } ] }, { @@ -347,18 +344,6 @@ let routes: Routes = [ path: 'blocks', component: BlocksList, }, - { - path: 'hashrate', - component: HashrateChartComponent, - }, - { - path: 'hashrate/pools', - component: HashrateChartPoolsComponent, - }, - { - path: 'pools', - component: PoolRankingComponent, - }, { path: 'pool', children: [ @@ -395,6 +380,10 @@ let routes: Routes = [ path: 'mining/pools', component: PoolRankingComponent, }, + { + path: 'mining/block-fees', + component: BlockFeesGraphComponent, + } ] }, { @@ -507,19 +496,7 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { { path: 'mempool', component: StatisticsComponent, - }, - { - path: 'mining/hashrate-difficulty', - component: HashrateChartComponent, - }, - { - path: 'mining/pools-dominance', - component: HashrateChartPoolsComponent, - }, - { - path: 'mining/pools', - component: PoolRankingComponent, - }, + } ] }, { @@ -639,19 +616,7 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { { path: 'mempool', component: StatisticsComponent, - }, - { - path: 'mining/hashrate-difficulty', - component: HashrateChartComponent, - }, - { - path: 'mining/pools-dominance', - component: HashrateChartPoolsComponent, - }, - { - path: 'mining/pools', - component: PoolRankingComponent, - }, + } ] }, { diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 807c88ade..4536a2ff1 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -80,6 +80,7 @@ import { DifficultyAdjustmentsTable } from './components/difficulty-adjustments- import { BlocksList } from './components/blocks-list/blocks-list.component'; import { RewardStatsComponent } from './components/reward-stats/reward-stats.component'; import { DataCyDirective } from './data-cy.directive'; +import { BlockFeesGraphComponent } from './components/block-fees-graph/block-fees-graph.component'; @NgModule({ declarations: [ @@ -141,6 +142,7 @@ import { DataCyDirective } from './data-cy.directive'; BlocksList, DataCyDirective, RewardStatsComponent, + BlockFeesGraphComponent, ], imports: [ BrowserModule.withServerTransition({ appId: 'serverApp' }), diff --git a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.html b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.html new file mode 100644 index 000000000..88c07e208 --- /dev/null +++ b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.html @@ -0,0 +1,61 @@ +
+ +
+ Block fees + +
+ + + + + + + + + +
+ +
+ +
+
+
+
+
+ +
+ + +
+
+
Hashrate
+

+ +

+
+
+
Difficulty
+

+ +

+
+
+
\ No newline at end of file diff --git a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.scss b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.scss new file mode 100644 index 000000000..54dbe5fad --- /dev/null +++ b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.scss @@ -0,0 +1,135 @@ +.card-header { + border-bottom: 0; + font-size: 18px; + @media (min-width: 465px) { + font-size: 20px; + } +} + +.main-title { + position: relative; + color: #ffffff91; + margin-top: -13px; + font-size: 10px; + text-transform: uppercase; + font-weight: 500; + text-align: center; + padding-bottom: 3px; +} + +.full-container { + padding: 0px 15px; + width: 100%; + min-height: 500px; + height: calc(100% - 150px); + @media (max-width: 992px) { + height: 100%; + padding-bottom: 100px; + }; +} + +.chart { + width: 100%; + height: 100%; + padding-bottom: 20px; + padding-right: 10px; + @media (max-width: 992px) { + padding-bottom: 25px; + } + @media (max-width: 829px) { + padding-bottom: 50px; + } + @media (max-width: 767px) { + padding-bottom: 25px; + } + @media (max-width: 629px) { + padding-bottom: 55px; + } + @media (max-width: 567px) { + padding-bottom: 55px; + } +} +.chart-widget { + width: 100%; + height: 100%; + max-height: 270px; +} + +.formRadioGroup { + margin-top: 6px; + display: flex; + flex-direction: column; + @media (min-width: 1130px) { + position: relative; + top: -65px; + } + @media (min-width: 830px) and (max-width: 1130px) { + position: relative; + top: 0px; + } + @media (min-width: 830px) { + flex-direction: row; + float: right; + margin-top: 0px; + } + .btn-sm { + font-size: 9px; + @media (min-width: 830px) { + font-size: 14px; + } + } +} + +.pool-distribution { + min-height: 56px; + display: block; + @media (min-width: 485px) { + display: flex; + flex-direction: row; + } + h5 { + margin-bottom: 10px; + } + .item { + width: 50%; + display: inline-block; + margin: 0px auto 20px; + &:nth-child(2) { + order: 2; + @media (min-width: 485px) { + order: 3; + } + } + &:nth-child(3) { + order: 3; + @media (min-width: 485px) { + order: 2; + display: block; + } + @media (min-width: 768px) { + display: none; + } + @media (min-width: 992px) { + display: block; + } + } + .card-title { + font-size: 1rem; + color: #4a68b9; + } + .card-text { + font-size: 18px; + span { + color: #ffffff66; + font-size: 12px; + } + } + } +} + +.skeleton-loader { + width: 100%; + display: block; + max-width: 80px; + margin: 15px auto 3px; +} diff --git a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts new file mode 100644 index 000000000..6a729d4f6 --- /dev/null +++ b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts @@ -0,0 +1,195 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core'; +import { EChartsOption, graphic } from 'echarts'; +import { Observable } from 'rxjs'; +import { map, share, startWith, switchMap, tap } from 'rxjs/operators'; +import { ApiService } from 'src/app/services/api.service'; +import { SeoService } from 'src/app/services/seo.service'; +import { formatNumber } from '@angular/common'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { formatterXAxisLabel } from 'src/app/shared/graphs.utils'; + +@Component({ + selector: 'app-block-fees-graph', + templateUrl: './block-fees-graph.component.html', + styleUrls: ['./block-fees-graph.component.scss'], + styles: [` + .loadingGraphs { + position: absolute; + top: 50%; + left: calc(50% - 15px); + z-index: 100; + } + `], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class BlockFeesGraphComponent implements OnInit { + @Input() tableOnly = false; + @Input() widget = false; + @Input() right: number | string = 45; + @Input() left: number | string = 75; + + radioGroupForm: FormGroup; + + chartOptions: EChartsOption = {}; + chartInitOptions = { + renderer: 'svg', + }; + + statsObservable$: Observable; + isLoading = true; + formatNumber = formatNumber; + timespan = ''; + + constructor( + @Inject(LOCALE_ID) public locale: string, + private seoService: SeoService, + private apiService: ApiService, + private formBuilder: FormBuilder + ) { + this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' }); + this.radioGroupForm.controls.dateSpan.setValue('1y'); + } + + ngOnInit(): void { + if (!this.widget) { + this.seoService.setTitle($localize`:@@mining.block-fees:Block Fees`); + } + + this.statsObservable$ = this.radioGroupForm.get('dateSpan').valueChanges + .pipe( + startWith('1y'), + switchMap((timespan) => { + this.timespan = timespan; + this.isLoading = true; + return this.apiService.getHistoricalBlockFees$(timespan) + .pipe( + tap((data: any) => { + this.prepareChartOptions({ + blockFees: data.blockFees.map(val => [val.timestamp * 1000, val.avg_fees / 100000000]), + }); + this.isLoading = false; + }), + map((data: any) => { + const availableTimespanDay = ( + (new Date().getTime() / 1000) - (data.oldestIndexedBlockTimestamp) + ) / 3600 / 24; + + return { + availableTimespanDay: availableTimespanDay, + }; + }), + ); + }), + share() + ); + } + + prepareChartOptions(data) { + this.chartOptions = { + animation: false, + color: [ + new graphic.LinearGradient(0, 0, 0, 0.65, [ + { offset: 0, color: '#F4511E' }, + { offset: 0.25, color: '#FB8C00' }, + { offset: 0.5, color: '#FFB300' }, + { offset: 0.75, color: '#FDD835' }, + { offset: 1, color: '#7CB342' } + ]), + ], + grid: { + top: 30, + bottom: 80, + right: this.right, + left: this.left, + }, + tooltip: { + show: !this.isMobile() || !this.widget, + trigger: 'axis', + axisPointer: { + type: 'line' + }, + backgroundColor: 'rgba(17, 19, 31, 1)', + borderRadius: 4, + shadowColor: 'rgba(0, 0, 0, 0.5)', + textStyle: { + color: '#b1b1b1', + align: 'left', + }, + borderColor: '#000', + formatter: (ticks) => { + const tick = ticks[0]; + const feesString = `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.3-3')} BTC`; + return ` + ${tick.axisValueLabel}
+ ${feesString} + `; + } + }, + xAxis: { + name: formatterXAxisLabel(this.locale, this.timespan), + nameLocation: 'middle', + nameTextStyle: { + padding: [10, 0, 0, 0], + }, + type: 'time', + splitNumber: this.isMobile() ? 5 : 10, + }, + yAxis: [ + { + type: 'value', + axisLabel: { + color: 'rgb(110, 112, 121)', + formatter: (val) => { + return `${val} BTC`; + } + }, + splitLine: { + show: false, + } + }, + ], + series: [ + { + zlevel: 0, + name: 'Fees', + showSymbol: false, + symbol: 'none', + data: data.blockFees, + type: 'line', + lineStyle: { + width: 2, + }, + }, + ], + dataZoom: this.widget ? null : [{ + type: 'inside', + realtime: true, + zoomLock: true, + maxSpan: 100, + minSpan: 10, + moveOnMouseMove: false, + }, { + showDetail: false, + show: true, + type: 'slider', + brushSelect: false, + realtime: true, + left: 20, + right: 15, + selectedDataBackground: { + lineStyle: { + color: '#fff', + opacity: 0.45, + }, + areaStyle: { + opacity: 0, + } + }, + }], + }; + } + + isMobile() { + return (window.innerWidth <= 767.98); + } +} diff --git a/frontend/src/app/components/graphs/graphs.component.html b/frontend/src/app/components/graphs/graphs.component.html index 55397bec7..97654beb5 100644 --- a/frontend/src/app/components/graphs/graphs.component.html +++ b/frontend/src/app/components/graphs/graphs.component.html @@ -16,6 +16,10 @@ [routerLink]="['/graphs/mining/hashrate-difficulty' | relativeUrl]" i18n="mining.hashrate-difficulty"> Hashrate & Difficulty + + Block Fees + diff --git a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.html b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.html index 4e9c66495..4107f1554 100644 --- a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.html +++ b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.html @@ -19,7 +19,7 @@
- Hashrate & Difficulty + Hashrate & Difficulty