From ba04bdfddc23c6bb87a5bf77f2e0977a909b798a 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 b79157d7275701d9339695094a4a60bf18132d6c 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 7742fffd22a1203f6489e5049ffb0962f0f33a9d 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 884e3efc19aaa0a81deefe6bb4597e7958c339f7 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 8c804ba28dcef8e56285d49c268bd57e72f21a81 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 ac031d8f240f770fbac933b5d25eda064b139cd7 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 1d8de2e37426e8e659731e37efc1e3ac7fd57bb0 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 64637c2b5ee9e2457bb82ec212fe11ba9d533e98 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 3096e0f5774e83ceb00992522792c4b0241dc419 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 5d806421f0437b8922bc6cc401d7059b36cad3da 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 281538b23963f2e83e39a93c020c54f269c5f1a4 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 e99714fcd733fcf0444f53be08940ae6d307f9dc 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 627adec8b03555a6834558c484db5fa15674a2e6 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 a5db6a0d368ec252f319687be8461d3edbcff959 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 a16add8ab8a4663f272e2bacbe48e8713984933c 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 0cff70fa6551e1b4212dd57ef6675e9dd9680629 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 8e9dd90a77ed00e5e2b2c5454ef864bf06546aaf 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 443e12b384f68cad9f02161b0f656cb779ebb03d 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 f60b419767554686eb75cf174f2e58c3a655ea88 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 9e78fdd45fe39d3ea9d6566109018f741efd3aab 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 e95b3885ee1ac5c661679b93caff4ade19a3ba4b 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 44a105b15685bcbeaf7d8f1191a8e47f4e471c2d 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 fadb27a684085c9aa46c887e3c075a97ba1d78ca 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 a2e8883216cc9d0173aeefcb86ceddbd28c6e009 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 55c94a4b655297dcdd16a366fdf5d3b558baf36d 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 0c88ec13162ceba01deec0f5c500c92e940ad8c4 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 cc15197da13b4b1d459ad88dbf1c7f1db2125056 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 c90751182b89157977309fa52e339b9ceb419681 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 01175d55312ac68637a7895e2a0232cc5a7aa9cf 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 d3ee75364b92a50fb3ee3c75898f2bf7a3f2f60f 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 8dc030021a106476582aafcca795b46b8ed2751c 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 5b98ba43f14169792e303fdcab4eb9895b9c5843 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 e3c61fa9e5220f21571f9f5d77d91539a5a7dbc0 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 de4c205ecbcc8af49ebcb49d8d4ed2b30d88a5b3 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 d0221c616fcff9b973a0b0062bf681cd1e2d2d39 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 42b77352cc36a7ec5ed02b0466eb864903ffe230 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 b56ea34b14678acb736802ecfa6894a997dbe9b9 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 f7a3fed3e0610924db88f8f895b1b918cf00b249 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 63624a905468b17b006e089adc79d58f3dc1712b 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 b9115762f5df397d9c7c15d86715a244ad3d955d 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 c71e79ee05bb14247bb6b4a42c194dc847cc683a 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 e9d6963d073181574edd6844446648c0de621ba2 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 71d0ba911ec2b3cdf92e01135a4557b583d7383f 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 74ce91497fd344a835ce1dd2eeb44d39f9b472e1 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 698bc9759ddc96828ac998aaa4a7b326fcd69fe5 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 277f1ea3f2a9e95743af29904f9ee5da3addd5a0 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 dfa614ff25d872c49435f7d24c9623cf01a7a2d6 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 1bea953ed08733a322cfae785a64ee55b66fe51f 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 86669c2d0d372b61e52f843dc3fd382d30148ed8 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 70dbf3edfc30e8d2ee140f5eba0d548e10847480 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 386a63a1ecaced646f1c2b71cee71a9856161af2 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 22c29d7269c8265fd27a1dfff8de26520dd82047 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 3c3968c7090f25d382b03b23ab89f3294e8ace4f 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 fd016924941a77e413e0cfb880307fd5888c4f42 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 baed9a9f11950862295627b43b4b1eab735a7551 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 a7260211caba43079e1c929497542d448729189c 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 733bcc71183d9a5954d0bb6ea9df45041dddfb24 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 c1f011d64b8861f915f0c55af4708f53c862efc9 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 4fcc56936d42a4619f8815b874ecb047619ac683 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 b92ac2c499745951f3a63bc925633ba40adebc9a 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 98224b5ddcf442edec624ce48028d2d28a889934 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 8423f8600df5e4d6f3f0fff778a88a9d5247d000 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 ae0bc02c7880a151229a93f35bd1cbf853437967 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