From 5de2bf4e9d46540fc4d797dcfda848db3ba0a5a0 Mon Sep 17 00:00:00 2001 From: softsimon Date: Fri, 19 Aug 2022 17:54:52 +0400 Subject: [PATCH 01/76] Updated mempool debug log --- backend/src/api/mempool.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/backend/src/api/mempool.ts b/backend/src/api/mempool.ts index 43aea6059..76c8b169f 100644 --- a/backend/src/api/mempool.ts +++ b/backend/src/api/mempool.ts @@ -103,12 +103,11 @@ class Mempool { return txTimes; } - public async $updateMempool() { - logger.debug('Updating mempool'); + public async $updateMempool(): Promise { + logger.debug(`Updating mempool...`); const start = new Date().getTime(); let hasChange: boolean = false; const currentMempoolSize = Object.keys(this.mempoolCache).length; - let txCount = 0; const transactions = await bitcoinApi.$getRawMempool(); const diff = transactions.length - currentMempoolSize; const newTransactions: TransactionExtended[] = []; @@ -124,7 +123,6 @@ class Mempool { try { const transaction = await transactionUtils.$getTransactionExtended(txid); this.mempoolCache[txid] = transaction; - txCount++; if (this.inSync) { this.txPerSecondArray.push(new Date().getTime()); this.vBytesPerSecondArray.push({ @@ -133,14 +131,9 @@ class Mempool { }); } hasChange = true; - if (diff > 0) { - logger.debug('Fetched transaction ' + txCount + ' / ' + diff); - } else { - logger.debug('Fetched transaction ' + txCount); - } newTransactions.push(transaction); } catch (e) { - logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e)); + logger.debug(`Error finding transaction '${txid}' in the mempool: ` + (e instanceof Error ? e.message : e)); } } @@ -197,8 +190,7 @@ class Mempool { const end = new Date().getTime(); const time = end - start; - logger.debug(`New mempool size: ${Object.keys(this.mempoolCache).length} Change: ${diff}`); - logger.debug('Mempool updated in ' + time / 1000 + ' seconds'); + logger.debug(`Mempool updated in ${time / 1000} seconds. New size: ${Object.keys(this.mempoolCache).length} (${diff > 0 ? '+' + diff : diff})`); } public handleRbfTransactions(rbfTransactions: { [txid: string]: TransactionExtended; }) { From baaa53f0ce513b0e469e872c1d2993f731f7926a Mon Sep 17 00:00:00 2001 From: junderw Date: Sun, 18 Sep 2022 22:30:09 +0900 Subject: [PATCH 02/76] Backend: Add block height from timestamp endpoint --- backend/src/api/mining/mining-routes.ts | 20 +++++++++++++ backend/src/repositories/BlocksRepository.ts | 30 ++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/backend/src/api/mining/mining-routes.ts b/backend/src/api/mining/mining-routes.ts index f52d42d1f..c9ace12e5 100644 --- a/backend/src/api/mining/mining-routes.ts +++ b/backend/src/api/mining/mining-routes.ts @@ -27,6 +27,7 @@ class MiningRoutes { .get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty-adjustments/:interval', this.$getDifficultyAdjustments) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/predictions/:interval', this.$getHistoricalBlockPrediction) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/:hash', this.$getBlockAudit) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/timestamp/:timestamp', this.$getHeightFromTimestamp) ; } @@ -246,6 +247,25 @@ class MiningRoutes { res.status(500).send(e instanceof Error ? e.message : e); } } + + private async $getHeightFromTimestamp(req: Request, res: Response) { + try { + const timestamp = parseInt(req.params.timestamp, 10); + // Prevent non-integers that are not seconds + if (!/^[1-9][0-9]*$/.test(req.params.timestamp) || timestamp >= 2 ** 32) { + throw new Error(`Invalid timestamp, value must be Unix seconds`); + } + const result = await BlocksRepository.$getBlockHeightFromTimestamp( + timestamp, + ); + res.header('Pragma', 'public'); + res.header('Cache-control', 'public'); + res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString()); + res.json(result); + } catch (e) { + res.status(500).send(e instanceof Error ? e.message : e); + } + } } export default new MiningRoutes(); diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 40f670833..c5a1a2ae4 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -392,6 +392,36 @@ class BlocksRepository { } } + /** + * Get the first block at or directly after a given timestamp + * @param timestamp number unix time in seconds + * @returns The height and timestamp of a block (timestamp might vary from given timestamp) + */ + public async $getBlockHeightFromTimestamp( + timestamp: number, + ): Promise<{ height: number; timestamp: number }> { + try { + // Get first block at or after the given timestamp + const query = `SELECT height, blockTimestamp as timestamp FROM blocks + WHERE blockTimestamp >= FROM_UNIXTIME(?) + ORDER BY blockTimestamp ASC + LIMIT 1`; + const params = [timestamp]; + const [rows]: any[][] = await DB.query(query, params); + if (rows.length === 0) { + throw new Error(`No block was found after timestamp ${timestamp}`); + } + + return rows[0]; + } catch (e) { + logger.err( + 'Cannot get block height from timestamp from the db. Reason: ' + + (e instanceof Error ? e.message : e), + ); + throw e; + } + } + /** * Return blocks height */ From eca9daf5e256b1fe6b21b0a7fb5d627a96bc3940 Mon Sep 17 00:00:00 2001 From: junderw Date: Mon, 19 Sep 2022 16:44:53 +0900 Subject: [PATCH 03/76] Fix: Add hash and reverse search order --- backend/src/api/mining/mining-routes.ts | 6 +++++- backend/src/repositories/BlocksRepository.ts | 10 +++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/backend/src/api/mining/mining-routes.ts b/backend/src/api/mining/mining-routes.ts index c9ace12e5..ac4b82363 100644 --- a/backend/src/api/mining/mining-routes.ts +++ b/backend/src/api/mining/mining-routes.ts @@ -251,8 +251,12 @@ class MiningRoutes { private async $getHeightFromTimestamp(req: Request, res: Response) { try { const timestamp = parseInt(req.params.timestamp, 10); + // This will prevent people from entering milliseconds etc. + // Block timestamps are allowed to be up to 2 hours off, so 24 hours + // will never put the maximum value before the most recent block + const nowPlus1day = Math.floor(Date.now() / 1000) + 60 * 60 * 24; // Prevent non-integers that are not seconds - if (!/^[1-9][0-9]*$/.test(req.params.timestamp) || timestamp >= 2 ** 32) { + if (!/^[1-9][0-9]*$/.test(req.params.timestamp) || timestamp > nowPlus1day) { throw new Error(`Invalid timestamp, value must be Unix seconds`); } const result = await BlocksRepository.$getBlockHeightFromTimestamp( diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index c5a1a2ae4..590e9de37 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -399,17 +399,17 @@ class BlocksRepository { */ public async $getBlockHeightFromTimestamp( timestamp: number, - ): Promise<{ height: number; timestamp: number }> { + ): Promise<{ height: number; hash: string; timestamp: number }> { try { // Get first block at or after the given timestamp - const query = `SELECT height, blockTimestamp as timestamp FROM blocks - WHERE blockTimestamp >= FROM_UNIXTIME(?) - ORDER BY blockTimestamp ASC + const query = `SELECT height, hash, blockTimestamp as timestamp FROM blocks + WHERE blockTimestamp <= FROM_UNIXTIME(?) + ORDER BY blockTimestamp DESC LIMIT 1`; const params = [timestamp]; const [rows]: any[][] = await DB.query(query, params); if (rows.length === 0) { - throw new Error(`No block was found after timestamp ${timestamp}`); + throw new Error(`No block was found before timestamp ${timestamp}`); } return rows[0]; From 19727b853cc23d6524cff170a2c5901ab2fc097c Mon Sep 17 00:00:00 2001 From: softsimon Date: Thu, 29 Sep 2022 13:45:03 +0400 Subject: [PATCH 04/76] Handle network url ending matching better --- frontend/src/app/services/state.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index b0e018941..7b513ee54 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -158,7 +158,8 @@ export class StateService { // (?:[a-z]{2}(?:-[A-Z]{2})?\/)? optional locale prefix (non-capturing) // (?:preview\/)? optional "preview" prefix (non-capturing) // (bisq|testnet|liquidtestnet|liquid|signet)/ network string (captured as networkMatches[1]) - const networkMatches = url.match(/^\/(?:[a-z]{2}(?:-[A-Z]{2})?\/)?(?:preview\/)?(bisq|testnet|liquidtestnet|liquid|signet)/); + // ($|\/) network string must end or end with a slash + const networkMatches = url.match(/^\/(?:[a-z]{2}(?:-[A-Z]{2})?\/)?(?:preview\/)?(bisq|testnet|liquidtestnet|liquid|signet)($|\/)/); switch (networkMatches && networkMatches[1]) { case 'liquid': if (this.network !== 'liquid') { From e62986d3dc3bec2a935ceb23b3cbe5fa186835b9 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Fri, 30 Sep 2022 19:10:11 +0200 Subject: [PATCH 05/76] Fixes #2592 --- backend/src/api/lightning/clightning/clightning-convert.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/api/lightning/clightning/clightning-convert.ts b/backend/src/api/lightning/clightning/clightning-convert.ts index 121fb20ea..9b3c62f04 100644 --- a/backend/src/api/lightning/clightning/clightning-convert.ts +++ b/backend/src/api/lightning/clightning/clightning-convert.ts @@ -70,6 +70,8 @@ export async function convertAndmergeBidirectionalChannels(clChannels: any[]): P logger.info(`Building partial channels from clightning output. Channels processed: ${channelProcessed + 1} of ${keys.length}`); loggerTimer = new Date().getTime() / 1000; } + + channelProcessed++; } return consolidatedChannelList; From 579f5e1fbdc75a4ddb31a8718bde427bd5d0e4fa Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 3 Oct 2022 16:57:15 +0000 Subject: [PATCH 06/76] Fix tx marker boundary condition --- .../mempool-blocks/mempool-blocks.component.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) 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 17236e2ca..e1a443680 100644 --- a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts +++ b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts @@ -287,11 +287,12 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy { this.arrowVisible = true; - for (const block of this.mempoolBlocks) { - for (let i = 0; i < block.feeRange.length - 1; i++) { + let found = false; + for (let txInBlockIndex = 0; txInBlockIndex < this.mempoolBlocks.length && !found; txInBlockIndex++) { + const block = this.mempoolBlocks[txInBlockIndex]; + for (let i = 0; i < block.feeRange.length - 1 && !found; i++) { if (this.txFeePerVSize < block.feeRange[i + 1] && this.txFeePerVSize >= block.feeRange[i]) { - const txInBlockIndex = this.mempoolBlocks.indexOf(block); - const feeRangeIndex = block.feeRange.findIndex((val, index) => this.txFeePerVSize < block.feeRange[index + 1]); + const feeRangeIndex = i; const feeRangeChunkSize = 1 / (block.feeRange.length - 1); const txFee = this.txFeePerVSize - block.feeRange[i]; @@ -306,9 +307,13 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy { + ((1 - feePosition) * blockedFilledPercentage * this.blockWidth); this.rightPosition = arrowRightPosition; - break; + found = true; } } + if (this.txFeePerVSize >= block.feeRange[block.feeRange.length - 1]) { + this.rightPosition = txInBlockIndex * (this.blockWidth + this.blockPadding); + found = true; + } } } From 89244a0c352ef66387f216f9be51bd423fad1e8f Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 3 Oct 2022 21:44:55 +0000 Subject: [PATCH 07/76] Enable block scrolling in ltr time mode --- .../src/app/components/app/app.component.ts | 2 ++ .../blockchain/blockchain.component.scss | 26 +++++++++++++++---- .../mempool-blocks.component.scss | 6 +++++ .../app/components/start/start.component.html | 2 +- .../app/components/start/start.component.ts | 14 ++++++++-- 5 files changed, 42 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/components/app/app.component.ts b/frontend/src/app/components/app/app.component.ts index 633e466a4..d9d6f77d6 100644 --- a/frontend/src/app/components/app/app.component.ts +++ b/frontend/src/app/components/app/app.component.ts @@ -25,6 +25,8 @@ export class AppComponent implements OnInit { if (this.locale.startsWith('ar') || this.locale.startsWith('fa') || this.locale.startsWith('he')) { this.dir = 'rtl'; this.class = 'rtl-layout'; + } else { + this.class = 'ltr-layout'; } tooltipConfig.animation = false; diff --git a/frontend/src/app/components/blockchain/blockchain.component.scss b/frontend/src/app/components/blockchain/blockchain.component.scss index 2f13374fe..97d864398 100644 --- a/frontend/src/app/components/blockchain/blockchain.component.scss +++ b/frontend/src/app/components/blockchain/blockchain.component.scss @@ -27,7 +27,6 @@ left: 0; top: 75px; transform: translateX(50vw); - transition: transform 1s; } .position-container.liquid, .position-container.liquidtestnet { @@ -97,14 +96,31 @@ } .blockchain-wrapper.ltr-transition .blocks-wrapper, +.blockchain-wrapper.ltr-transition .position-container, .blockchain-wrapper.ltr-transition .time-toggle { transition: transform 1s; } -.blockchain-wrapper.time-ltr .blocks-wrapper { - transform: scaleX(-1); +.blockchain-wrapper.time-ltr { + .blocks-wrapper { + transform: scaleX(-1); + } + + .time-toggle { + transform: translateX(-50%) scaleX(-1); + } } -.blockchain-wrapper.time-ltr .time-toggle { - transform: translateX(-50%) scaleX(-1); +:host-context(.ltr-layout) { + .blockchain-wrapper.time-ltr .blocks-wrapper, + .blockchain-wrapper .blocks-wrapper { + direction: ltr; + } +} + +:host-context(.rtl-layout) { + .blockchain-wrapper.time-ltr .blocks-wrapper, + .blockchain-wrapper .blocks-wrapper { + direction: rtl; + } } \ No newline at end of file diff --git a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.scss b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.scss index 8032be92f..565d4b302 100644 --- a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.scss +++ b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.scss @@ -146,4 +146,10 @@ .block-body { transform: scaleX(-1); } +} + +:host-context(.rtl-layout) { + #arrow-up { + transform: translateX(70px); + } } \ No newline at end of file diff --git a/frontend/src/app/components/start/start.component.html b/frontend/src/app/components/start/start.component.html index 9d7f39ba2..89b6efdc3 100644 --- a/frontend/src/app/components/start/start.component.html +++ b/frontend/src/app/components/start/start.component.html @@ -8,7 +8,7 @@
{{ eventName }} in {{ countdown | number }} block{{ countdown === 1 ? '' : 's' }}!
-
diff --git a/frontend/src/app/components/start/start.component.ts b/frontend/src/app/components/start/start.component.ts index 78e004985..37c94baa3 100644 --- a/frontend/src/app/components/start/start.component.ts +++ b/frontend/src/app/components/start/start.component.ts @@ -1,4 +1,5 @@ -import { Component, ElementRef, HostListener, OnInit, ViewChild } from '@angular/core'; +import { Component, ElementRef, HostListener, OnInit, OnDestroy, ViewChild } from '@angular/core'; +import { Subscription } from 'rxjs'; import { StateService } from '../../services/state.service'; import { specialBlocks } from '../../app.constants'; @@ -7,7 +8,7 @@ import { specialBlocks } from '../../app.constants'; templateUrl: './start.component.html', styleUrls: ['./start.component.scss'], }) -export class StartComponent implements OnInit { +export class StartComponent implements OnInit, OnDestroy { interval = 60; colors = ['#5E35B1', '#ffffff']; @@ -16,6 +17,8 @@ export class StartComponent implements OnInit { eventName = ''; mouseDragStartX: number; blockchainScrollLeftInit: number; + timeLtrSubscription: Subscription; + timeLtr: boolean = this.stateService.timeLtr.value; @ViewChild('blockchainContainer') blockchainContainer: ElementRef; constructor( @@ -23,6 +26,9 @@ export class StartComponent implements OnInit { ) { } ngOnInit() { + this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => { + this.timeLtr = !!ltr; + }); this.stateService.blocks$ .subscribe((blocks: any) => { if (this.stateService.network !== '') { @@ -72,4 +78,8 @@ export class StartComponent implements OnInit { this.mouseDragStartX = null; this.stateService.setBlockScrollingInProgress(false); } + + ngOnDestroy() { + this.timeLtrSubscription.unsubscribe(); + } } From 18f5266fdadb89da865e92d7ececbd4dbc1b651b Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 3 Oct 2022 22:07:59 +0000 Subject: [PATCH 08/76] Reverse time by default for RTL languages --- frontend/src/app/services/state.service.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index 920f32dd9..9f7cc58a3 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable, PLATFORM_ID } from '@angular/core'; +import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core'; import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable } from 'rxjs'; import { Transaction } from '../interfaces/electrs.interface'; import { IBackendInfo, MempoolBlock, MempoolBlockWithTransactions, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, TransactionStripped } from '../interfaces/websocket.interface'; @@ -113,6 +113,7 @@ export class StateService { constructor( @Inject(PLATFORM_ID) private platformId: any, + @Inject(LOCALE_ID) private locale: string, private router: Router, private storageService: StorageService, ) { @@ -151,7 +152,10 @@ export class StateService { this.blockVSize = this.env.BLOCK_WEIGHT_UNITS / 4; - this.timeLtr = new BehaviorSubject(this.storageService.getValue('time-preference-ltr') === 'true'); + const savedTimePreference = this.storageService.getValue('time-preference-ltr'); + const rtlLanguage = (this.locale.startsWith('ar') || this.locale.startsWith('fa') || this.locale.startsWith('he')); + // default time direction is right-to-left, unless locale is a RTL language + this.timeLtr = new BehaviorSubject(savedTimePreference === 'true' || (savedTimePreference == null && rtlLanguage)); this.timeLtr.subscribe((ltr) => { this.storageService.setValue('time-preference-ltr', ltr ? 'true' : 'false'); }); From 27869a00c1d5876ad3aff3f98696e543e0e343f6 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 4 Oct 2022 21:00:46 +0000 Subject: [PATCH 09/76] Scroll to input/output when clicked in tx diagram --- .../transaction/transaction.component.html | 7 +- .../transaction/transaction.component.ts | 11 +++ .../transactions-list.component.html | 10 +-- .../transactions-list.component.ts | 84 +++++++++++-------- .../tx-bowtie-graph-tooltip.component.html | 2 +- .../tx-bowtie-graph.component.html | 2 + .../tx-bowtie-graph.component.ts | 22 +++-- 7 files changed, 88 insertions(+), 50 deletions(-) diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index 5b35fc7b1..dcd59a1f8 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -208,7 +208,10 @@ [lineLimit]="inOutLimit" [maxStrands]="graphExpanded ? maxInOut : 24" [network]="network" - [tooltip]="true"> + [tooltip]="true" + (selectInput)="selectInput($event)" + (selectOutput)="selectOutput($event)" + >
@@ -240,7 +243,7 @@
- +

Details

diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index 1db6e8f09..8aa313c81 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -47,6 +47,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { now = new Date().getTime(); timeAvg$: Observable; liquidUnblinding = new LiquidUnblinding(); + inputIndex: number; outputIndex: number; showFlow: boolean = true; graphExpanded: boolean = false; @@ -334,6 +335,16 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { this.graphExpanded = false; } + selectInput(input) { + this.inputIndex = input; + this.outputIndex = null; + } + + selectOutput(output) { + this.outputIndex = output; + this.inputIndex = null; + } + @HostListener('window:resize', ['$event']) setGraphSize(): void { if (this.graphContainer) { 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 81c3dce5c..e26740aa5 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.html +++ b/frontend/src/app/components/transactions-list/transactions-list.component.html @@ -20,9 +20,9 @@
- + - + @@ -158,7 +158,7 @@
@@ -146,7 +146,7 @@
- + rowLimit && tx['@voutLimit'] && !outputIndex"> + diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.ts b/frontend/src/app/components/transactions-list/transactions-list.component.ts index 8fd81af51..03c2d59ee 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.ts +++ b/frontend/src/app/components/transactions-list/transactions-list.component.ts @@ -24,6 +24,7 @@ export class TransactionsListComponent implements OnInit, OnChanges { @Input() transactionPage = false; @Input() errorUnblinded = false; @Input() paginated = false; + @Input() inputIndex: number; @Input() outputIndex: number; @Input() address: string = ''; @Input() rowLimit = 12; @@ -37,6 +38,8 @@ export class TransactionsListComponent implements OnInit, OnChanges { showDetails$ = new BehaviorSubject(false); assetsMinimal: any; transactionsLength: number = 0; + inputRowLimit: number = 12; + outputRowLimit: number = 12; constructor( public stateService: StateService, @@ -97,50 +100,57 @@ export class TransactionsListComponent implements OnInit, OnChanges { ).subscribe(() => this.ref.markForCheck()); } - ngOnChanges(): void { - if (!this.transactions || !this.transactions.length) { - return; + ngOnChanges(changes): void { + if (changes.inputIndex || changes.outputIndex || changes.rowLimit) { + this.inputRowLimit = Math.max(this.rowLimit, (this.inputIndex || 0) + 3); + this.outputRowLimit = Math.max(this.rowLimit, (this.outputIndex || 0) + 3); + if (this.inputIndex || this.outputIndex) { + setTimeout(() => { + const assetBoxElements = document.getElementsByClassName('assetBox'); + if (assetBoxElements && assetBoxElements[0]) { + assetBoxElements[0].scrollIntoView({block: "center"}); + } + }, 10); + } } - - this.transactionsLength = this.transactions.length; - if (this.outputIndex) { - setTimeout(() => { - const assetBoxElements = document.getElementsByClassName('assetBox'); - if (assetBoxElements && assetBoxElements[0]) { - assetBoxElements[0].scrollIntoView(); - } - }, 10); - } - - this.transactions.forEach((tx) => { - tx['@voutLimit'] = true; - tx['@vinLimit'] = true; - if (tx['addressValue'] !== undefined) { + if (changes.transactions || changes.address) { + if (!this.transactions || !this.transactions.length) { return; } - if (this.address) { - const addressIn = tx.vout - .filter((v: Vout) => v.scriptpubkey_address === this.address) - .map((v: Vout) => v.value || 0) - .reduce((a: number, b: number) => a + b, 0); + this.transactionsLength = this.transactions.length; - const addressOut = tx.vin - .filter((v: Vin) => v.prevout && v.prevout.scriptpubkey_address === this.address) - .map((v: Vin) => v.prevout.value || 0) - .reduce((a: number, b: number) => a + b, 0); - tx['addressValue'] = addressIn - addressOut; - } - }); - const txIds = this.transactions.filter((tx) => !tx._outspends).map((tx) => tx.txid); - if (txIds.length) { - this.refreshOutspends$.next(txIds); - } - if (this.stateService.env.LIGHTNING) { - const txIds = this.transactions.filter((tx) => !tx._channels).map((tx) => tx.txid); + this.transactions.forEach((tx) => { + tx['@voutLimit'] = true; + tx['@vinLimit'] = true; + if (tx['addressValue'] !== undefined) { + return; + } + + if (this.address) { + const addressIn = tx.vout + .filter((v: Vout) => v.scriptpubkey_address === this.address) + .map((v: Vout) => v.value || 0) + .reduce((a: number, b: number) => a + b, 0); + + const addressOut = tx.vin + .filter((v: Vin) => v.prevout && v.prevout.scriptpubkey_address === this.address) + .map((v: Vin) => v.prevout.value || 0) + .reduce((a: number, b: number) => a + b, 0); + + tx['addressValue'] = addressIn - addressOut; + } + }); + const txIds = this.transactions.filter((tx) => !tx._outspends).map((tx) => tx.txid); if (txIds.length) { - this.refreshChannels$.next(txIds); + this.refreshOutspends$.next(txIds); + } + if (this.stateService.env.LIGHTNING) { + const txIds = this.transactions.filter((tx) => !tx._channels).map((tx) => tx.txid); + if (txIds.length) { + this.refreshChannels$.next(txIds); + } } } } diff --git a/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html b/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html index 563e6ed00..6872438a0 100644 --- a/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html +++ b/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html @@ -44,7 +44,7 @@ Output Fee - #{{ line.index }} + #{{ line.index + 1 }}

Confidential

diff --git a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.html b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.html index 03056cd53..3f0143a67 100644 --- a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.html +++ b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.html @@ -60,6 +60,7 @@ attr.marker-start="url(#{{input.class}}-arrow)" (pointerover)="onHover($event, 'input', i);" (pointerout)="onBlur($event, 'input', i);" + (click)="onClick($event, 'input', inputData[i].index);" /> @@ -70,6 +71,7 @@ attr.marker-start="url(#{{output.class}}-arrow)" (pointerover)="onHover($event, 'output', i);" (pointerout)="onBlur($event, 'output', i);" + (click)="onClick($event, 'output', outputData[i].index);" /> diff --git a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts index 16e2736f7..592232955 100644 --- a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts +++ b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, Input, OnChanges, HostListener } from '@angular/core'; +import { Component, OnInit, Input, Output, EventEmitter, OnChanges, HostListener } from '@angular/core'; import { Transaction } from '../../interfaces/electrs.interface'; interface SvgLine { @@ -35,6 +35,9 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { @Input() maxStrands = 24; // number of inputs/outputs to keep fully on-screen. @Input() tooltip = false; + @Output() selectInput = new EventEmitter(); + @Output() selectOutput = new EventEmitter(); + inputData: Xput[]; outputData: Xput[]; inputs: SvgLine[]; @@ -76,11 +79,12 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { this.combinedWeight = Math.min(this.maxCombinedWeight, Math.floor((this.width - (2 * this.midWidth)) / 6)); const totalValue = this.calcTotalValue(this.tx); - let voutWithFee = this.tx.vout.map(v => { + let voutWithFee = this.tx.vout.map((v, i) => { return { type: v.scriptpubkey_type === 'fee' ? 'fee' : 'output', value: v?.value, address: v?.scriptpubkey_address || v?.scriptpubkey_type?.toUpperCase(), + index: i, pegout: v?.pegout?.scriptpubkey_address, confidential: (this.isLiquid && v?.value === undefined), } as Xput; @@ -91,11 +95,12 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { } const outputCount = voutWithFee.length; - let truncatedInputs = this.tx.vin.map(v => { + let truncatedInputs = this.tx.vin.map((v, i) => { return { type: 'input', value: v?.prevout?.value, address: v?.prevout?.scriptpubkey_address || v?.prevout?.scriptpubkey_type?.toUpperCase(), + index: i, coinbase: v?.is_coinbase, pegin: v?.is_pegin, confidential: (this.isLiquid && v?.prevout?.value === undefined), @@ -306,8 +311,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { }; } else { this.hoverLine = { - ...this.outputData[index], - index + ...this.outputData[index] }; } } @@ -315,4 +319,12 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { onBlur(event, side, index): void { this.hoverLine = null; } + + onClick(event, side, index): void { + if (side === 'input') { + this.selectInput.emit(index); + } else { + this.selectOutput.emit(index); + } + } } From 337918bb93f1bf987a46d58ddfdaeed0a870a8cf Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 4 Oct 2022 21:08:54 +0000 Subject: [PATCH 10/76] Shorten tx diagram toggle labels --- .../src/app/components/transaction/transaction.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index 5b35fc7b1..8eb085c27 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -195,7 +195,7 @@

Flow

- +
@@ -234,7 +234,7 @@
- +
From e909c56c3461b0c4df82c96a27c01112e03191ca Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 4 Oct 2022 22:09:32 +0000 Subject: [PATCH 11/76] Fix tx confirmation badge layout bug --- .../transaction/transaction.component.scss | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/frontend/src/app/components/transaction/transaction.component.scss b/frontend/src/app/components/transaction/transaction.component.scss index e0e7d79fe..df8d37ebc 100644 --- a/frontend/src/app/components/transaction/transaction.component.scss +++ b/frontend/src/app/components/transaction/transaction.component.scss @@ -3,38 +3,38 @@ } .container-buttons { - align-self: center; + align-self: flex-start; } .title-block { - flex-wrap: wrap; + flex-wrap: wrap; + align-items: baseline; @media (min-width: 650px) { flex-direction: row; } h1 { margin: 0rem; + margin-right: 15px; line-height: 1; } } .tx-link { - display: flex; - flex-grow: 1; margin-bottom: 0px; margin-top: 8px; - @media (min-width: 650px) { - align-self: end; - margin-left: 15px; - margin-top: 0px; - margin-bottom: -3px; - } - @media (min-width: 768px) { + display: inline-block; + width: 100%; + flex-shrink: 0; + @media (min-width: 651px) { + display: flex; + width: auto; + flex-grow: 1; margin-bottom: 0px; top: 1px; position: relative; - } - @media (max-width: 768px) { - order: 3; - } + } + @media (max-width: 650px) { + order: 3; + } } .td-width { From 64a7d2c5f25d1ca2c2723a0da77eb70fb32b7167 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 4 Oct 2022 23:29:04 +0000 Subject: [PATCH 12/76] Navigate to vin/vout page on diagram click --- .../tx-bowtie-graph.component.ts | 66 ++++++++++++++++++- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts index 592232955..7eaba9ce6 100644 --- a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts +++ b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts @@ -1,5 +1,11 @@ import { Component, OnInit, Input, Output, EventEmitter, OnChanges, HostListener } from '@angular/core'; -import { Transaction } from '../../interfaces/electrs.interface'; +import { StateService } from '../../services/state.service'; +import { Outspend, Transaction } from '../../interfaces/electrs.interface'; +import { Router } from '@angular/router'; +import { ReplaySubject, merge, Subscription } from 'rxjs'; +import { tap, switchMap } from 'rxjs/operators'; +import { ApiService } from '../../services/api.service'; +import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe'; interface SvgLine { path: string; @@ -48,6 +54,10 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { isLiquid: boolean = false; hoverLine: Xput | void = null; tooltipPosition = { x: 0, y: 0 }; + outspends: Outspend[] = []; + + outspendsSubscription: Subscription; + refreshOutspends$: ReplaySubject = new ReplaySubject(); gradientColors = { '': ['#9339f4', '#105fb0'], @@ -64,12 +74,45 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { gradient: string[] = ['#105fb0', '#105fb0']; + constructor( + private router: Router, + private relativeUrlPipe: RelativeUrlPipe, + private stateService: StateService, + private apiService: ApiService, + ) { } + ngOnInit(): void { this.initGraph(); + + this.outspendsSubscription = merge( + this.refreshOutspends$ + .pipe( + switchMap((txid) => this.apiService.getOutspendsBatched$([txid])), + tap((outspends: Outspend[][]) => { + if (!this.tx || !outspends || !outspends.length) { + return; + } + this.outspends = outspends[0]; + }), + ), + this.stateService.utxoSpent$ + .pipe( + tap((utxoSpent) => { + for (const i in utxoSpent) { + this.outspends[i] = { + spent: true, + txid: utxoSpent[i].txid, + vin: utxoSpent[i].vin, + }; + } + }), + ), + ).subscribe(() => {}); } ngOnChanges(): void { this.initGraph(); + this.refreshOutspends$.next(this.tx.txid); } initGraph(): void { @@ -322,9 +365,26 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { onClick(event, side, index): void { if (side === 'input') { - this.selectInput.emit(index); + const input = this.tx.vin[index]; + if (input && input.txid && input.vout != null) { + this.router.navigate([this.relativeUrlPipe.transform('/tx'), input.txid + ':' + input.vout], { + queryParamsHandling: 'merge', + fragment: 'flow' + }); + } else { + this.selectInput.emit(index); + } } else { - this.selectOutput.emit(index); + const output = this.tx.vout[index]; + const outspend = this.outspends[index]; + if (output && outspend && outspend.spent && outspend.txid) { + this.router.navigate([this.relativeUrlPipe.transform('/tx'), outspend.txid], { + queryParamsHandling: 'merge', + fragment: 'flow' + }); + } else { + this.selectOutput.emit(index); + } } } } From e4fa21a145b03468e6c73dee29688c111e226535 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 4 Oct 2022 23:30:14 +0000 Subject: [PATCH 13/76] Highlight url input/output in tx diagram & list --- .../transaction/transaction.component.html | 1 + .../transaction/transaction.component.ts | 11 +++++++++-- .../transactions-list.component.html | 2 +- .../transactions-list.component.ts | 2 +- .../tx-bowtie-graph/tx-bowtie-graph.component.html | 14 ++++++++++++++ .../tx-bowtie-graph/tx-bowtie-graph.component.scss | 11 +++++++++++ .../tx-bowtie-graph/tx-bowtie-graph.component.ts | 4 +++- 7 files changed, 40 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index dcd59a1f8..5bd40a557 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -209,6 +209,7 @@ [maxStrands]="graphExpanded ? maxInOut : 24" [network]="network" [tooltip]="true" + [inputIndex]="inputIndex" [outputIndex]="outputIndex" (selectInput)="selectInput($event)" (selectOutput)="selectOutput($event)" > diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index 8aa313c81..c64c112b1 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -122,8 +122,15 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { .pipe( switchMap((params: ParamMap) => { const urlMatch = (params.get('id') || '').split(':'); - this.txId = urlMatch[0]; - this.outputIndex = urlMatch[1] === undefined ? null : parseInt(urlMatch[1], 10); + if (urlMatch.length === 2 && urlMatch[1].length === 64) { + this.inputIndex = parseInt(urlMatch[0], 10); + this.outputIndex = null; + this.txId = urlMatch[1]; + } else { + this.txId = urlMatch[0]; + this.outputIndex = urlMatch[1] === undefined ? null : parseInt(urlMatch[1], 10); + this.inputIndex = null; + } this.seoService.setTitle( $localize`:@@bisq.transaction.browser-title:Transaction: ${this.txId}:INTERPOLATION:` ); 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 e26740aa5..e53c54a7a 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.html +++ b/frontend/src/app/components/transactions-list/transactions-list.component.html @@ -220,7 +220,7 @@ - + diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.ts b/frontend/src/app/components/transactions-list/transactions-list.component.ts index 03c2d59ee..4f3f1cec3 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.ts +++ b/frontend/src/app/components/transactions-list/transactions-list.component.ts @@ -104,7 +104,7 @@ export class TransactionsListComponent implements OnInit, OnChanges { if (changes.inputIndex || changes.outputIndex || changes.rowLimit) { this.inputRowLimit = Math.max(this.rowLimit, (this.inputIndex || 0) + 3); this.outputRowLimit = Math.max(this.rowLimit, (this.outputIndex || 0) + 3); - if (this.inputIndex || this.outputIndex) { + if ((this.inputIndex || this.outputIndex) && !changes.transactions) { setTimeout(() => { const assetBoxElements = document.getElementsByClassName('assetBox'); if (assetBoxElements && assetBoxElements[0]) { diff --git a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.html b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.html index 3f0143a67..ced3b5f57 100644 --- a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.html +++ b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.html @@ -41,6 +41,18 @@ + + + + + + + + + + + + @@ -56,6 +68,7 @@ (); @Output() selectOutput = new EventEmitter(); @@ -378,7 +380,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { const output = this.tx.vout[index]; const outspend = this.outspends[index]; if (output && outspend && outspend.spent && outspend.txid) { - this.router.navigate([this.relativeUrlPipe.transform('/tx'), outspend.txid], { + this.router.navigate([this.relativeUrlPipe.transform('/tx'), outspend.vin + ':' + outspend.txid], { queryParamsHandling: 'merge', fragment: 'flow' }); From cc5dacc896ff2d1f6a83238878186aebd33f0266 Mon Sep 17 00:00:00 2001 From: softsimon Date: Thu, 6 Oct 2022 20:33:54 +0400 Subject: [PATCH 14/76] Updating time toggle size --- .../src/app/components/blockchain/blockchain.component.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/blockchain/blockchain.component.scss b/frontend/src/app/components/blockchain/blockchain.component.scss index 2f13374fe..9b6c12a80 100644 --- a/frontend/src/app/components/blockchain/blockchain.component.scss +++ b/frontend/src/app/components/blockchain/blockchain.component.scss @@ -84,9 +84,9 @@ .time-toggle { color: white; - font-size: 1rem; + font-size: 0.8rem; position: absolute; - bottom: -1.5em; + bottom: -1.8em; left: 1px; transform: translateX(-50%); background: none; From ba50db8abaf6bfb5e8c0f992a2b3feeb1f6c0e7a Mon Sep 17 00:00:00 2001 From: softsimon Date: Thu, 6 Oct 2022 21:12:23 +0400 Subject: [PATCH 15/76] Display LN capacity on mobile fixes #2610 --- .../node-statistics/node-statistics.component.scss | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/lightning/node-statistics/node-statistics.component.scss b/frontend/src/app/lightning/node-statistics/node-statistics.component.scss index 1532f9c4b..dc69a4dae 100644 --- a/frontend/src/app/lightning/node-statistics/node-statistics.component.scss +++ b/frontend/src/app/lightning/node-statistics/node-statistics.component.scss @@ -37,8 +37,9 @@ } &.more-padding { padding-top: 10px; - } - &:first-child{ + } + &:last-child { + margin-bottom: 0; display: none; @media (min-width: 485px) { display: block; @@ -48,10 +49,7 @@ } @media (min-width: 992px) { display: block; - } - } - &:last-child { - margin-bottom: 0; + } } .card-text span { color: #ffffff66; From 0699a31531ca86ff4f3c7956d666cc5c6331a303 Mon Sep 17 00:00:00 2001 From: softsimon Date: Fri, 7 Oct 2022 00:54:33 +0400 Subject: [PATCH 16/76] Correcting all Lightning explorer i18n and extract fixes #2533 --- frontend/package.json | 2 +- .../components/graphs/graphs.component.html | 12 +- .../channel-box/channel-box.component.html | 28 +- .../channel/channel-preview.component.html | 16 +- .../lightning/channel/channel.component.html | 12 +- .../lightning/channel/channel.component.ts | 2 +- .../channels-list.component.html | 22 +- .../channels-statistics.component.html | 12 +- .../lightning-dashboard.component.html | 10 +- .../node/node-preview.component.html | 6 +- .../app/lightning/node/node.component.html | 9 +- .../src/app/lightning/node/node.component.ts | 2 +- .../nodes-channels-map.component.html | 2 +- .../nodes-channels-map.component.ts | 2 +- .../nodes-list/nodes-list.component.html | 4 +- .../nodes-map/nodes-map.component.html | 2 +- .../nodes-map/nodes-map.component.ts | 4 +- .../nodes-networks-chart.component.html | 2 +- .../nodes-networks-chart.component.ts | 10 +- .../nodes-per-country-chart.component.html | 2 +- .../nodes-per-country-chart.component.ts | 2 +- .../nodes-per-country.component.html | 2 +- .../nodes-per-isp-chart.component.html | 12 +- .../nodes-per-isp-chart.component.ts | 2 +- .../nodes-per-isp.component.html | 2 +- .../oldest-nodes/oldest-nodes.component.html | 2 +- .../top-nodes-per-capacity.component.html | 2 +- .../top-nodes-per-channels.component.html | 2 +- .../lightning-statistics-chart.component.html | 6 +- .../lightning-statistics-chart.component.ts | 6 +- frontend/src/locale/messages.xlf | 2780 ++++++++++++++--- 31 files changed, 2387 insertions(+), 592 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 18d527ccc..c5a062e01 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,7 +22,7 @@ "scripts": { "ng": "./node_modules/@angular/cli/bin/ng.js", "tsc": "./node_modules/typescript/bin/tsc", - "i18n-extract-from-source": "./node_modules/@angular/cli/bin/ng extract-i18n --out-file ./src/locale/messages.xlf", + "i18n-extract-from-source": "npm run ng -- extract-i18n --out-file ./src/locale/messages.xlf", "i18n-pull-from-transifex": "tx pull -a --parallel --minimum-perc 1 --force", "serve": "npm run generate-config && npm run ng -- serve -c local", "serve:stg": "npm run generate-config && npm run ng -- serve -c staging", diff --git a/frontend/src/app/components/graphs/graphs.component.html b/frontend/src/app/components/graphs/graphs.component.html index d6f9694d0..dd47a4ac7 100644 --- a/frontend/src/app/components/graphs/graphs.component.html +++ b/frontend/src/app/components/graphs/graphs.component.html @@ -31,17 +31,17 @@
Lightning nodes per network + i18n="lightning.nodes-networks">Lightning Nodes Per Network Network capacity + i18n="lightning.network-capacity">Lightning Network Capacity Lightning nodes per ISP + i18n="lightning.nodes-per-isp">Lightning Nodes Per ISP Lightning nodes per country + i18n="lightning.nodes-per-country">Lightning Nodes Per Country Lightning nodes world map + i18n="lightning.lightning.nodes-heatmap">Lightning Nodes World Map Lightning nodes channels world map + i18n="lightning.nodes-channels-world-map">Lightning Nodes Channels World Map
diff --git a/frontend/src/app/lightning/channel/channel-box/channel-box.component.html b/frontend/src/app/lightning/channel/channel-box/channel-box.component.html index a61273d4d..47b7fa3ba 100644 --- a/frontend/src/app/lightning/channel/channel-box/channel-box.component.html +++ b/frontend/src/app/lightning/channel/channel-box/channel-box.component.html @@ -7,7 +7,7 @@
-
{{ channel.channels }} channels
+
@@ -16,7 +16,7 @@
- + @@ -62,7 +67,7 @@ - + @@ -72,3 +77,4 @@ {{ i }} blocks +{{ i }} channels diff --git a/frontend/src/app/lightning/channel/channel-preview.component.html b/frontend/src/app/lightning/channel/channel-preview.component.html index 379de479a..fe7f45a13 100644 --- a/frontend/src/app/lightning/channel/channel-preview.component.html +++ b/frontend/src/app/lightning/channel/channel-preview.component.html @@ -7,9 +7,9 @@

{{ channel.short_id }}

- Inactive - Active - Closed + Inactive + Active + Closed
@@ -20,20 +20,20 @@
Fee rateFee rate {{ channel.fee_rate !== null ? (channel.fee_rate | amountShortener : 2 : undefined : true) : '-' }} ppm {{ channel.fee_rate !== null ? '(' + (channel.fee_rate / 10000 | amountShortener : 2 : undefined : true) + '%)' : '' }} @@ -33,19 +33,24 @@ {{ channel.base_fee_mtokens | amountShortener : 0 }} - msats + mSats - - - {{ channel.base_fee_mtokens === 0 ? 'Zero base fee' : 'Non-zero base fee' }} - + + Zero base fee + + + Non-zero base fee +
Timelock deltaTimelock delta
- + - + - + diff --git a/frontend/src/app/lightning/channel/channel.component.html b/frontend/src/app/lightning/channel/channel.component.html index 3fceab483..c25af5377 100644 --- a/frontend/src/app/lightning/channel/channel.component.html +++ b/frontend/src/app/lightning/channel/channel.component.html @@ -8,9 +8,9 @@
- Inactive - Active - Closed + Inactive + Active + Closed
@@ -45,7 +45,7 @@
CreatedCreated {{ channel.created | date:'yyyy-MM-dd HH:mm' }}
CapacityCapacity
Fee rateFee rate
- {{ channel.node_left.fee_rate }} ppm + {{ channel.node_left.fee_rate }} ppm - {{ channel.node_right.fee_rate }} ppm + {{ channel.node_right.fee_rate }} ppm
- + @@ -70,7 +70,7 @@
-

Opening transaction

+

Opening transaction

@@ -79,7 +79,7 @@
-

Closing transaction

   +

Closing transaction

   diff --git a/frontend/src/app/lightning/channel/channel.component.ts b/frontend/src/app/lightning/channel/channel.component.ts index a32414449..d64d388ea 100644 --- a/frontend/src/app/lightning/channel/channel.component.ts +++ b/frontend/src/app/lightning/channel/channel.component.ts @@ -34,7 +34,7 @@ export class ChannelComponent implements OnInit { return this.lightningApiService.getChannel$(params.get('short_id')) .pipe( tap((value) => { - this.seoService.setTitle(`Channel: ${value.short_id}`); + this.seoService.setTitle($localize`Channel: ${value.short_id}`); }), catchError((err) => { this.error = err; diff --git a/frontend/src/app/lightning/channels-list/channels-list.component.html b/frontend/src/app/lightning/channels-list/channels-list.component.html index a51e03ef8..cfa660ffb 100644 --- a/frontend/src/app/lightning/channels-list/channels-list.component.html +++ b/frontend/src/app/lightning/channels-list/channels-list.component.html @@ -2,10 +2,10 @@
@@ -32,12 +32,12 @@
- - + + - + - + @@ -53,7 +53,7 @@
CapacityCapacity
Node Alias Alias  StatusFee RateFee Rate Closing dateCapacityCapacity Channel ID
-
{{ node.channels }} channels
+
@@ -63,10 +63,10 @@
- Inactive - Active + Inactive + Active - Closed + Closed @@ -117,3 +117,5 @@
+ +{{ i }} channels diff --git a/frontend/src/app/lightning/channels-statistics/channels-statistics.component.html b/frontend/src/app/lightning/channels-statistics/channels-statistics.component.html index 60a43216c..d8cf345c0 100644 --- a/frontend/src/app/lightning/channels-statistics/channels-statistics.component.html +++ b/frontend/src/app/lightning/channels-statistics/channels-statistics.component.html @@ -14,7 +14,7 @@
{{ statistics.latest?.avg_capacity || 0 | number: '1.0-0' }} - sats + sats
@@ -29,7 +29,7 @@ placement="bottom">
{{ statistics.latest?.avg_fee_rate || 0 | number: '1.0-0' }} - ppm + ppm
@@ -44,7 +44,7 @@
{{ statistics.latest?.avg_base_fee_mtokens || 0 | number: '1.0-0' }} - msats + mSats
@@ -60,7 +60,7 @@
{{ statistics.latest?.med_capacity || 0 | number: '1.0-0' }} - sats + sats
@@ -75,7 +75,7 @@ placement="bottom">
{{ statistics.latest?.med_fee_rate || 0 | number: '1.0-0' }} - ppm + ppm
@@ -90,7 +90,7 @@
{{ statistics.latest?.med_base_fee_mtokens || 0 | number: '1.0-0' }} - msats + mSats
diff --git a/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.html b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.html index f7f505b5c..16d02807c 100644 --- a/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.html +++ b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.html @@ -7,7 +7,7 @@
- Network Statistics  + Network Statistics 
@@ -21,7 +21,7 @@
- Channels Statistics  + Channels Statistics 
@@ -46,7 +46,7 @@
-
Lightning network history
+
Lightning Network History
@@ -59,7 +59,7 @@
-
Liquidity ranking
+
Liquidity Ranking
 
@@ -73,7 +73,7 @@
-
Connectivity ranking
+
Connectivity Ranking
 
diff --git a/frontend/src/app/lightning/node/node-preview.component.html b/frontend/src/app/lightning/node/node-preview.component.html index c9b08ba3d..2c936edf4 100644 --- a/frontend/src/app/lightning/node/node-preview.component.html +++ b/frontend/src/app/lightning/node/node-preview.component.html @@ -29,13 +29,13 @@ - Average size + Average size - Location + Location {{ node.city.en }} @@ -47,7 +47,7 @@ - Location + Location unknown diff --git a/frontend/src/app/lightning/node/node.component.html b/frontend/src/app/lightning/node/node.component.html index 92f731bef..c6e3e794c 100644 --- a/frontend/src/app/lightning/node/node.component.html +++ b/frontend/src/app/lightning/node/node.component.html @@ -15,7 +15,6 @@
No node found for public key "{{ node.public_key | shortenString : 12}}" - Back to the lightning dashboard
@@ -45,7 +44,7 @@ - Location + Location @@ -61,19 +60,19 @@ - + - + - + diff --git a/frontend/src/app/lightning/node/node.component.ts b/frontend/src/app/lightning/node/node.component.ts index cbfa66c89..e2a8123ac 100644 --- a/frontend/src/app/lightning/node/node.component.ts +++ b/frontend/src/app/lightning/node/node.component.ts @@ -39,7 +39,7 @@ export class NodeComponent implements OnInit { return this.lightningApiService.getNode$(params.get('public_key')); }), map((node) => { - this.seoService.setTitle(`Node: ${node.alias}`); + this.seoService.setTitle($localize`Node: ${node.alias}`); const socketsObject = []; for (const socket of node.sockets.split(',')) { diff --git a/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.html b/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.html index c6fcabbdb..b262f58fd 100644 --- a/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.html +++ b/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.html @@ -3,7 +3,7 @@
- Lightning nodes channels world map + Lightning Nodes Channels World Map
(Tor nodes excluded)
diff --git a/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts b/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts index 22b846458..4da4e3cb4 100644 --- a/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts +++ b/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts @@ -66,7 +66,7 @@ export class NodesChannelsMap implements OnInit { } if (this.style === 'graph') { - this.seoService.setTitle($localize`Lightning nodes channels world map`); + this.seoService.setTitle($localize`Lightning Nodes Channels World Map`); } if (['nodepage', 'channelpage'].includes(this.style)) { diff --git a/frontend/src/app/lightning/nodes-list/nodes-list.component.html b/frontend/src/app/lightning/nodes-list/nodes-list.component.html index d21f0b30a..f4fe7a644 100644 --- a/frontend/src/app/lightning/nodes-list/nodes-list.component.html +++ b/frontend/src/app/lightning/nodes-list/nodes-list.component.html @@ -3,8 +3,8 @@
First seenFirst seen
Last updateLast update
ColorColor
{{ node.color }}
- - + + diff --git a/frontend/src/app/lightning/nodes-map/nodes-map.component.html b/frontend/src/app/lightning/nodes-map/nodes-map.component.html index f6a6f6009..7fed096f5 100644 --- a/frontend/src/app/lightning/nodes-map/nodes-map.component.html +++ b/frontend/src/app/lightning/nodes-map/nodes-map.component.html @@ -2,7 +2,7 @@
- Lightning nodes world map + Lightning Nodes World Map
(Tor nodes excluded)
diff --git a/frontend/src/app/lightning/nodes-map/nodes-map.component.ts b/frontend/src/app/lightning/nodes-map/nodes-map.component.ts index 5751c65f1..8d611e00f 100644 --- a/frontend/src/app/lightning/nodes-map/nodes-map.component.ts +++ b/frontend/src/app/lightning/nodes-map/nodes-map.component.ts @@ -47,7 +47,7 @@ export class NodesMap implements OnInit, OnChanges { ngOnInit(): void { if (!this.widget) { - this.seoService.setTitle($localize`Lightning nodes world map`); + this.seoService.setTitle($localize`:@@:af8560ca50882114be16c951650f83bca73161a7:Lightning Nodes World Map`); } if (!this.inputNodes$) { @@ -141,7 +141,7 @@ export class NodesMap implements OnInit, OnChanges { color: 'grey', fontSize: 15 }, - text: $localize`No data to display yet`, + text: $localize`No data to display yet. Try again later.`, left: 'center', top: 'center' }; diff --git a/frontend/src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.html b/frontend/src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.html index 82e97c969..f7047bfa7 100644 --- a/frontend/src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.html +++ b/frontend/src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.html @@ -2,7 +2,7 @@
- Lightning nodes per network + Lightning Nodes Per Network diff --git a/frontend/src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts b/frontend/src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts index c1647cd25..70e32cfe8 100644 --- a/frontend/src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts +++ b/frontend/src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts @@ -64,7 +64,7 @@ export class NodesNetworksChartComponent implements OnInit { if (this.widget) { this.miningWindowPreference = '3y'; } else { - this.seoService.setTitle($localize`Lightning nodes per network`); + this.seoService.setTitle($localize`:@@b420668a91f8ebaf6e6409c4ba87f1d45961d2bd:Lightning Nodes Per Network`); this.miningWindowPreference = this.miningService.getDefaultTimespan('all'); } this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); @@ -128,7 +128,7 @@ export class NodesNetworksChartComponent implements OnInit { color: 'grey', fontSize: 11 }, - text: $localize`Nodes per network`, + text: $localize`:@@b420668a91f8ebaf6e6409c4ba87f1d45961d2bd:Lightning Nodes Per Network`, left: 'center', top: 11, zlevel: 10, @@ -139,7 +139,7 @@ export class NodesNetworksChartComponent implements OnInit { { zlevel: 1, yAxisIndex: 0, - name: $localize`Unknown`, + name: $localize`:@@e5d8bb389c702588877f039d72178f219453a72d:Unknown`, showSymbol: false, symbol: 'none', data: data.unannounced_nodes, @@ -308,7 +308,7 @@ export class NodesNetworksChartComponent implements OnInit { icon: 'roundRect', }, { - name: $localize`Unknown`, + name: $localize`:@@e5d8bb389c702588877f039d72178f219453a72d:Unknown`, inactiveColor: 'rgb(110, 112, 121)', textStyle: { color: 'white', @@ -320,7 +320,7 @@ export class NodesNetworksChartComponent implements OnInit { '$localize`Reachable on Darknet Only`': true, '$localize`Reachable on Clearnet Only`': true, '$localize`Reachable on Clearnet and Darknet`': true, - '$localize`Unknown`': true, + '$localize`:@@e5d8bb389c702588877f039d72178f219453a72d:Unknown`': true, } }, yAxis: data.tor_nodes.length === 0 ? undefined : [ diff --git a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html index a8cfdcfb4..9928d57a8 100644 --- a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html +++ b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html @@ -2,7 +2,7 @@
- Lightning nodes per country + Lightning Nodes Per Country diff --git a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts index bf4a660c1..03d6967fe 100644 --- a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts +++ b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts @@ -43,7 +43,7 @@ export class NodesPerCountryChartComponent implements OnInit { } ngOnInit(): void { - this.seoService.setTitle($localize`Lightning nodes per country`); + this.seoService.setTitle($localize`:@@9d3ad4c6623870d96b65fb7a708fed6ce7c20044:Lightning Nodes Per Country`); this.nodesPerCountryObservable$ = this.apiService.getNodesPerCountry$() .pipe( diff --git a/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.html b/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.html index 543cf951c..b1c84e2fc 100644 --- a/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.html +++ b/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.html @@ -58,7 +58,7 @@
- + diff --git a/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html b/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html index 093a8ad1a..a72540c70 100644 --- a/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html +++ b/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html @@ -3,21 +3,21 @@
-
Clearnet capacity
+
Clearnet Capacity

-
Unknown capacity
+
Unknown Capacity

-
Tor capacity
+
Tor Capacity

@@ -80,19 +80,19 @@

-
Clearnet capacity
+
Clearnet Capacity

-
Unknown capacity
+
Unknown Capacity

-
Tor capacity
+
Tor Capacity

diff --git a/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts b/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts index 5150f84a7..3299c529c 100644 --- a/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts +++ b/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts @@ -48,7 +48,7 @@ export class NodesPerISPChartComponent implements OnInit { ngOnInit(): void { if (!this.widget) { - this.seoService.setTitle($localize`Lightning nodes per ISP`); + this.seoService.setTitle($localize`:@@8573a1576789bd2c4faeaed23037c4917812c6cf:Lightning Nodes Per ISP`); } this.nodesPerAsObservable$ = combineLatest([ diff --git a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.html b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.html index 441dc429e..a2f2a9b29 100644 --- a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.html +++ b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp.component.html @@ -55,7 +55,7 @@
- + diff --git a/frontend/src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.html b/frontend/src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.html index e82ff0ac8..40df83a53 100644 --- a/frontend/src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.html +++ b/frontend/src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.html @@ -8,7 +8,7 @@ - + diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html index 80e93f7ac..c62716624 100644 --- a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html @@ -10,7 +10,7 @@ - + diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html index ef2a05659..f321573c1 100644 --- a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html @@ -10,7 +10,7 @@ - + diff --git a/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.html b/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.html index a4401147d..c4817f5c3 100644 --- a/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.html +++ b/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.html @@ -2,7 +2,7 @@
- Channels & Capacity + Lightning Network Capacity @@ -49,9 +49,7 @@
-
- Indexing in progress -
+
Indexing in progress
\ No newline at end of file diff --git a/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts b/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts index e4b71ae41..24a365a39 100644 --- a/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts +++ b/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts @@ -63,7 +63,7 @@ export class LightningStatisticsChartComponent implements OnInit { if (this.widget) { this.miningWindowPreference = '3y'; } else { - this.seoService.setTitle($localize`Channels and Capacity`); + this.seoService.setTitle($localize`:@@ea8db27e6db64f8b940711948c001a1100e5fe9f:Lightning Network Capacity`); this.miningWindowPreference = this.miningService.getDefaultTimespan('all'); } this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); @@ -119,7 +119,7 @@ export class LightningStatisticsChartComponent implements OnInit { color: 'grey', fontSize: 11 }, - text: $localize`Channels & Capacity`, + text: $localize`:@@ea8db27e6db64f8b940711948c001a1100e5fe9f:Lightning Network Capacity`, left: 'center', top: 11, zlevel: 10, @@ -341,7 +341,7 @@ export class LightningStatisticsChartComponent implements OnInit { this.chartInstance.setOption(this.chartOptions); download(this.chartInstance.getDataURL({ pixelRatio: 2, - }), `block-sizes-weights-${this.timespan}-${Math.round(now.getTime() / 1000)}.svg`); + }), `lightning-network-capacity-${this.timespan}-${Math.round(now.getTime() / 1000)}.svg`); // @ts-ignore this.chartOptions.grid.bottom = prevBottom; this.chartOptions.backgroundColor = 'none'; diff --git a/frontend/src/locale/messages.xlf b/frontend/src/locale/messages.xlf index b280b9e7a..fd5e19096 100644 --- a/frontend/src/locale/messages.xlf +++ b/frontend/src/locale/messages.xlf @@ -249,6 +249,10 @@ src/app/bisq/bisq-address/bisq-address.component.html2 + + src/app/components/address/address-preview.component.html + 3 + src/app/components/address/address.component.html 3 @@ -261,6 +265,10 @@ src/app/bisq/bisq-address/bisq-address.component.html 22 + + src/app/components/address/address-preview.component.html + 23 + src/app/components/address/address.component.html 31 @@ -277,6 +285,10 @@ src/app/bisq/bisq-blocks/bisq-blocks.component.html 14,15 + + src/app/components/address/address-preview.component.html + 27 + src/app/components/address/address.component.html 35 @@ -289,6 +301,10 @@ src/app/bisq/bisq-address/bisq-address.component.html 30 + + src/app/components/address/address-preview.component.html + 32 + src/app/components/address/address.component.html 40 @@ -307,7 +323,7 @@ src/app/components/block/block.component.html - 310,311 + 295,296 src/app/components/blockchain-blocks/blockchain-blocks.component.html @@ -331,7 +347,7 @@ src/app/components/block/block.component.html - 311,312 + 296,297 src/app/components/blockchain-blocks/blockchain-blocks.component.html @@ -356,10 +372,6 @@ src/app/bisq/bisq-block/bisq-block.component.html 4 - - src/app/components/block/block.component.html - 16,17 - shared.block-title @@ -372,9 +384,13 @@ src/app/bisq/bisq-block/bisq-block.component.html 82 + + src/app/components/block-audit/block-audit.component.html + 28,29 + src/app/components/block/block.component.html - 52,53 + 40,41 block.hash @@ -392,9 +408,17 @@ src/app/bisq/bisq-transaction/bisq-transaction.component.html34,36 + + src/app/components/block-audit/block-audit.component.html + 34,36 + + + src/app/components/block/block-preview.component.html + 26,28 + src/app/components/block/block.component.html - 56,58 + 44,46 src/app/components/blocks-list/blocks-list.component.html @@ -492,9 +516,17 @@ src/app/bisq/bisq-transactions/bisq-transactions.component.ts 81 + + src/app/components/address/address-preview.component.html + 36 + src/app/components/bisq-master-page/bisq-master-page.component.html - 34,36 + 63,65 + + + src/app/components/block-audit/block-audit.component.html + 60,64 src/app/components/blocks-list/blocks-list.component.html @@ -514,7 +546,7 @@ src/app/components/bisq-master-page/bisq-master-page.component.html - 37,39 + 66,68 src/app/components/blocks-list/blocks-list.component.html @@ -522,11 +554,11 @@ src/app/components/liquid-master-page/liquid-master-page.component.html - 35,37 + 68,70 src/app/components/master-page/master-page.component.html - 39,41 + 49,51 src/app/components/pool-ranking/pool-ranking.component.html @@ -677,6 +709,14 @@ src/app/components/mining-dashboard/mining-dashboard.component.html 43 + + src/app/lightning/lightning-dashboard/lightning-dashboard.component.html + 40 + + + src/app/lightning/lightning-dashboard/lightning-dashboard.component.html + 52 + dashboard.view-more @@ -687,7 +727,7 @@ src/app/components/about/about.component.html - 385,389 + 381,385 src/app/dashboard/dashboard.component.html @@ -839,6 +879,10 @@ src/app/bisq/bisq-stats/bisq-stats.component.html 70 + + src/app/components/address/address-preview.component.html + 40 + BSQ unspent transaction outputs @@ -943,11 +987,11 @@ src/app/components/block/block.component.html - 267,268 + 252,253 src/app/components/transaction/transaction.component.html - 230,232 + 272,274 transaction.version @@ -965,6 +1009,10 @@ src/app/components/block-overview-tooltip/block-overview-tooltip.component.html12 + + src/app/components/transaction/transaction-preview.component.html + 3 + src/app/components/transaction/transaction.component.html 13,16 @@ -1072,11 +1120,11 @@ src/app/components/transaction/transaction.component.html - 204,209 + 246,251 src/app/components/transaction/transaction.component.html - 316,322 + 390,396 transaction.details @@ -1092,11 +1140,11 @@ src/app/components/transaction/transaction.component.html - 194,197 + 233,237 src/app/components/transaction/transaction.component.html - 287,293 + 361,367 Transaction inputs and outputstransaction.inputs-and-outputs @@ -1107,9 +1155,13 @@ src/app/bisq/bisq-transaction/bisq-transaction.component.ts50 + + src/app/components/transaction/transaction-preview.component.ts + 106 + src/app/components/transaction/transaction.component.ts - 114,113 + 127,126 @@ -1174,9 +1226,13 @@ src/app/bisq/bisq-transactions/bisq-transactions.component.ts 34 + + src/app/components/block/block-preview.component.html + 10,11 + src/app/components/block/block.component.html - 5,7 + 6,8 @@ -1292,31 +1348,11 @@ 13,17 - - Become a sponsor ❤️ - - src/app/components/about/about.component.html - 30,31 - - about.become-a-sponsor - - - Navigate to https://mempool.space/sponsor to sponsor - - src/app/components/about/about.component.html - 31 - - - src/app/components/sponsor/sponsor.component.html - 10 - - about.navigate-to-sponsor - Enterprise Sponsors 🚀 src/app/components/about/about.component.html - 35,38 + 29,32 about.sponsors.enterprise.withRocket @@ -1324,31 +1360,23 @@ Community Sponsors ❤️ src/app/components/about/about.component.html - 183,186 + 177,180 about.sponsors.withHeart - - Self-Hosted Integrations + + Community Integrations src/app/components/about/about.component.html - 197,199 + 191,193 - about.self-hosted-integrations - - - Wallet Integrations - - src/app/components/about/about.component.html - 231,233 - - about.wallet-integrations + about.community-integrations Community Alliances src/app/components/about/about.component.html - 285,287 + 281,283 about.alliances @@ -1356,7 +1384,7 @@ Project Translators src/app/components/about/about.component.html - 301,303 + 297,299 about.translators @@ -1364,7 +1392,7 @@ Project Contributors src/app/components/about/about.component.html - 315,317 + 311,313 about.contributors @@ -1372,7 +1400,7 @@ Project Members src/app/components/about/about.component.html - 327,329 + 323,325 about.project_members @@ -1380,7 +1408,7 @@ Project Maintainers src/app/components/about/about.component.html - 340,342 + 336,338 about.maintainers @@ -1392,32 +1420,87 @@ src/app/components/bisq-master-page/bisq-master-page.component.html - 46,49 + 75,78 src/app/components/liquid-master-page/liquid-master-page.component.html - 52,55 + 85,88 src/app/components/master-page/master-page.component.html - 51,54 + 58,61 - Multisig of + Multisig of src/app/components/address-labels/address-labels.component.ts - 127 + 105 Unconfidential + + src/app/components/address/address-preview.component.html + 15 + src/app/components/address/address.component.html 23 address.unconfidential + + Confidential + + src/app/components/address/address-preview.component.html + 56 + + + src/app/components/address/address.component.html + 154 + + + src/app/components/amount/amount.component.html + 6,9 + + + src/app/components/asset-circulation/asset-circulation.component.html + 2,4 + + + src/app/components/asset/asset.component.html + 163 + + + src/app/components/transaction/transaction-preview.component.html + 21 + + + src/app/components/transactions-list/transactions-list.component.html + 288,290 + + + src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html + 49 + + + src/app/dashboard/dashboard.component.html + 131,132 + + shared.confidential + + + Address: + + src/app/components/address/address-preview.component.ts + 70 + + + src/app/components/address/address.component.ts + 78 + + of transaction @@ -1450,41 +1533,6 @@ Electrum server limit exceeded error - - Confidential - - src/app/components/address/address.component.html - 154 - - - src/app/components/amount/amount.component.html - 6,9 - - - src/app/components/asset-circulation/asset-circulation.component.html - 2,4 - - - src/app/components/asset/asset.component.html - 163 - - - src/app/components/transactions-list/transactions-list.component.html - 288,290 - - - src/app/dashboard/dashboard.component.html - 131,132 - - shared.confidential - - - Address: - - src/app/components/address/address.component.ts - 78 - - Asset @@ -1508,6 +1556,10 @@ src/app/components/assets/assets.component.html 29,31 + + src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html + 28,30 + Asset name header @@ -1643,7 +1695,7 @@ src/app/components/liquid-master-page/liquid-master-page.component.html - 46,48 + 79,81 Assets page header @@ -1662,7 +1714,7 @@ src/app/components/pool-ranking/pool-ranking.component.html - 70,76 + 72,78 src/app/components/pool/pool.component.html @@ -1745,15 +1797,15 @@ Offline src/app/components/bisq-master-page/bisq-master-page.component.html - 7,8 + 36,37 src/app/components/liquid-master-page/liquid-master-page.component.html - 8,9 + 41,42 src/app/components/master-page/master-page.component.html - 9,10 + 14,15 master-page.offline @@ -1761,15 +1813,15 @@ Reconnecting... src/app/components/bisq-master-page/bisq-master-page.component.html - 8,13 + 37,42 src/app/components/liquid-master-page/liquid-master-page.component.html - 9,14 + 42,47 src/app/components/master-page/master-page.component.html - 10,15 + 15,20 master-page.reconnecting @@ -1777,15 +1829,15 @@ Layer 2 Networks src/app/components/bisq-master-page/bisq-master-page.component.html - 21,22 + 50,51 src/app/components/liquid-master-page/liquid-master-page.component.html - 22,23 + 55,56 src/app/components/master-page/master-page.component.html - 23,24 + 28,29 master-page.layer2-networks-header @@ -1793,15 +1845,15 @@ Dashboard src/app/components/bisq-master-page/bisq-master-page.component.html - 31,33 + 60,62 src/app/components/liquid-master-page/liquid-master-page.component.html - 32,34 + 65,67 src/app/components/master-page/master-page.component.html - 33,35 + 38,40 master-page.dashboard @@ -1809,7 +1861,7 @@ Stats src/app/components/bisq-master-page/bisq-master-page.component.html - 40,42 + 69,71 master-page.stats @@ -1817,19 +1869,151 @@ Docs src/app/components/bisq-master-page/bisq-master-page.component.html - 43,45 + 72,74 src/app/components/liquid-master-page/liquid-master-page.component.html - 49,51 + 82,84 master-page.docs + + Block + + src/app/components/block-audit/block-audit.component.html + 7,9 + + shared.block-title + + + Template vs Mined + + src/app/components/block-audit/block-audit.component.html + 11,17 + + shared.template-vs-mined + + + Size + + src/app/components/block-audit/block-audit.component.html + 44,46 + + + src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts + 180,179 + + + src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts + 226,224 + + + src/app/components/block/block.component.html + 50,52 + + + src/app/components/blocks-list/blocks-list.component.html + 23,25 + + + src/app/components/mempool-block/mempool-block.component.html + 32,35 + + + src/app/components/mempool-graph/mempool-graph.component.ts + 260 + + + src/app/components/pool/pool.component.html + 219,222 + + + src/app/components/pool/pool.component.html + 266,270 + + + src/app/components/transaction/transaction.component.html + 254,256 + + + src/app/dashboard/dashboard.component.html + 91,94 + + blockAudit.size + + + Weight + + src/app/components/block-audit/block-audit.component.html + 48,49 + + + src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts + 188,187 + + + src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts + 257,254 + + + src/app/components/block/block-preview.component.html + 32,34 + + + src/app/components/block/block.component.html + 54,56 + + + src/app/components/transaction/transaction.component.html + 262,264 + + block.weight + + + Match rate + + src/app/components/block-audit/block-audit.component.html + 64,67 + + block.match-rate + + + Missing txs + + src/app/components/block-audit/block-audit.component.html + 68,71 + + block.missing-txs + + + Added txs + + src/app/components/block-audit/block-audit.component.html + 72,75 + + block.added-txs + + + Missing + + src/app/components/block-audit/block-audit.component.html + 84,85 + + block.missing-txs + + + Added + + src/app/components/block-audit/block-audit.component.html + 86,92 + + block.added-txs + Block Fee Rates src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.html - 5,7 + 6,8 src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts @@ -1837,7 +2021,7 @@ src/app/components/graphs/graphs.component.html - 14 + 18 mining.block-fee-rates @@ -1847,17 +2031,9 @@ src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts 188 - - src/app/components/block-fees-graph/block-fees-graph.component.ts - 137 - src/app/components/block-prediction-graph/block-prediction-graph.component.ts - 128 - - - src/app/components/block-rewards-graph/block-rewards-graph.component.ts - 135 + 142 src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts @@ -1870,17 +2046,9 @@ src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts 190 - - src/app/components/block-fees-graph/block-fees-graph.component.ts - 139 - src/app/components/block-prediction-graph/block-prediction-graph.component.ts - 130 - - - src/app/components/block-rewards-graph/block-rewards-graph.component.ts - 137 + 144 src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts @@ -1891,35 +2059,51 @@ Block Fees src/app/components/block-fees-graph/block-fees-graph.component.html - 5,7 + 6,7 src/app/components/block-fees-graph/block-fees-graph.component.ts - 60 + 62 src/app/components/graphs/graphs.component.html - 16 + 20 mining.block-fees - - Fees + + Indexing blocks src/app/components/block-fees-graph/block-fees-graph.component.ts - 175,173 + 110,105 - src/app/components/blocks-list/blocks-list.component.html - 19,20 + src/app/components/block-rewards-graph/block-rewards-graph.component.ts + 108,103 - src/app/components/pool/pool.component.html - 217,219 + src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts + 115,110 - src/app/components/pool/pool.component.html - 264,266 + src/app/components/hashrate-chart/hashrate-chart.component.ts + 171,166 + + + src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts + 167,162 + + + src/app/components/indexing-progress/indexing-progress.component.html + 1 + + + src/app/components/pool/pool-preview.component.ts + 122,117 + + + src/app/components/pool/pool.component.ts + 114,109 @@ -1930,7 +2114,7 @@ src/app/components/transaction/transaction.component.html - 386 + 460 Transaction feetransaction.fee @@ -1941,9 +2125,13 @@ src/app/components/block-overview-tooltip/block-overview-tooltip.component.html23 + + src/app/components/transaction/transaction-preview.component.html + 27 + src/app/components/transaction/transaction.component.html - 386,387 + 460,461 src/app/components/transactions-list/transactions-list.component.html @@ -1964,7 +2152,15 @@ src/app/components/transaction/transaction.component.html - 389,391 + 463,465 + + + src/app/lightning/channel/channel-box/channel-box.component.html + 19 + + + src/app/lightning/channel/channel-preview.component.html + 31,34 Transaction fee ratetransaction.fee-rate @@ -1976,12 +2172,16 @@ 28 - src/app/components/block/block.component.html - 75 + src/app/components/block/block-preview.component.html + 37,40 src/app/components/block/block.component.html - 172 + 60 + + + src/app/components/block/block.component.html + 157 src/app/components/blockchain-blocks/blockchain-blocks.component.html @@ -2023,6 +2223,10 @@ src/app/components/mempool-blocks/mempool-blocks.component.html 13,16 + + src/app/components/transaction/transaction-preview.component.html + 39 + src/app/components/transaction/transaction.component.html 169,170 @@ -2033,11 +2237,11 @@ src/app/components/transaction/transaction.component.html - 391,394 + 465,468 src/app/components/transaction/transaction.component.html - 402,404 + 476,478 src/app/components/transactions-list/transactions-list.component.html @@ -2066,7 +2270,7 @@ src/app/components/transaction/transaction.component.html - 216,219 + 258,261 Transaction Virtual Sizetransaction.vsize @@ -2075,7 +2279,7 @@ Block Prediction Accuracy src/app/components/block-prediction-graph/block-prediction-graph.component.html - 5,7 + 6,8 src/app/components/block-prediction-graph/block-prediction-graph.component.ts @@ -2083,61 +2287,49 @@ src/app/components/graphs/graphs.component.html - 22 + 26 mining.block-prediction-accuracy + + No data to display yet. Try again later. + + src/app/components/block-prediction-graph/block-prediction-graph.component.ts + 108,103 + + + src/app/lightning/nodes-map/nodes-map.component.ts + 144,139 + + Match rate src/app/components/block-prediction-graph/block-prediction-graph.component.ts - 176,174 + 189,187 Block Rewards src/app/components/block-rewards-graph/block-rewards-graph.component.html - 6,8 + 7,8 src/app/components/block-rewards-graph/block-rewards-graph.component.ts - 58 + 60 src/app/components/graphs/graphs.component.html - 18 + 22 mining.block-rewards - - Reward - - src/app/components/block-rewards-graph/block-rewards-graph.component.ts - 175,173 - - - src/app/components/blocks-list/blocks-list.component.html - 18,19 - - - src/app/components/blocks-list/blocks-list.component.html - 18,19 - - - src/app/components/pool/pool.component.html - 216,218 - - - src/app/components/pool/pool.component.html - 263,265 - - Block Sizes and Weights src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.html - 4,6 + 5,7 src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts @@ -2145,104 +2337,113 @@ src/app/components/graphs/graphs.component.html - 20 + 24 mining.block-sizes-weights - - Indexing blocks + + Block - src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts - 115,110 - - - src/app/components/hashrate-chart/hashrate-chart.component.ts - 171,166 - - - src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts - 167,162 - - - src/app/components/indexing-progress/indexing-progress.component.html - 1 - - - src/app/components/pool/pool.component.ts - 114,109 - - - - Size - - src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts - 180,179 - - - src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts - 226,224 + src/app/components/block/block-preview.component.html + 3,7 src/app/components/block/block.component.html - 65,67 + 5,6 + + shared.block-title + + + + + src/app/components/block/block-preview.component.html + 11,12 + + shared.block-title + + + Median fee + + src/app/components/block/block-preview.component.html + 36,37 - src/app/components/blocks-list/blocks-list.component.html - 23,25 + src/app/components/block/block.component.html + 59,60 + + + src/app/components/block/block.component.html + 156,157 src/app/components/mempool-block/mempool-block.component.html - 32,35 - - - src/app/components/mempool-graph/mempool-graph.component.ts - 260 - - - src/app/components/pool/pool.component.html - 219,222 - - - src/app/components/pool/pool.component.html - 266,270 - - - src/app/components/transaction/transaction.component.html - 212,214 - - - src/app/dashboard/dashboard.component.html - 91,94 + 16,17 + block.median-fee - - Weight + + Total fees - src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts - 188,187 - - - src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts - 257,254 + src/app/components/block/block-preview.component.html + 41,43 src/app/components/block/block.component.html - 69,71 + 64,65 - src/app/components/transaction/transaction.component.html - 220,222 + src/app/components/block/block.component.html + 90,92 + + + src/app/components/block/block.component.html + 161,163 + + + src/app/components/block/block.component.html + 187,190 + + + src/app/components/mempool-block/mempool-block.component.html + 24,25 + + Total fees in a block + block.total-fees + + + Miner + + src/app/components/block/block-preview.component.html + 53,55 + + + src/app/components/block/block.component.html + 99,101 + + + src/app/components/block/block.component.html + 196,198 + + block.miner + + + Block : + + src/app/components/block/block-preview.component.ts + 98 + + + src/app/components/block/block.component.ts + 201 Next Block src/app/components/block/block.component.html - 7,8 - - - src/app/components/block/block.component.html - 19,20 + 8,9 src/app/components/mempool-block/mempool-block.component.ts @@ -2254,35 +2455,19 @@ Previous Block src/app/components/block/block.component.html - 26,27 + 15,16 Previous Block - - Median fee - - src/app/components/block/block.component.html - 74,75 - - - src/app/components/block/block.component.html - 171,172 - - - src/app/components/mempool-block/mempool-block.component.html - 16,17 - - block.median-fee - Based on average native segwit transaction of 140 vBytes src/app/components/block/block.component.html - 75,77 + 60,62 src/app/components/block/block.component.html - 172,174 + 157,159 src/app/components/fees-box/fees-box.component.html @@ -2306,15 +2491,15 @@ Transaction fee tooltip - - Total fees + + Subsidy + fees: src/app/components/block/block.component.html - 79,80 + 79,81 src/app/components/block/block.component.html - 105,107 + 94,98 src/app/components/block/block.component.html @@ -2322,53 +2507,16 @@ src/app/components/block/block.component.html - 202,205 - - - src/app/components/mempool-block/mempool-block.component.html - 24,25 - - Total fees in a block - block.total-fees - - - Subsidy + fees: - - src/app/components/block/block.component.html - 94,96 - - - src/app/components/block/block.component.html - 109,113 - - - src/app/components/block/block.component.html - 191,193 - - - src/app/components/block/block.component.html - 206,210 + 191,195 Total subsidy and fees in a block block.subsidy-and-fees - - Miner - - src/app/components/block/block.component.html - 114,116 - - - src/app/components/block/block.component.html - 211,213 - - block.miner - Bits src/app/components/block/block.component.html - 271,273 + 256,258 block.bits @@ -2376,7 +2524,7 @@ Merkle root src/app/components/block/block.component.html - 275,277 + 260,262 block.merkle-root @@ -2384,7 +2532,7 @@ Difficulty src/app/components/block/block.component.html - 285,288 + 270,273 src/app/components/difficulty-adjustments-table/difficulty-adjustments-table.component.html @@ -2396,15 +2544,15 @@ src/app/components/hashrate-chart/hashrate-chart.component.html - 70,72 + 75,77 src/app/components/hashrate-chart/hashrate-chart.component.ts - 280,279 + 284,283 src/app/components/hashrate-chart/hashrate-chart.component.ts - 363,360 + 371,368 block.difficulty @@ -2412,7 +2560,7 @@ Nonce src/app/components/block/block.component.html - 289,291 + 274,276 block.nonce @@ -2420,7 +2568,7 @@ Block Header Hex src/app/components/block/block.component.html - 293,294 + 278,279 block.header @@ -2428,11 +2576,19 @@ Details src/app/components/block/block.component.html - 304,308 + 289,293 src/app/components/transaction/transaction.component.html - 197,201 + 238,243 + + + src/app/lightning/channel/channel.component.html + 75,77 + + + src/app/lightning/channel/channel.component.html + 85,87 Transaction Detailstransaction.details @@ -2441,21 +2597,30 @@ Error loading data. src/app/components/block/block.component.html - 323,325 + 308,310 src/app/components/block/block.component.html - 359,363 + 344,348 + + + src/app/lightning/channel/channel-preview.component.html + 70,75 + + + src/app/lightning/channel/channel.component.html + 98,104 + + + src/app/lightning/node/node-preview.component.html + 66,69 + + + src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.html + 61,64 error.general-loading-data - - Block : - - src/app/components/block/block.component.ts - 175 - - Pool @@ -2496,6 +2661,42 @@ latest-blocks.mined + + Reward + + src/app/components/blocks-list/blocks-list.component.html + 18,19 + + + src/app/components/blocks-list/blocks-list.component.html + 18,19 + + + src/app/components/pool/pool.component.html + 216,218 + + + src/app/components/pool/pool.component.html + 263,265 + + latest-blocks.reward + + + Fees + + src/app/components/blocks-list/blocks-list.component.html + 19,20 + + + src/app/components/pool/pool.component.html + 217,219 + + + src/app/components/pool/pool.component.html + 264,266 + + latest-blocks.fees + TXs @@ -2528,7 +2729,7 @@ Copied! src/app/components/clipboard/clipboard.component.ts - 15 + 19 @@ -2590,6 +2791,10 @@ src/app/components/mempool-blocks/mempool-blocks.component.html 35,36 + + src/app/lightning/channel/channel-box/channel-box.component.html + 79 + shared.blocks @@ -2792,7 +2997,7 @@ Mining src/app/components/graphs/graphs.component.html - 5 + 8 mining @@ -2800,11 +3005,11 @@ Pools Ranking src/app/components/graphs/graphs.component.html - 8 + 11 src/app/components/pool-ranking/pool-ranking.component.html - 35,37 + 36,37 mining.pools @@ -2812,30 +3017,122 @@ Pools Dominance src/app/components/graphs/graphs.component.html - 10 + 13 src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.html - 6,8 + 7,8 mining.pools-dominance - + Hashrate & Difficulty src/app/components/graphs/graphs.component.html - 12 - - - src/app/components/hashrate-chart/hashrate-chart.component.html - 23,25 - - - src/app/components/hashrate-chart/hashrate-chart.component.ts - 73 + 15,16 mining.hashrate-difficulty + + Lightning + + src/app/components/graphs/graphs.component.html + 31 + + lightning + + + Lightning Nodes Per Network + + src/app/components/graphs/graphs.component.html + 34 + + + src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.html + 5,7 + + + src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts + 67 + + + src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts + 131,126 + + lightning.nodes-networks + + + Lightning Network Capacity + + src/app/components/graphs/graphs.component.html + 36 + + + src/app/lightning/statistics-chart/lightning-statistics-chart.component.html + 5,7 + + + src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts + 66 + + + src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts + 122,117 + + lightning.network-capacity + + + Lightning Nodes Per ISP + + src/app/components/graphs/graphs.component.html + 38 + + + src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts + 51 + + lightning.nodes-per-isp + + + Lightning Nodes Per Country + + src/app/components/graphs/graphs.component.html + 40 + + + src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html + 5,7 + + + src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts + 46 + + lightning.nodes-per-country + + + Lightning Nodes World Map + + src/app/components/graphs/graphs.component.html + 42 + + + src/app/lightning/nodes-map/nodes-map.component.html + 5,7 + + lightning.lightning.nodes-heatmap + + + Lightning Nodes Channels World Map + + src/app/components/graphs/graphs.component.html + 44 + + + src/app/lightning/nodes-channels-map/nodes-channels-map.component.html + 6,8 + + lightning.nodes-channels-world-map + Hashrate @@ -2844,31 +3141,47 @@ src/app/components/hashrate-chart/hashrate-chart.component.html - 64,66 + 69,71 src/app/components/hashrate-chart/hashrate-chart.component.ts - 269,268 + 273,272 src/app/components/hashrate-chart/hashrate-chart.component.ts - 351,349 + 359,356 src/app/components/pool-ranking/pool-ranking.component.html 93,95 + + src/app/components/pool/pool-preview.component.html + 22,23 + mining.hashrate + + Hashrate & Difficulty + + src/app/components/hashrate-chart/hashrate-chart.component.html + 27,29 + + + src/app/components/hashrate-chart/hashrate-chart.component.ts + 73 + + mining.hashrate-difficulty + Hashrate (MA) src/app/components/hashrate-chart/hashrate-chart.component.ts - 288,287 + 292,291 src/app/components/hashrate-chart/hashrate-chart.component.ts - 374,372 + 382,380 @@ -2896,11 +3209,11 @@ Graphs src/app/components/liquid-master-page/liquid-master-page.component.html - 38,41 + 71,74 src/app/components/master-page/master-page.component.html - 42,44 + 52,54 src/app/components/statistics/statistics.component.ts @@ -2912,7 +3225,7 @@ Mining Dashboard src/app/components/master-page/master-page.component.html - 36,38 + 41,43 src/app/components/mining-dashboard/mining-dashboard.component.ts @@ -2920,23 +3233,27 @@ mining.mining-dashboard - - TV view + + Lightning Explorer src/app/components/master-page/master-page.component.html - 45,47 + 44,45 + master-page.lightning + + + beta - src/app/components/television/television.component.ts - 37 + src/app/components/master-page/master-page.component.html + 45,48 - master-page.tvview + beta Documentation src/app/components/master-page/master-page.component.html - 48,50 + 55,57 src/app/docs/docs/docs.component.html @@ -3094,6 +3411,14 @@ src/app/components/pool-ranking/pool-ranking.component.html 90,92 + + src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html + 27,29 + + + src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html + 57,59 + mining.rank @@ -3132,22 +3457,34 @@ Mining Pools src/app/components/pool-ranking/pool-ranking.component.ts - 56 + 57 blocks src/app/components/pool-ranking/pool-ranking.component.ts - 162,160 + 165,163 src/app/components/pool-ranking/pool-ranking.component.ts - 165,164 + 168,167 + + mining pool + + src/app/components/pool/pool-preview.component.html + 3,5 + + mining.pools + Tags + + src/app/components/pool/pool-preview.component.html + 18,19 + src/app/components/pool/pool.component.html 22,23 @@ -3182,7 +3519,7 @@ src/app/components/transactions-list/transactions-list.component.html - 262,264 + 262,265 show-all @@ -3355,7 +3692,7 @@ src/app/components/transaction/transaction.component.html - 238,239 + 280,281 transaction.hex @@ -3444,11 +3781,11 @@ mining.average-fee - - TXID, block height, hash or address + + Explore the full Bitcoin ecosystem src/app/components/search-form/search-form.component.html - 4 + 4,6 search-form.searchbar-placeholder @@ -3456,54 +3793,10 @@ Search src/app/components/search-form/search-form.component.html - 7 + 11,18 search-form.search-title - - Sponsor - - src/app/components/sponsor/sponsor.component.html - 7 - - - src/app/components/sponsor/sponsor.component.ts - 34 - - sponsor.title - - - Request invoice - - src/app/components/sponsor/sponsor.component.html - 53 - - about.sponsor.request-invoice - - - Waiting for transaction... - - src/app/components/sponsor/sponsor.component.html - 142 - - about.sponsor.waiting-for-transaction - - - Donation confirmed! - - src/app/components/sponsor/sponsor.component.html - 148 - - about.sponsor.donation-confirmed - - - Thank you! - - src/app/components/sponsor/sponsor.component.html - 149 - - about.sponsor.thank-you - Mempool by vBytes (sat/vByte) @@ -3512,11 +3805,23 @@ statistics.memory-by-vBytes + + TV view + + src/app/components/statistics/statistics.component.html + 18 + + + src/app/components/television/television.component.ts + 37 + + master-page.tvview + Filter src/app/components/statistics/statistics.component.html - 49 + 57 statistics.component-filter.title @@ -3524,7 +3829,7 @@ Invert src/app/components/statistics/statistics.component.html - 68 + 76 statistics.component-invert.title @@ -3532,7 +3837,7 @@ Transaction vBytes per second (vB/s) src/app/components/statistics/statistics.component.html - 88 + 96 statistics.transaction-vbytes-per-second @@ -3540,7 +3845,7 @@ Just now src/app/components/time-since/time-since.component.ts - 57 + 64 src/app/components/time-span/time-span.component.ts @@ -3551,31 +3856,15 @@ ago src/app/components/time-since/time-since.component.ts - 67 + 74 src/app/components/time-since/time-since.component.ts - 68 + 75 src/app/components/time-since/time-since.component.ts - 69 - - - src/app/components/time-since/time-since.component.ts - 70 - - - src/app/components/time-since/time-since.component.ts - 71 - - - src/app/components/time-since/time-since.component.ts - 72 - - - src/app/components/time-since/time-since.component.ts - 73 + 76 src/app/components/time-since/time-since.component.ts @@ -3595,15 +3884,31 @@ src/app/components/time-since/time-since.component.ts - 81 + 84 src/app/components/time-since/time-since.component.ts - 82 + 85 src/app/components/time-since/time-since.component.ts - 83 + 86 + + + src/app/components/time-since/time-since.component.ts + 87 + + + src/app/components/time-since/time-since.component.ts + 88 + + + src/app/components/time-since/time-since.component.ts + 89 + + + src/app/components/time-since/time-since.component.ts + 90 @@ -3728,6 +4033,15 @@ 96 + + Fee + + src/app/components/transaction/transaction-preview.component.html + 27 + + Transaction fee + transaction.fee + This transaction has been replaced by: @@ -3756,6 +4070,30 @@ src/app/components/transaction/transaction.component.html 101,102 + + src/app/lightning/node/node.component.html + 63,66 + + + src/app/lightning/nodes-per-country/nodes-per-country.component.html + 61,63 + + + src/app/lightning/nodes-per-isp/nodes-per-isp.component.html + 58,60 + + + src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.html + 11,13 + + + src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html + 13,15 + + + src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html + 13,15 + Transaction first seen transaction.first-seen @@ -3795,11 +4133,56 @@ Transaction Ancestortransaction.ancestor + + Flow + + src/app/components/transaction/transaction.component.html + 195,198 + + + src/app/components/transaction/transaction.component.html + 330,334 + + Transaction flow + transaction.flow + + + Hide flow diagram + + src/app/components/transaction/transaction.component.html + 198,203 + + hide-flow-diagram + + + Show more + + src/app/components/transaction/transaction.component.html + 215,217 + + show-more + + + Show less + + src/app/components/transaction/transaction.component.html + 217,223 + + show-less + + + Show flow diagram + + src/app/components/transaction/transaction.component.html + 237,238 + + show + Locktime src/app/components/transaction/transaction.component.html - 234,236 + 276,278 transaction.locktime @@ -3807,7 +4190,7 @@ Transaction not found. src/app/components/transaction/transaction.component.html - 365,366 + 439,440 transaction.error.transaction-not-found @@ -3815,7 +4198,7 @@ Waiting for it to appear in the mempool... src/app/components/transaction/transaction.component.html - 366,371 + 440,445 transaction.error.waiting-for-it-to-appear @@ -3823,7 +4206,7 @@ Effective fee rate src/app/components/transaction/transaction.component.html - 399,402 + 473,476 Effective transaction fee ratetransaction.effective-fee-rate @@ -3960,11 +4343,55 @@ transactions-list.load-to-reveal-fee-info - - This transaction saved % on fees by using native SegWit-Bech32 + + other inputs + + src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html + 12 + + transaction.other-inputs + + + other outputs + + src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html + 13 + + transaction.other-outputs + + + Input + + src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html + 43 + + transaction.input + + + Output + + src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html + 44 + + transaction.output + + + Fee + + src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html + 45 + + + src/app/dashboard/dashboard.component.html + 127,129 + + transaction.fee + + + This transaction saved % on fees by using native SegWit src/app/components/tx-features/tx-features.component.html - 1 + 2 ngbTooltip about segwit gains @@ -3972,56 +4399,93 @@ SegWit src/app/components/tx-features/tx-features.component.html - 1 + 2 src/app/components/tx-features/tx-features.component.html - 3 + 4 src/app/components/tx-features/tx-features.component.html - 5 + 6 SegWit tx-features.tag.segwit - - This transaction saved % on fees by using SegWit and could save % more by fully upgrading to native SegWit-Bech32 + + This transaction saved % on fees by using SegWit and could save % more by fully upgrading to native SegWit src/app/components/tx-features/tx-features.component.html - 3 + 4 ngbTooltip about double segwit gains - - This transaction could save % on fees by upgrading to native SegWit-Bech32 or % by upgrading to SegWit-P2SH + + This transaction could save % on fees by upgrading to native SegWit or % by upgrading to SegWit-P2SH src/app/components/tx-features/tx-features.component.html - 5 + 6 ngbTooltip about missed out gains + + This transaction uses Taproot and thereby saved at least % on fees + + src/app/components/tx-features/tx-features.component.html + 12 + + Tooltip about fees saved with taproot + + + Taproot + + src/app/components/tx-features/tx-features.component.html + 12 + + + src/app/components/tx-features/tx-features.component.html + 14 + + + src/app/components/tx-features/tx-features.component.html + 16 + + + src/app/components/tx-features/tx-features.component.html + 18 + + Taproot + tx-features.tag.taproot + + + This transaction uses Taproot and already saved at least % on fees, but could save an additional % by fully using Taproot + + src/app/components/tx-features/tx-features.component.html + 14 + + Tooltip about fees that saved and could be saved with taproot + + + This transaction could save % on fees by using Taproot + + src/app/components/tx-features/tx-features.component.html + 16 + + Tooltip about fees that could be saved with taproot + This transaction uses Taproot src/app/components/tx-features/tx-features.component.html - 8 + 18 - Taproot tooltip + Tooltip about taproot - - Taproot + + This transaction supports Replace-By-Fee (RBF) allowing fee bumping src/app/components/tx-features/tx-features.component.html - 8 - - tx-features.tag.taproot - - - This transaction support Replace-By-Fee (RBF) allowing fee bumping - - src/app/components/tx-features/tx-features.component.html - 9 + 25 RBF tooltip @@ -4029,11 +4493,11 @@ RBF src/app/components/tx-features/tx-features.component.html - 9 + 25 src/app/components/tx-features/tx-features.component.html - 10 + 26 RBF tx-features.tag.rbf @@ -4042,7 +4506,7 @@ This transaction does NOT support Replace-By-Fee (RBF) and cannot be fee bumped using this method src/app/components/tx-features/tx-features.component.html - 10 + 26 RBF disabled tooltip @@ -4104,14 +4568,6 @@ dashboard.latest-transactions.USD - - Fee - - src/app/dashboard/dashboard.component.html - 127,129 - - dashboard.latest-transactions.fee - Minimum fee @@ -4163,7 +4619,7 @@ src/app/docs/api-docs/api-docs.component.html - 95,98 + 97,100 Api docs endpoint @@ -4171,18 +4627,18 @@ Description src/app/docs/api-docs/api-docs.component.html - 60,61 + 62,63 src/app/docs/api-docs/api-docs.component.html - 99,100 + 101,102 Default push: action: 'want', data: ['blocks', ...] to express what you want pushed. Available: blocks, mempool-blocks, live-2h-chart, and stats.Push transactions related to address: 'track-address': '3PbJ...bF9B' to receive all new transactions containing that address as input or output. Returns an array of transactions. address-transactions for new mempool transactions, and block-transactions for new block confirmed transactions. src/app/docs/api-docs/api-docs.component.html - 100,101 + 102,103 api-docs.websocket.websocket @@ -4200,6 +4656,10 @@ src/app/docs/code-template/code-template.component.html29,30 + + src/app/docs/code-template/code-template.component.html + 36,37 + API Docs code example @@ -4214,7 +4674,7 @@ Response src/app/docs/code-template/code-template.component.html - 36,37 + 43,44 API Docs API response @@ -4236,6 +4696,1336 @@ 39 + + Base fee + + src/app/lightning/channel/channel-box/channel-box.component.html + 30 + + + src/app/lightning/channel/channel-preview.component.html + 41,44 + + lightning.base-fee + + + mSats + + src/app/lightning/channel/channel-box/channel-box.component.html + 36 + + + src/app/lightning/channels-statistics/channels-statistics.component.html + 47,50 + + + src/app/lightning/channels-statistics/channels-statistics.component.html + 93,96 + + shared.m-sats + + + This channel supports zero base fee routing + + src/app/lightning/channel/channel-box/channel-box.component.html + 45 + + lightning.zero-base-fee-tooltip + + + Zero base fee + + src/app/lightning/channel/channel-box/channel-box.component.html + 46 + + lightning.zero-base-fee + + + This channel does not support zero base fee routing + + src/app/lightning/channel/channel-box/channel-box.component.html + 51 + + lightning.non-zero-base-fee-tooltip + + + Non-zero base fee + + src/app/lightning/channel/channel-box/channel-box.component.html + 52 + + lightning.non-zero-base-fee + + + Min HTLC + + src/app/lightning/channel/channel-box/channel-box.component.html + 58 + + lightning.min-htlc + + + Max HTLC + + src/app/lightning/channel/channel-box/channel-box.component.html + 64 + + lightning.max-htlc + + + Timelock delta + + src/app/lightning/channel/channel-box/channel-box.component.html + 70 + + lightning.timelock-delta + + + channels + + src/app/lightning/channel/channel-box/channel-box.component.html + 80 + + + src/app/lightning/channels-list/channels-list.component.html + 121,122 + + lightning.x-channels + + + lightning channel + + src/app/lightning/channel/channel-preview.component.html + 3,5 + + lightning.channel + + + Inactive + + src/app/lightning/channel/channel-preview.component.html + 10,11 + + + src/app/lightning/channel/channel.component.html + 11,12 + + + src/app/lightning/channels-list/channels-list.component.html + 66,67 + + status.inactive + + + Active + + src/app/lightning/channel/channel-preview.component.html + 11,12 + + + src/app/lightning/channel/channel.component.html + 12,13 + + + src/app/lightning/channels-list/channels-list.component.html + 67,69 + + status.active + + + Closed + + src/app/lightning/channel/channel-preview.component.html + 12,14 + + + src/app/lightning/channel/channel.component.html + 13,14 + + + src/app/lightning/channels-list/channels-list.component.html + 8,13 + + + src/app/lightning/channels-list/channels-list.component.html + 69,71 + + status.closed + + + Created + + src/app/lightning/channel/channel-preview.component.html + 23,26 + + + src/app/lightning/channel/channel.component.html + 29,30 + + lightning.created + + + Capacity + + src/app/lightning/channel/channel-preview.component.html + 27,28 + + + src/app/lightning/channel/channel.component.html + 48,49 + + + src/app/lightning/channels-list/channels-list.component.html + 40,43 + + + src/app/lightning/node-statistics/node-statistics.component.html + 4,5 + + + src/app/lightning/node-statistics/node-statistics.component.html + 47,50 + + + src/app/lightning/nodes-list/nodes-list.component.html + 6,7 + + + src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html + 31,34 + + + src/app/lightning/nodes-per-country/nodes-per-country.component.html + 63,65 + + + src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html + 61,64 + + + src/app/lightning/nodes-per-isp/nodes-per-isp.component.html + 60,62 + + lightning.capacity + + + ppm + + src/app/lightning/channel/channel-preview.component.html + 34,35 + + + src/app/lightning/channel/channel-preview.component.html + 36,41 + + + src/app/lightning/channels-statistics/channels-statistics.component.html + 32,35 + + + src/app/lightning/channels-statistics/channels-statistics.component.html + 78,81 + + lightning.ppm + + + Lightning channel + + src/app/lightning/channel/channel.component.html + 2,5 + + + src/app/lightning/channel/channel.component.html + 106,108 + + lightning.channel + + + Last update + + src/app/lightning/channel/channel.component.html + 33,34 + + + src/app/lightning/node/node.component.html + 69,71 + + + src/app/lightning/nodes-per-country/nodes-per-country.component.html + 62,64 + + + src/app/lightning/nodes-per-isp/nodes-per-isp.component.html + 59,61 + + + src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.html + 14,15 + + + src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html + 14,15 + + + src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html + 14,15 + + lightning.last-update + + + Closing date + + src/app/lightning/channel/channel.component.html + 37,38 + + + src/app/lightning/channels-list/channels-list.component.html + 39,40 + + lightning.closing_date + + + Opening transaction + + src/app/lightning/channel/channel.component.html + 73,74 + + lightning.opening-transaction + + + Closing transaction + + src/app/lightning/channel/channel.component.html + 82,84 + + lightning.closing-transaction + + + Channel: + + src/app/lightning/channel/channel.component.ts + 37 + + + + Open + + src/app/lightning/channels-list/channels-list.component.html + 5,7 + + open + + + No channels to display + + src/app/lightning/channels-list/channels-list.component.html + 29,35 + + lightning.empty-channels-list + + + Alias + + src/app/lightning/channels-list/channels-list.component.html + 35,37 + + + src/app/lightning/group/group.component.html + 72,74 + + + src/app/lightning/nodes-list/nodes-list.component.html + 5,6 + + + src/app/lightning/nodes-per-country/nodes-per-country.component.html + 60,61 + + + src/app/lightning/nodes-per-isp/nodes-per-isp.component.html + 57,58 + + + src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.html + 10,11 + + + src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html + 10,11 + + + src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html + 10,12 + + lightning.alias + + + Status + + src/app/lightning/channels-list/channels-list.component.html + 37,38 + + status + + + Fee Rate + + src/app/lightning/channels-list/channels-list.component.html + 38,39 + + Transaction fee rate + transaction.fee-rate + + + Channel ID + + src/app/lightning/channels-list/channels-list.component.html + 41,45 + + channels.id + + + sats + + src/app/lightning/channels-list/channels-list.component.html + 61,65 + + + src/app/lightning/channels-list/channels-list.component.html + 85,89 + + + src/app/lightning/channels-statistics/channels-statistics.component.html + 17,20 + + + src/app/lightning/channels-statistics/channels-statistics.component.html + 63,66 + + + src/app/lightning/group/group-preview.component.html + 34,36 + + + src/app/lightning/group/group.component.html + 32,34 + + + src/app/lightning/group/group.component.html + 83,87 + + + src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html + 50,55 + + + src/app/lightning/nodes-per-country/nodes-per-country.component.html + 22,24 + + + src/app/lightning/nodes-per-country/nodes-per-country.component.html + 82,85 + + + src/app/lightning/nodes-per-isp/nodes-per-isp.component.html + 23,25 + + + src/app/lightning/nodes-per-isp/nodes-per-isp.component.html + 79,82 + + shared.sats + + + Avg Capacity + + src/app/lightning/channels-statistics/channels-statistics.component.html + 13,15 + + + src/app/lightning/channels-statistics/channels-statistics.component.html + 107,110 + + ln.average-capacity + + + Avg Fee Rate + + src/app/lightning/channels-statistics/channels-statistics.component.html + 26,28 + + + src/app/lightning/channels-statistics/channels-statistics.component.html + 114,117 + + ln.average-feerate + + + The average fee rate charged by routing nodes, ignoring fee rates > 0.5% or 5000ppm + + src/app/lightning/channels-statistics/channels-statistics.component.html + 28,30 + + ln.average-feerate-desc + + + Avg Base Fee + + src/app/lightning/channels-statistics/channels-statistics.component.html + 41,43 + + + src/app/lightning/channels-statistics/channels-statistics.component.html + 121,124 + + ln.average-basefee + + + The average base fee charged by routing nodes, ignoring base fees > 5000ppm + + src/app/lightning/channels-statistics/channels-statistics.component.html + 43,45 + + ln.average-basefee-desc + + + Med Capacity + + src/app/lightning/channels-statistics/channels-statistics.component.html + 59,61 + + ln.median-capacity + + + Med Fee Rate + + src/app/lightning/channels-statistics/channels-statistics.component.html + 72,74 + + ln.average-feerate + + + The median fee rate charged by routing nodes, ignoring fee rates > 0.5% or 5000ppm + + src/app/lightning/channels-statistics/channels-statistics.component.html + 74,76 + + ln.median-feerate-desc + + + Med Base Fee + + src/app/lightning/channels-statistics/channels-statistics.component.html + 87,89 + + ln.median-basefee + + + The median base fee charged by routing nodes, ignoring base fees > 5000ppm + + src/app/lightning/channels-statistics/channels-statistics.component.html + 89,91 + + ln.median-basefee-desc + + + Lightning node group + + src/app/lightning/group/group-preview.component.html + 3,5 + + + src/app/lightning/group/group.component.html + 2,6 + + lightning.node + + + Nodes + + src/app/lightning/group/group-preview.component.html + 25,29 + + + src/app/lightning/group/group.component.html + 23,27 + + + src/app/lightning/node-statistics/node-statistics.component.html + 17,18 + + + src/app/lightning/node-statistics/node-statistics.component.html + 54,57 + + + src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html + 30,32 + + + src/app/lightning/nodes-per-country/nodes-per-country.component.html + 13,16 + + + src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html + 60,62 + + + src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.html + 22,26 + + + src/app/lightning/nodes-per-isp/nodes-per-isp.component.html + 14,18 + + lightning.node-count + + + Liquidity + + src/app/lightning/group/group-preview.component.html + 29,31 + + + src/app/lightning/group/group.component.html + 27,29 + + + src/app/lightning/nodes-per-country/nodes-per-country.component.html + 17,19 + + + src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.html + 26,28 + + + src/app/lightning/nodes-per-isp/nodes-per-isp.component.html + 18,20 + + + src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.html + 12,13 + + + src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html + 11,12 + + + src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html + 12,13 + + lightning.liquidity + + + Channels + + src/app/lightning/group/group-preview.component.html + 40,43 + + + src/app/lightning/group/group.component.html + 40,44 + + + src/app/lightning/node-statistics/node-statistics.component.html + 29,30 + + + src/app/lightning/node-statistics/node-statistics.component.html + 61,64 + + + src/app/lightning/nodes-list/nodes-list.component.html + 7,10 + + + src/app/lightning/nodes-per-country/nodes-per-country.component.html + 30,33 + + + src/app/lightning/nodes-per-country/nodes-per-country.component.html + 64,66 + + + src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.html + 35,39 + + + src/app/lightning/nodes-per-isp/nodes-per-isp.component.html + 31,35 + + + src/app/lightning/nodes-per-isp/nodes-per-isp.component.html + 61,63 + + + src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.html + 13,14 + + + src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html + 12,13 + + + src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html + 11,12 + + lightning.channels + + + Average size + + src/app/lightning/group/group-preview.component.html + 44,46 + + + src/app/lightning/node/node-preview.component.html + 32,34 + + lightning.active-channels-avg + + + Location + + src/app/lightning/group/group.component.html + 74,77 + + + src/app/lightning/node/node-preview.component.html + 38,42 + + + src/app/lightning/node/node-preview.component.html + 50,54 + + + src/app/lightning/node/node.component.html + 47,49 + + + src/app/lightning/nodes-per-country/nodes-per-country.component.html + 65,68 + + + src/app/lightning/nodes-per-isp/nodes-per-isp.component.html + 62,65 + + + src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.html + 15,18 + + + src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html + 15,17 + + + src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html + 15,17 + + lightning.location + + + Network Statistics + + src/app/lightning/lightning-dashboard/lightning-dashboard.component.html + 10 + + lightning.network-statistics-title + + + Channels Statistics + + src/app/lightning/lightning-dashboard/lightning-dashboard.component.html + 24 + + lightning.channel-statistics-title + + + Lightning Network History + + src/app/lightning/lightning-dashboard/lightning-dashboard.component.html + 49 + + lightning.network-history + + + Liquidity Ranking + + src/app/lightning/lightning-dashboard/lightning-dashboard.component.html + 62 + + lightning.liquidity-ranking + + + Connectivity Ranking + + src/app/lightning/lightning-dashboard/lightning-dashboard.component.html + 76 + + lightning.connectivity-ranking + + + Lightning Network + + src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts + 27 + + + + Percentage change past week + + src/app/lightning/node-statistics/node-statistics.component.html + 5,7 + + + src/app/lightning/node-statistics/node-statistics.component.html + 18,20 + + + src/app/lightning/node-statistics/node-statistics.component.html + 30,32 + + mining.percentage-change-last-week + + + lightning node + + src/app/lightning/node/node-preview.component.html + 3,5 + + lightning.node + + + Active capacity + + src/app/lightning/node/node-preview.component.html + 20,22 + + + src/app/lightning/node/node.component.html + 27,30 + + lightning.active-capacity + + + Active channels + + src/app/lightning/node/node-preview.component.html + 26,30 + + + src/app/lightning/node/node.component.html + 34,38 + + lightning.active-channels + + + Country + + src/app/lightning/node/node-preview.component.html + 44,47 + + country + + + Lightning node + + src/app/lightning/node/node.component.html + 2,4 + + + src/app/lightning/node/node.component.html + 165,167 + + lightning.node + + + No node found for public key "" + + src/app/lightning/node/node.component.html + 17,19 + + lightning.node-not-found + + + Average channel size + + src/app/lightning/node/node.component.html + 40,43 + + lightning.active-channels-avg + + + Unknown + + src/app/lightning/node/node.component.html + 52,56 + + + src/app/lightning/node/node.component.html + 91,95 + + + src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts + 142,139 + + + src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts + 311,310 + + unknown + + + Color + + src/app/lightning/node/node.component.html + 75,77 + + lightning.color + + + ISP + + src/app/lightning/node/node.component.html + 82,83 + + + src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html + 59,60 + + isp + + + Exclusively on Tor + + src/app/lightning/node/node.component.html + 88,90 + + tor + + + Open channels + + src/app/lightning/node/node.component.html + 145,148 + + lightning.open-channels + + + Closed channels + + src/app/lightning/node/node.component.html + 149,152 + + lightning.open-channels + + + Node: + + src/app/lightning/node/node.component.ts + 42 + + + + (Tor nodes excluded) + + src/app/lightning/nodes-channels-map/nodes-channels-map.component.html + 8,11 + + + src/app/lightning/nodes-map/nodes-map.component.html + 7,10 + + + src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html + 10,15 + + lightning.tor-nodes-excluded + + + Lightning Nodes Channels World Map + + src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts + 69 + + + + No geolocation data available + + src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts + 218,213 + + + + Active channels map + + src/app/lightning/nodes-channels/node-channels.component.html + 2,3 + + lightning.active-channels-map + + + af8560ca50882114be16c951650f83bca73161a7:Lightning Nodes World Map + + src/app/lightning/nodes-map/nodes-map.component.ts + 50 + + + + Indexing in progess + + src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts + 121,116 + + + src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts + 112,107 + + + + Reachable on Clearnet Only + + src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts + 164,161 + + + src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts + 303,302 + + + + Reachable on Clearnet and Darknet + + src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts + 185,182 + + + src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts + 295,294 + + + + Reachable on Darknet Only + + src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts + 206,203 + + + src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts + 287,286 + + + + Share + + src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html + 29,31 + + + src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html + 59,61 + + lightning.share + + + nodes<br> + + src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts + 103,102 + + + src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts + 157,156 + + + src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts + 189,188 + + + + BTC capacity + + src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts + 104,102 + + + + Lightning nodes in + + src/app/lightning/nodes-per-country/nodes-per-country.component.html + 3,7 + + lightning.nodes-in-country + + + ISP Count + + src/app/lightning/nodes-per-country/nodes-per-country.component.html + 34,38 + + lightning.isp-count + + + Top ISP + + src/app/lightning/nodes-per-country/nodes-per-country.component.html + 38,40 + + lightning.top-isp + + + Lightning nodes in + + src/app/lightning/nodes-per-country/nodes-per-country.component.ts + 35 + + + + Clearnet Capacity + + src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html + 6,8 + + + src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html + 83,86 + + lightning.clearnet-capacity + + + How much liquidity is running on nodes advertising at least one clearnet IP address + + src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html + 8,9 + + lightning.clearnet-capacity-desc + + + Unknown Capacity + + src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html + 13,15 + + + src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html + 89,92 + + lightning.unknown-capacity + + + How much liquidity is running on nodes which ISP was not identifiable + + src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html + 15,16 + + lightning.unknown-capacity-desc + + + Tor Capacity + + src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html + 20,22 + + + src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html + 95,97 + + lightning.tor-capacity + + + How much liquidity is running on nodes advertising only Tor addresses + + src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html + 22,23 + + lightning.tor-capacity-desc + + + Top 100 ISPs hosting LN nodes + + src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html + 31,33 + + lightning.top-100-isp-ln + + + (Tor nodes excluded) + + src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html + 37,39 + + lightning.tor-nodes-excluded + + + BTC + + src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts + 158,156 + + + src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts + 190,188 + + + + lightning ISP + + src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.html + 3,5 + + lightning.node + + + Top country + + src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.html + 39,41 + + + src/app/lightning/nodes-per-isp/nodes-per-isp.component.html + 35,37 + + lightning.top-country + + + Top node + + src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.html + 45,48 + + lightning.top-node + + + Lightning nodes on ISP: [AS] + + src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.ts + 44 + + + src/app/lightning/nodes-per-isp/nodes-per-isp.component.ts + 39 + + + + Lightning nodes on ISP: + + src/app/lightning/nodes-per-isp/nodes-per-isp.component.html + 2,4 + + lightning.nodes-for-isp + + + ASN + + src/app/lightning/nodes-per-isp/nodes-per-isp.component.html + 11,14 + + lightning.asn + + + Top 100 oldest lightning nodes + + src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.html + 3,6 + + lightning.top-100-oldest-nodes + + + Oldest lightning nodes + + src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.ts + 27 + + + + Top 100 nodes liquidity ranking + + src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html + 3,7 + + lightning.top-100-liquidity + + + Liquidity Ranking + + src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts + 29 + + + + Top 100 nodes connectivity ranking + + src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html + 3,7 + + lightning.top-100-connectivity + + + Connectivity Ranking + + src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts + 29 + + + + Liquidity ranking + + src/app/lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component.html + 8 + + lightning.liquidity-ranking + + + Connectivity ranking + + src/app/lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component.html + 22 + + lightning.connectivity-ranking + + + Oldest nodes + + src/app/lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component.html + 36 + + lightning.top-channels-age + + + Top lightning nodes + + src/app/lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component.ts + 22 + + + + Indexing in progress + + src/app/lightning/statistics-chart/lightning-statistics-chart.component.html + 52,55 + + lightning.indexing-in-progress + + + Capacity + + src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts + 282,279 + + year From aad084d1088507a8f50c59a440ad7a1531c9cf09 Mon Sep 17 00:00:00 2001 From: softsimon Date: Fri, 7 Oct 2022 01:20:32 +0400 Subject: [PATCH 17/76] Inverting med/avg toggle --- .../channels-statistics.component.html | 8 ++++---- .../channels-statistics/channels-statistics.component.ts | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/lightning/channels-statistics/channels-statistics.component.html b/frontend/src/app/lightning/channels-statistics/channels-statistics.component.html index 60a43216c..9d2ca83e8 100644 --- a/frontend/src/app/lightning/channels-statistics/channels-statistics.component.html +++ b/frontend/src/app/lightning/channels-statistics/channels-statistics.component.html @@ -1,9 +1,9 @@
- avg + avg | - med + med
diff --git a/frontend/src/app/lightning/channels-statistics/channels-statistics.component.ts b/frontend/src/app/lightning/channels-statistics/channels-statistics.component.ts index 9fc761983..4059e9420 100644 --- a/frontend/src/app/lightning/channels-statistics/channels-statistics.component.ts +++ b/frontend/src/app/lightning/channels-statistics/channels-statistics.component.ts @@ -18,5 +18,6 @@ export class ChannelsStatisticsComponent implements OnInit { switchMode(mode: 'avg' | 'med') { this.mode = mode; + return false; } } From f2704a1e6a27346b04ecce0481b733651042a82e Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 7 Oct 2022 16:50:26 +0000 Subject: [PATCH 18/76] Add requested url to unfurler error logs --- unfurler/src/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/unfurler/src/index.ts b/unfurler/src/index.ts index 60126863d..0b423ff92 100644 --- a/unfurler/src/index.ts +++ b/unfurler/src/index.ts @@ -112,14 +112,14 @@ class Server { const screenshot = await page.screenshot(); return screenshot; } else if (success === false) { - logger.warn(`failed to render page preview for ${action} due to client-side error, e.g. requested an invalid txid`); + logger.warn(`failed to render ${path} for ${action} due to client-side error, e.g. requested an invalid txid`); page.repairRequested = true; } else { - logger.warn(`failed to render page preview for ${action} due to puppeteer timeout`); + logger.warn(`failed to render ${path} for ${action} due to puppeteer timeout`); page.repairRequested = true; } } catch (e) { - logger.err(`failed to render page for ${action}: ` + (e instanceof Error ? e.message : `${e}`)); + logger.err(`failed to render ${path} for ${action}: ` + (e instanceof Error ? e.message : `${e}`)); page.repairRequested = true; } } @@ -154,7 +154,7 @@ class Server { res.send(img); } } catch (e) { - logger.err(e instanceof Error ? e.message : `${e}`); + logger.err(e instanceof Error ? e.message : `${e} ${req.params[0]}`); res.status(500).send(e instanceof Error ? e.message : e); } } From 5f835f99deb5796be9342e4db1e59b0b846396bd Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sat, 8 Oct 2022 13:47:33 -0700 Subject: [PATCH 19/76] Ignore the new config files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 687e9e8cb..b41b0db08 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ data docker-compose.yml backend/mempool-config.json *.swp +frontend/src/resources/config.template.js +frontend/src/resources/config.js From 3bcf7bbd842ae8e813c80dca299cb9e3b1e04e0d Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sat, 8 Oct 2022 13:48:29 -0700 Subject: [PATCH 20/76] Serve the frontend config from resources, stop bundling the generated file --- frontend/angular.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/angular.json b/frontend/angular.json index 1ed29cad9..672b84417 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -152,15 +152,14 @@ "assets": [ "src/favicon.ico", "src/resources", - "src/robots.txt" + "src/robots.txt", + "src/config.js", + "src/config.template.js" ], "styles": [ "src/styles.scss", "node_modules/@fortawesome/fontawesome-svg-core/styles.css" ], - "scripts": [ - "generated-config.js" - ], "vendorChunk": true, "extractLicenses": false, "buildOptimizer": false, From b79fc9bfb788fb7129142aff3d7be4b28e951665 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sat, 8 Oct 2022 14:51:32 -0700 Subject: [PATCH 21/76] Update config generator to output the template and new config file --- frontend/generate-config.js | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/frontend/generate-config.js b/frontend/generate-config.js index 1f37953b7..4b7f80cd8 100644 --- a/frontend/generate-config.js +++ b/frontend/generate-config.js @@ -2,7 +2,8 @@ var fs = require('fs'); const { spawnSync } = require('child_process'); const CONFIG_FILE_NAME = 'mempool-frontend-config.json'; -const GENERATED_CONFIG_FILE_NAME = 'generated-config.js'; +const GENERATED_CONFIG_FILE_NAME = 'src/resources/config.js'; +const GENERATED_TEMPLATE_CONFIG_FILE_NAME = 'src/resources/config.template.js'; let settings = []; let configContent = {}; @@ -67,10 +68,17 @@ if (process.env.DOCKER_COMMIT_HASH) { const newConfig = `(function (window) { window.__env = window.__env || {};${settings.reduce((str, obj) => `${str} - window.__env.${obj.key} = ${ typeof obj.value === 'string' ? `'${obj.value}'` : obj.value };`, '')} + window.__env.${obj.key} = ${typeof obj.value === 'string' ? `'${obj.value}'` : obj.value};`, '')} window.__env.GIT_COMMIT_HASH = '${gitCommitHash}'; window.__env.PACKAGE_JSON_VERSION = '${packetJsonVersion}'; - }(global || this));`; + }(this));`; + +const newConfigTemplate = `(function (window) { + window.__env = window.__env || {};${settings.reduce((str, obj) => `${str} + window.__env.${obj.key} = ${typeof obj.value === 'string' ? `'\${${obj.key}}'` : `\${${obj.key}}`};`, '')} + window.__env.GIT_COMMIT_HASH = '${gitCommitHash}'; + window.__env.PACKAGE_JSON_VERSION = '${packetJsonVersion}'; + }(this));`; function readConfig(path) { try { @@ -89,6 +97,16 @@ function writeConfig(path, config) { } } +function writeConfigTemplate(path, config) { + try { + fs.writeFileSync(path, config, 'utf8'); + } catch (e) { + throw new Error(e); + } +} + +writeConfigTemplate(GENERATED_TEMPLATE_CONFIG_FILE_NAME, newConfigTemplate); + const currentConfig = readConfig(GENERATED_CONFIG_FILE_NAME); if (currentConfig && currentConfig === newConfig) { @@ -106,4 +124,4 @@ if (currentConfig && currentConfig === newConfig) { console.log('NEW CONFIG: ', newConfig); writeConfig(GENERATED_CONFIG_FILE_NAME, newConfig); console.log(`${GENERATED_CONFIG_FILE_NAME} file updated`); -}; +} From 20c16a4eff829012df763baa69c9f8456f14e8f8 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sat, 8 Oct 2022 14:58:09 -0700 Subject: [PATCH 22/76] Update index files to read the new config file --- frontend/src/index.bisq.html | 9 ++++++++- frontend/src/index.liquid.html | 9 ++++++++- frontend/src/index.mempool.html | 9 ++++++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/frontend/src/index.bisq.html b/frontend/src/index.bisq.html index 8da1e77e0..82f491d50 100644 --- a/frontend/src/index.bisq.html +++ b/frontend/src/index.bisq.html @@ -1,10 +1,15 @@ + mempool - Bisq Markets + + @@ -31,11 +36,13 @@ - + + + diff --git a/frontend/src/index.liquid.html b/frontend/src/index.liquid.html index 89a6984ba..78a78aaf2 100644 --- a/frontend/src/index.liquid.html +++ b/frontend/src/index.liquid.html @@ -1,10 +1,15 @@ + mempool - Liquid Network + + @@ -17,7 +22,7 @@ - + @@ -33,7 +38,9 @@ + + diff --git a/frontend/src/index.mempool.html b/frontend/src/index.mempool.html index 1176a3da2..b6514ed9a 100644 --- a/frontend/src/index.mempool.html +++ b/frontend/src/index.mempool.html @@ -1,10 +1,15 @@ + mempool - Bitcoin Explorer + + @@ -17,7 +22,7 @@ - + @@ -32,7 +37,9 @@ + + From 4b61f620926f359f491671d660ee5f79a3acce25 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 11 Oct 2022 16:17:17 +0000 Subject: [PATCH 23/76] Fix tx confirmation badge alignment regression --- .../src/app/components/transaction/transaction.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/components/transaction/transaction.component.scss b/frontend/src/app/components/transaction/transaction.component.scss index df8d37ebc..7127a898a 100644 --- a/frontend/src/app/components/transaction/transaction.component.scss +++ b/frontend/src/app/components/transaction/transaction.component.scss @@ -3,7 +3,7 @@ } .container-buttons { - align-self: flex-start; + align-self: center; } .title-block { From 957f3c32b48cd08b4cdd4bb914988c6d3d5bb6ef Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 11 Oct 2022 17:01:23 +0000 Subject: [PATCH 24/76] Save flow diagram preference to localStorage --- .../transaction/transaction.component.html | 6 ++-- .../transaction/transaction.component.ts | 30 +++++++++++++++---- frontend/src/app/services/state.service.ts | 11 +++++++ frontend/src/app/services/storage.service.ts | 8 +++++ 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index 360d3e34f..86930bcc7 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -190,7 +190,7 @@
- +

Flow

@@ -238,7 +238,7 @@
- +
@@ -329,7 +329,7 @@
- +

Flow

diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index c64c112b1..2be549569 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -49,12 +49,15 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { liquidUnblinding = new LiquidUnblinding(); inputIndex: number; outputIndex: number; - showFlow: boolean = true; graphExpanded: boolean = false; graphWidth: number = 1000; graphHeight: number = 360; inOutLimit: number = 150; maxInOut: number = 0; + flowPrefSubscription: Subscription; + hideFlow: boolean = this.stateService.hideFlow.value; + overrideFlowPreference: boolean = null; + flowEnabled: boolean; tooltipPosition: { x: number, y: number }; @@ -78,6 +81,12 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { (network) => (this.network = network) ); + this.setFlowEnabled(); + this.flowPrefSubscription = this.stateService.hideFlow.subscribe((hide) => { + this.hideFlow = !!hide; + this.setFlowEnabled(); + }); + this.timeAvg$ = timer(0, 1000) .pipe( switchMap(() => this.stateService.difficultyAdjustment$), @@ -245,11 +254,14 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { this.queryParamsSubscription = this.route.queryParams.subscribe((params) => { if (params.showFlow === 'false') { - this.showFlow = false; + this.overrideFlowPreference = false; + } else if (params.showFlow === 'true') { + this.overrideFlowPreference = true; } else { - this.showFlow = true; - this.setGraphSize(); + this.overrideFlowPreference = null; } + this.setFlowEnabled(); + this.setGraphSize(); }); } @@ -325,15 +337,20 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { } toggleGraph() { - this.showFlow = !this.showFlow; + const showFlow = !this.flowEnabled; + this.stateService.hideFlow.next(!showFlow); this.router.navigate([], { relativeTo: this.route, - queryParams: { showFlow: this.showFlow }, + queryParams: { showFlow: showFlow }, queryParamsHandling: 'merge', fragment: 'flow' }); } + setFlowEnabled() { + this.flowEnabled = (this.overrideFlowPreference != null ? this.overrideFlowPreference : !this.hideFlow); + } + expandGraph() { this.graphExpanded = true; } @@ -365,6 +382,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { this.txReplacedSubscription.unsubscribe(); this.blocksSubscription.unsubscribe(); this.queryParamsSubscription.unsubscribe(); + this.flowPrefSubscription.unsubscribe(); this.leaveTransaction(); } } diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index 9f7cc58a3..d7feb38bb 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -110,6 +110,7 @@ export class StateService { blockScrolling$: Subject = new Subject(); timeLtr: BehaviorSubject; + hideFlow: BehaviorSubject; constructor( @Inject(PLATFORM_ID) private platformId: any, @@ -159,6 +160,16 @@ export class StateService { this.timeLtr.subscribe((ltr) => { this.storageService.setValue('time-preference-ltr', ltr ? 'true' : 'false'); }); + + const savedFlowPreference = this.storageService.getValue('flow-preference'); + this.hideFlow = new BehaviorSubject(savedFlowPreference === 'hide'); + this.hideFlow.subscribe((hide) => { + if (hide) { + this.storageService.setValue('flow-preference', hide ? 'hide' : 'show'); + } else { + this.storageService.removeItem('flow-preference'); + } + }); } setNetworkBasedonUrl(url: string) { diff --git a/frontend/src/app/services/storage.service.ts b/frontend/src/app/services/storage.service.ts index f3ea694b2..73a013146 100644 --- a/frontend/src/app/services/storage.service.ts +++ b/frontend/src/app/services/storage.service.ts @@ -46,4 +46,12 @@ export class StorageService { console.log(e); } } + + removeItem(key: string): void { + try { + localStorage.removeItem(key); + } catch (e) { + console.log(e); + } + } } From 65d4393b7885d2614c8732b8f704f48949195821 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 11 Oct 2022 23:06:46 +0000 Subject: [PATCH 25/76] Fix tx preview flow diagram highlight bug --- .../components/tx-bowtie-graph/tx-bowtie-graph.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.html b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.html index ced3b5f57..a85f62c65 100644 --- a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.html +++ b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.html @@ -68,7 +68,7 @@ Date: Wed, 12 Oct 2022 22:13:29 +0000 Subject: [PATCH 26/76] Maintain page when switching networks --- frontend/src/app/app-routing.module.ts | 46 ++++++++-- frontend/src/app/bisq/bisq.routing.module.ts | 7 ++ .../bisq-master-page.component.html | 12 +-- .../bisq-master-page.component.ts | 7 ++ .../liquid-master-page.component.html | 12 +-- .../liquid-master-page.component.ts | 7 ++ .../master-page/master-page.component.html | 12 +-- .../master-page/master-page.component.ts | 7 ++ frontend/src/app/docs/docs.routing.module.ts | 1 + .../src/app/graphs/graphs.routing.module.ts | 20 +++++ .../app/lightning/lightning.routing.module.ts | 2 + .../src/app/services/navigation.service.ts | 90 +++++++++++++++++++ 12 files changed, 199 insertions(+), 24 deletions(-) create mode 100644 frontend/src/app/services/navigation.service.ts diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 260efcafe..69c78fc83 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -74,12 +74,14 @@ let routes: Routes = [ children: [], component: AddressComponent, data: { - ogImage: true + ogImage: true, + networkSpecific: true, } }, { path: 'tx', component: StartComponent, + data: { networkSpecific: true }, children: [ { path: ':id', @@ -90,6 +92,7 @@ let routes: Routes = [ { path: 'block', component: StartComponent, + data: { networkSpecific: true }, children: [ { path: ':id', @@ -102,6 +105,7 @@ let routes: Routes = [ }, { path: 'block-audit', + data: { networkSpecific: true }, children: [ { path: ':id', @@ -121,12 +125,13 @@ let routes: Routes = [ { path: 'lightning', loadChildren: () => import('./lightning/lightning.module').then(m => m.LightningModule), - data: { preload: browserWindowEnv && browserWindowEnv.LIGHTNING === true }, + data: { preload: browserWindowEnv && browserWindowEnv.LIGHTNING === true, networks: ['bitcoin'] }, }, ], }, { path: 'status', + data: { networks: ['bitcoin', 'liquid'] }, component: StatusViewComponent }, { @@ -185,11 +190,13 @@ let routes: Routes = [ children: [], component: AddressComponent, data: { - ogImage: true + ogImage: true, + networkSpecific: true, } }, { path: 'tx', + data: { networkSpecific: true }, component: StartComponent, children: [ { @@ -200,6 +207,7 @@ let routes: Routes = [ }, { path: 'block', + data: { networkSpecific: true }, component: StartComponent, children: [ { @@ -213,6 +221,7 @@ let routes: Routes = [ }, { path: 'block-audit', + data: { networkSpecific: true }, children: [ { path: ':id', @@ -230,12 +239,14 @@ let routes: Routes = [ }, { path: 'lightning', + data: { networks: ['bitcoin'] }, loadChildren: () => import('./lightning/lightning.module').then(m => m.LightningModule) }, ], }, { path: 'status', + data: { networks: ['bitcoin', 'liquid'] }, component: StatusViewComponent }, { @@ -291,11 +302,13 @@ let routes: Routes = [ children: [], component: AddressComponent, data: { - ogImage: true + ogImage: true, + networkSpecific: true, } }, { path: 'tx', + data: { networkSpecific: true }, component: StartComponent, children: [ { @@ -306,6 +319,7 @@ let routes: Routes = [ }, { path: 'block', + data: { networkSpecific: true }, component: StartComponent, children: [ { @@ -319,6 +333,7 @@ let routes: Routes = [ }, { path: 'block-audit', + data: { networkSpecific: true }, children: [ { path: ':id', @@ -336,6 +351,7 @@ let routes: Routes = [ }, { path: 'lightning', + data: { networks: ['bitcoin'] }, loadChildren: () => import('./lightning/lightning.module').then(m => m.LightningModule) }, ], @@ -359,6 +375,7 @@ let routes: Routes = [ }, { path: 'status', + data: { networks: ['bitcoin', 'liquid'] }, component: StatusViewComponent }, { @@ -422,11 +439,13 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { children: [], component: AddressComponent, data: { - ogImage: true + ogImage: true, + networkSpecific: true, } }, { path: 'tx', + data: { networkSpecific: true }, component: StartComponent, children: [ { @@ -437,6 +456,7 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { }, { path: 'block', + data: { networkSpecific: true }, component: StartComponent, children: [ { @@ -450,18 +470,22 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { }, { path: 'assets', + data: { networks: ['liquid'] }, component: AssetsNavComponent, children: [ { path: 'all', + data: { networks: ['liquid'] }, component: AssetsComponent, }, { path: 'asset/:id', + data: { networkSpecific: true }, component: AssetComponent }, { path: 'group/:id', + data: { networkSpecific: true }, component: AssetGroupComponent }, { @@ -482,6 +506,7 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { }, { path: 'status', + data: { networks: ['bitcoin', 'liquid'] }, component: StatusViewComponent }, { @@ -532,11 +557,13 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { children: [], component: AddressComponent, data: { - ogImage: true + ogImage: true, + networkSpecific: true, } }, { path: 'tx', + data: { networkSpecific: true }, component: StartComponent, children: [ { @@ -547,6 +574,7 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { }, { path: 'block', + data: { networkSpecific: true }, component: StartComponent, children: [ { @@ -560,22 +588,27 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { }, { path: 'assets', + data: { networks: ['liquid'] }, component: AssetsNavComponent, children: [ { path: 'featured', + data: { networkSpecific: true }, component: AssetsFeaturedComponent, }, { path: 'all', + data: { networks: ['liquid'] }, component: AssetsComponent, }, { path: 'asset/:id', + data: { networkSpecific: true }, component: AssetComponent }, { path: 'group/:id', + data: { networkSpecific: true }, component: AssetGroupComponent }, { @@ -609,6 +642,7 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { }, { path: 'status', + data: { networks: ['bitcoin', 'liquid']}, component: StatusViewComponent }, { diff --git a/frontend/src/app/bisq/bisq.routing.module.ts b/frontend/src/app/bisq/bisq.routing.module.ts index f7385ae63..11acdca2a 100644 --- a/frontend/src/app/bisq/bisq.routing.module.ts +++ b/frontend/src/app/bisq/bisq.routing.module.ts @@ -20,14 +20,17 @@ const routes: Routes = [ }, { path: 'markets', + data: { networks: ['bisq'] }, component: BisqDashboardComponent, }, { path: 'transactions', + data: { networks: ['bisq'] }, component: BisqTransactionsComponent }, { path: 'market/:pair', + data: { networkSpecific: true }, component: BisqMarketComponent, }, { @@ -36,6 +39,7 @@ const routes: Routes = [ }, { path: 'tx/:id', + data: { networkSpecific: true }, component: BisqTransactionComponent }, { @@ -45,14 +49,17 @@ const routes: Routes = [ }, { path: 'block/:id', + data: { networkSpecific: true }, component: BisqBlockComponent, }, { path: 'address/:id', + data: { networkSpecific: true }, component: BisqAddressComponent, }, { path: 'stats', + data: { networks: ['bisq'] }, component: BisqStatsComponent, }, { diff --git a/frontend/src/app/components/bisq-master-page/bisq-master-page.component.html b/frontend/src/app/components/bisq-master-page/bisq-master-page.component.html index d07f9d60c..2054f1a5d 100644 --- a/frontend/src/app/components/bisq-master-page/bisq-master-page.component.html +++ b/frontend/src/app/components/bisq-master-page/bisq-master-page.component.html @@ -44,13 +44,13 @@ diff --git a/frontend/src/app/components/bisq-master-page/bisq-master-page.component.ts b/frontend/src/app/components/bisq-master-page/bisq-master-page.component.ts index d69b37d3d..941d9e21e 100644 --- a/frontend/src/app/components/bisq-master-page/bisq-master-page.component.ts +++ b/frontend/src/app/components/bisq-master-page/bisq-master-page.component.ts @@ -3,6 +3,7 @@ import { Env, StateService } from '../../services/state.service'; import { Observable } from 'rxjs'; import { LanguageService } from '../../services/language.service'; import { EnterpriseService } from '../../services/enterprise.service'; +import { NavigationService } from '../../services/navigation.service'; @Component({ selector: 'app-bisq-master-page', @@ -15,17 +16,23 @@ export class BisqMasterPageComponent implements OnInit { env: Env; isMobile = window.innerWidth <= 767.98; urlLanguage: string; + networkPaths: { [network: string]: string }; constructor( private stateService: StateService, private languageService: LanguageService, private enterpriseService: EnterpriseService, + private navigationService: NavigationService, ) { } ngOnInit() { this.env = this.stateService.env; this.connectionState$ = this.stateService.connectionState$; this.urlLanguage = this.languageService.getLanguageForUrl(); + this.navigationService.subnetPaths.subscribe((paths) => { + console.log('network paths updated...'); + this.networkPaths = paths; + }); } collapse(): void { diff --git a/frontend/src/app/components/liquid-master-page/liquid-master-page.component.html b/frontend/src/app/components/liquid-master-page/liquid-master-page.component.html index 7f22fd465..17f371202 100644 --- a/frontend/src/app/components/liquid-master-page/liquid-master-page.component.html +++ b/frontend/src/app/components/liquid-master-page/liquid-master-page.component.html @@ -49,13 +49,13 @@ diff --git a/frontend/src/app/components/liquid-master-page/liquid-master-page.component.ts b/frontend/src/app/components/liquid-master-page/liquid-master-page.component.ts index d78cd457e..c57673529 100644 --- a/frontend/src/app/components/liquid-master-page/liquid-master-page.component.ts +++ b/frontend/src/app/components/liquid-master-page/liquid-master-page.component.ts @@ -3,6 +3,7 @@ import { Env, StateService } from '../../services/state.service'; import { merge, Observable, of} from 'rxjs'; import { LanguageService } from '../../services/language.service'; import { EnterpriseService } from '../../services/enterprise.service'; +import { NavigationService } from '../../services/navigation.service'; @Component({ selector: 'app-liquid-master-page', @@ -17,11 +18,13 @@ export class LiquidMasterPageComponent implements OnInit { officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE; network$: Observable; urlLanguage: string; + networkPaths: { [network: string]: string }; constructor( private stateService: StateService, private languageService: LanguageService, private enterpriseService: EnterpriseService, + private navigationService: NavigationService, ) { } ngOnInit() { @@ -29,6 +32,10 @@ export class LiquidMasterPageComponent implements OnInit { this.connectionState$ = this.stateService.connectionState$; this.network$ = merge(of(''), this.stateService.networkChanged$); this.urlLanguage = this.languageService.getLanguageForUrl(); + this.navigationService.subnetPaths.subscribe((paths) => { + console.log('network paths updated...'); + this.networkPaths = paths; + }); } collapse(): void { diff --git a/frontend/src/app/components/master-page/master-page.component.html b/frontend/src/app/components/master-page/master-page.component.html index 1c28d5dce..5c365f0f9 100644 --- a/frontend/src/app/components/master-page/master-page.component.html +++ b/frontend/src/app/components/master-page/master-page.component.html @@ -22,13 +22,13 @@ diff --git a/frontend/src/app/components/master-page/master-page.component.ts b/frontend/src/app/components/master-page/master-page.component.ts index 994f0412f..8f7b4fecc 100644 --- a/frontend/src/app/components/master-page/master-page.component.ts +++ b/frontend/src/app/components/master-page/master-page.component.ts @@ -3,6 +3,7 @@ import { Env, StateService } from '../../services/state.service'; import { Observable, merge, of } from 'rxjs'; import { LanguageService } from '../../services/language.service'; import { EnterpriseService } from '../../services/enterprise.service'; +import { NavigationService } from '../../services/navigation.service'; @Component({ selector: 'app-master-page', @@ -18,11 +19,13 @@ export class MasterPageComponent implements OnInit { officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE; urlLanguage: string; subdomain = ''; + networkPaths: { [network: string]: string }; constructor( public stateService: StateService, private languageService: LanguageService, private enterpriseService: EnterpriseService, + private navigationService: NavigationService, ) { } ngOnInit() { @@ -31,6 +34,10 @@ export class MasterPageComponent implements OnInit { this.network$ = merge(of(''), this.stateService.networkChanged$); this.urlLanguage = this.languageService.getLanguageForUrl(); this.subdomain = this.enterpriseService.getSubdomain(); + this.navigationService.subnetPaths.subscribe((paths) => { + console.log('network paths updated...'); + this.networkPaths = paths; + }); } collapse(): void { diff --git a/frontend/src/app/docs/docs.routing.module.ts b/frontend/src/app/docs/docs.routing.module.ts index 52eb54f2e..0f01016de 100644 --- a/frontend/src/app/docs/docs.routing.module.ts +++ b/frontend/src/app/docs/docs.routing.module.ts @@ -39,6 +39,7 @@ if (browserWindowEnv.BASE_MODULE && (browserWindowEnv.BASE_MODULE === 'bisq' || }, { path: 'faq', + data: { networks: ['bitcoin'] }, component: DocsComponent }, { diff --git a/frontend/src/app/graphs/graphs.routing.module.ts b/frontend/src/app/graphs/graphs.routing.module.ts index 57ef6cef7..bf0e0a0a7 100644 --- a/frontend/src/app/graphs/graphs.routing.module.ts +++ b/frontend/src/app/graphs/graphs.routing.module.ts @@ -37,10 +37,12 @@ const routes: Routes = [ children: [ { path: 'mining/pool/:slug', + data: { networks: ['bitcoin'] }, component: PoolComponent, }, { path: 'mining', + data: { networks: ['bitcoin'] }, component: StartComponent, children: [ { @@ -51,6 +53,7 @@ const routes: Routes = [ }, { path: 'mempool-block/:id', + data: { networks: ['bitcoin', 'liquid'] }, component: StartComponent, children: [ { @@ -61,62 +64,77 @@ const routes: Routes = [ }, { path: 'graphs', + data: { networks: ['bitcoin', 'liquid'] }, component: GraphsComponent, children: [ { path: 'mempool', + data: { networks: ['bitcoin', 'liquid'] }, component: StatisticsComponent, }, { path: 'mining/hashrate-difficulty', + data: { networks: ['bitcoin'] }, component: HashrateChartComponent, }, { path: 'mining/pools-dominance', + data: { networks: ['bitcoin'] }, component: HashrateChartPoolsComponent, }, { path: 'mining/pools', + data: { networks: ['bitcoin'] }, component: PoolRankingComponent, }, { path: 'mining/block-fees', + data: { networks: ['bitcoin'] }, component: BlockFeesGraphComponent, }, { path: 'mining/block-rewards', + data: { networks: ['bitcoin'] }, component: BlockRewardsGraphComponent, }, { path: 'mining/block-fee-rates', + data: { networks: ['bitcoin'] }, component: BlockFeeRatesGraphComponent, }, { path: 'mining/block-sizes-weights', + data: { networks: ['bitcoin'] }, component: BlockSizesWeightsGraphComponent, }, { path: 'lightning/nodes-networks', + data: { networks: ['bitcoin'] }, component: NodesNetworksChartComponent, }, { path: 'lightning/capacity', + data: { networks: ['bitcoin'] }, component: LightningStatisticsChartComponent, }, { path: 'lightning/nodes-per-isp', + data: { networks: ['bitcoin'] }, component: NodesPerISPChartComponent, }, { path: 'lightning/nodes-per-country', + data: { networks: ['bitcoin'] }, component: NodesPerCountryChartComponent, }, { path: 'lightning/nodes-map', + data: { networks: ['bitcoin'] }, component: NodesMap, }, { path: 'lightning/nodes-channels-map', + data: { networks: ['bitcoin'] }, component: NodesChannelsMap, }, { @@ -125,6 +143,7 @@ const routes: Routes = [ }, { path: 'mining/block-prediction', + data: { networks: ['bitcoin'] }, component: BlockPredictionGraphComponent, }, ] @@ -141,6 +160,7 @@ const routes: Routes = [ }, { path: 'tv', + data: { networks: ['bitcoin', 'liquid'] }, component: TelevisionComponent }, ]; diff --git a/frontend/src/app/lightning/lightning.routing.module.ts b/frontend/src/app/lightning/lightning.routing.module.ts index e2121f8e8..79c3bc297 100644 --- a/frontend/src/app/lightning/lightning.routing.module.ts +++ b/frontend/src/app/lightning/lightning.routing.module.ts @@ -21,10 +21,12 @@ const routes: Routes = [ }, { path: 'node/:public_key', + data: { networkSpecific: true }, component: NodeComponent, }, { path: 'channel/:short_id', + data: { networkSpecific: true }, component: ChannelComponent, }, { diff --git a/frontend/src/app/services/navigation.service.ts b/frontend/src/app/services/navigation.service.ts new file mode 100644 index 000000000..661a8c38f --- /dev/null +++ b/frontend/src/app/services/navigation.service.ts @@ -0,0 +1,90 @@ +import { Injectable } from '@angular/core'; +import { Router, ActivatedRoute, NavigationEnd, ActivatedRouteSnapshot } from '@angular/router'; +import { BehaviorSubject } from 'rxjs'; +import { filter, map } from 'rxjs/operators'; +import { StateService } from './state.service'; + +const networkModules = { + bitcoin: { + subnets: [ + { name: 'mainnet', path: '' }, + { name: 'testnet', path: '/testnet' }, + { name: 'signet', path: '/signet' }, + ], + }, + liquid: { + subnets: [ + { name: 'liquid', path: '' }, + { name: 'liquidtestnet', path: '/testnet' }, + ], + }, + bisq: { + subnets: [ + { name: 'bisq', path: '' }, + ], + }, +}; +const networks = Object.keys(networkModules); + +@Injectable({ + providedIn: 'root' +}) +export class NavigationService { + subnetPaths = new BehaviorSubject>({}); + + constructor( + private stateService: StateService, + private router: Router, + ) { + this.router.events.pipe( + filter(event => event instanceof NavigationEnd), + map(() => this.router.routerState.snapshot.root), + ).subscribe((state) => { + this.updateSubnetPaths(state); + }); + } + + // For each network (bitcoin/liquid/bisq), find and save the longest url path compatible with the current route + updateSubnetPaths(root: ActivatedRouteSnapshot): void { + let path = ''; + const networkPaths = {}; + let route = root; + // traverse the router state tree until all network paths are set, or we reach the end of the tree + while (!networks.reduce((acc, network) => acc && !!networkPaths[network], true) && route) { + // 'networkSpecific' paths may correspond to valid routes on other networks, but aren't directly compatible + // (e.g. we shouldn't link a mainnet transaction page to the same txid on testnet or liquid) + if (route.data?.networkSpecific) { + networks.forEach(network => { + if (networkPaths[network] == null) { + networkPaths[network] = path; + } + }); + } + // null or empty networks list is shorthand for "compatible with every network" + if (route.data?.networks?.length) { + // if the list is non-empty, only those networks are compatible + networks.forEach(network => { + if (!route.data.networks.includes(network)) { + if (networkPaths[network] == null) { + networkPaths[network] = path; + } + } + }); + } + if (route.url?.length) { + path = [path, ...route.url.map(segment => segment.path).filter(path => { + return path.length && !['testnet', 'signet'].includes(path); + })].join('/'); + } + route = route.firstChild; + } + + const subnetPaths = {}; + Object.entries(networkModules).forEach(([key, network]) => { + network.subnets.forEach(subnet => { + subnetPaths[subnet.name] = subnet.path + (networkPaths[key] != null ? networkPaths[key] : path); + }); + }); + this.subnetPaths.next(subnetPaths); + } +} From d66696489470ba1ccd5d6380915f2908c469c391 Mon Sep 17 00:00:00 2001 From: softsimon Date: Thu, 13 Oct 2022 17:15:17 +0400 Subject: [PATCH 27/76] Correcting i18n strings related to Lightning explorer --- .../transaction-preview.component.html | 2 +- .../tx-bowtie-graph-tooltip.component.html | 2 +- .../app/dashboard/dashboard.component.html | 2 +- .../channels-list.component.html | 2 +- .../group/group-preview.component.html | 2 +- .../app/lightning/group/group.component.html | 2 +- .../node/node-preview.component.html | 2 +- .../nodes-map/nodes-map.component.ts | 2 +- .../nodes-per-country.component.html | 4 +- .../nodes-per-isp-chart.component.html | 4 +- .../nodes-per-isp-preview.component.html | 2 +- .../oldest-nodes/oldest-nodes.component.html | 4 +- frontend/src/locale/messages.xlf | 186 +++++++----------- 13 files changed, 89 insertions(+), 127 deletions(-) diff --git a/frontend/src/app/components/transaction/transaction-preview.component.html b/frontend/src/app/components/transaction/transaction-preview.component.html index 76ef972c3..f023a77b1 100644 --- a/frontend/src/app/components/transaction/transaction-preview.component.html +++ b/frontend/src/app/components/transaction/transaction-preview.component.html @@ -24,7 +24,7 @@ ‎{{ transactionTime * 1000 | date:'yyyy-MM-dd HH:mm' }} - Fee {{ tx.fee | number }} sat + Fee {{ tx.fee | number }} sat diff --git a/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html b/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html index 6872438a0..cbf2f7d5a 100644 --- a/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html +++ b/frontend/src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html @@ -42,7 +42,7 @@ Input Output - Fee + Fee #{{ line.index + 1 }}

diff --git a/frontend/src/app/dashboard/dashboard.component.html b/frontend/src/app/dashboard/dashboard.component.html index a6c10721f..03cd613f4 100644 --- a/frontend/src/app/dashboard/dashboard.component.html +++ b/frontend/src/app/dashboard/dashboard.component.html @@ -123,7 +123,7 @@
- + diff --git a/frontend/src/app/lightning/channels-list/channels-list.component.html b/frontend/src/app/lightning/channels-list/channels-list.component.html index cfa660ffb..bedd2e21e 100644 --- a/frontend/src/app/lightning/channels-list/channels-list.component.html +++ b/frontend/src/app/lightning/channels-list/channels-list.component.html @@ -35,7 +35,7 @@ - + diff --git a/frontend/src/app/lightning/group/group-preview.component.html b/frontend/src/app/lightning/group/group-preview.component.html index 8a918be35..d821e2752 100644 --- a/frontend/src/app/lightning/group/group-preview.component.html +++ b/frontend/src/app/lightning/group/group-preview.component.html @@ -1,6 +1,6 @@
- Lightning node group + Lightning node group
diff --git a/frontend/src/app/lightning/group/group.component.html b/frontend/src/app/lightning/group/group.component.html index fdc79b692..f7dfd0585 100644 --- a/frontend/src/app/lightning/group/group.component.html +++ b/frontend/src/app/lightning/group/group.component.html @@ -1,5 +1,5 @@
-
Lightning node group
+
Lightning node group
diff --git a/frontend/src/app/lightning/node/node-preview.component.html b/frontend/src/app/lightning/node/node-preview.component.html index 2c936edf4..89d5d4245 100644 --- a/frontend/src/app/lightning/node/node-preview.component.html +++ b/frontend/src/app/lightning/node/node-preview.component.html @@ -1,6 +1,6 @@
- lightning node + Lightning node

diff --git a/frontend/src/app/lightning/nodes-map/nodes-map.component.ts b/frontend/src/app/lightning/nodes-map/nodes-map.component.ts index 8d611e00f..db13ca6fe 100644 --- a/frontend/src/app/lightning/nodes-map/nodes-map.component.ts +++ b/frontend/src/app/lightning/nodes-map/nodes-map.component.ts @@ -47,7 +47,7 @@ export class NodesMap implements OnInit, OnChanges { ngOnInit(): void { if (!this.widget) { - this.seoService.setTitle($localize`:@@:af8560ca50882114be16c951650f83bca73161a7:Lightning Nodes World Map`); + this.seoService.setTitle($localize`:@@af8560ca50882114be16c951650f83bca73161a7:Lightning Nodes World Map`); } if (!this.inputNodes$) { diff --git a/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.html b/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.html index b1c84e2fc..e2c4553b8 100644 --- a/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.html +++ b/frontend/src/app/lightning/nodes-per-country/nodes-per-country.component.html @@ -1,6 +1,6 @@
-

- Lightning nodes in {{ country?.name }} +

+ Lightning nodes in {{ country?.name }} {{ country?.flag }}

diff --git a/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html b/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html index a72540c70..60f5ca9d4 100644 --- a/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html +++ b/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html @@ -33,8 +33,8 @@
- - (Tor nodes excluded) + + (Tor nodes excluded)
diff --git a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.html b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.html index 4db69156f..c1a998935 100644 --- a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.html +++ b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.html @@ -1,6 +1,6 @@
- lightning ISP + Lightning ISP
diff --git a/frontend/src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.html b/frontend/src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.html index 40df83a53..e29a85b2a 100644 --- a/frontend/src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.html +++ b/frontend/src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.html @@ -1,6 +1,6 @@
-

- Top 100 oldest lightning nodes +

+ Top 100 oldest lightning nodes

diff --git a/frontend/src/locale/messages.xlf b/frontend/src/locale/messages.xlf index fd5e19096..8a5d91038 100644 --- a/frontend/src/locale/messages.xlf +++ b/frontend/src/locale/messages.xlf @@ -991,7 +991,7 @@ src/app/components/transaction/transaction.component.html - 272,274 + 276,278 transaction.version @@ -1120,11 +1120,11 @@ src/app/components/transaction/transaction.component.html - 246,251 + 250,255 src/app/components/transaction/transaction.component.html - 390,396 + 394,400 transaction.details @@ -1140,11 +1140,11 @@ src/app/components/transaction/transaction.component.html - 233,237 + 237,241 src/app/components/transaction/transaction.component.html - 361,367 + 365,371 Transaction inputs and outputs transaction.inputs-and-outputs @@ -1161,7 +1161,7 @@ src/app/components/transaction/transaction.component.ts - 127,126 + 135,134 @@ -1933,7 +1933,7 @@ src/app/components/transaction/transaction.component.html - 254,256 + 258,260 src/app/dashboard/dashboard.component.html @@ -1965,7 +1965,7 @@ src/app/components/transaction/transaction.component.html - 262,264 + 266,268 block.weight @@ -2112,9 +2112,21 @@ src/app/components/block-overview-tooltip/block-overview-tooltip.component.html 22 + + src/app/components/transaction/transaction-preview.component.html + 27 + src/app/components/transaction/transaction.component.html - 460 + 464 + + + src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html + 45 + + + src/app/dashboard/dashboard.component.html + 127,129 Transaction fee transaction.fee @@ -2131,7 +2143,7 @@ src/app/components/transaction/transaction.component.html - 460,461 + 464,465 src/app/components/transactions-list/transactions-list.component.html @@ -2152,7 +2164,7 @@ src/app/components/transaction/transaction.component.html - 463,465 + 467,469 src/app/lightning/channel/channel-box/channel-box.component.html @@ -2162,6 +2174,10 @@ src/app/lightning/channel/channel-preview.component.html 31,34 + + src/app/lightning/channels-list/channels-list.component.html + 38,39 + Transaction fee rate transaction.fee-rate @@ -2237,11 +2253,11 @@ src/app/components/transaction/transaction.component.html - 465,468 + 469,472 src/app/components/transaction/transaction.component.html - 476,478 + 480,482 src/app/components/transactions-list/transactions-list.component.html @@ -2270,7 +2286,7 @@ src/app/components/transaction/transaction.component.html - 258,261 + 262,265 Transaction Virtual Size transaction.vsize @@ -2580,7 +2596,7 @@ src/app/components/transaction/transaction.component.html - 238,243 + 242,247 src/app/lightning/channel/channel.component.html @@ -3119,6 +3135,10 @@ src/app/lightning/nodes-map/nodes-map.component.html 5,7 + + src/app/lightning/nodes-map/nodes-map.component.ts + 50 + lightning.lightning.nodes-heatmap @@ -3692,7 +3712,7 @@ src/app/components/transaction/transaction.component.html - 280,281 + 284,285 transaction.hex @@ -4033,15 +4053,6 @@ 96 - - Fee - - src/app/components/transaction/transaction-preview.component.html - 27 - - Transaction fee - transaction.fee - This transaction has been replaced by: @@ -4141,24 +4152,24 @@ src/app/components/transaction/transaction.component.html - 330,334 + 334,338 Transaction flow transaction.flow - - Hide flow diagram + + Hide diagram src/app/components/transaction/transaction.component.html 198,203 - hide-flow-diagram + hide-diagram Show more src/app/components/transaction/transaction.component.html - 215,217 + 219,221 show-more @@ -4166,23 +4177,23 @@ Show less src/app/components/transaction/transaction.component.html - 217,223 + 221,227 show-less - - Show flow diagram + + Show diagram src/app/components/transaction/transaction.component.html - 237,238 + 241,242 - show + show-diagram Locktime src/app/components/transaction/transaction.component.html - 276,278 + 280,282 transaction.locktime @@ -4190,7 +4201,7 @@ Transaction not found. src/app/components/transaction/transaction.component.html - 439,440 + 443,444 transaction.error.transaction-not-found @@ -4198,7 +4209,7 @@ Waiting for it to appear in the mempool... src/app/components/transaction/transaction.component.html - 440,445 + 444,449 transaction.error.waiting-for-it-to-appear @@ -4206,7 +4217,7 @@ Effective fee rate src/app/components/transaction/transaction.component.html - 473,476 + 477,480 Effective transaction fee rate transaction.effective-fee-rate @@ -4375,18 +4386,6 @@ transaction.output - - Fee - - src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html - 45 - - - src/app/dashboard/dashboard.component.html - 127,129 - - transaction.fee - This transaction saved % on fees by using native SegWit @@ -5067,15 +5066,6 @@ status - - Fee Rate - - src/app/lightning/channels-list/channels-list.component.html - 38,39 - - Transaction fee rate - transaction.fee-rate - Channel ID @@ -5238,7 +5228,7 @@ src/app/lightning/group/group.component.html 2,6 - lightning.node + lightning.node-group Nodes @@ -5487,12 +5477,20 @@ mining.percentage-change-last-week - - lightning node + + Lightning node src/app/lightning/node/node-preview.component.html 3,5 + + src/app/lightning/node/node.component.html + 2,4 + + + src/app/lightning/node/node.component.html + 165,167 + lightning.node @@ -5527,18 +5525,6 @@ country - - Lightning node - - src/app/lightning/node/node.component.html - 2,4 - - - src/app/lightning/node/node.component.html - 165,167 - - lightning.node - No node found for public key "" @@ -5640,6 +5626,10 @@ src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html 10,15 + + src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html + 37,41 + lightning.tor-nodes-excluded @@ -5664,13 +5654,6 @@ lightning.active-channels-map - - af8560ca50882114be16c951650f83bca73161a7:Lightning Nodes World Map - - src/app/lightning/nodes-map/nodes-map.component.ts - 50 - - Indexing in progess @@ -5749,16 +5732,11 @@ 104,102 - - Lightning nodes in + + Lightning nodes in src/app/lightning/nodes-per-country/nodes-per-country.component.html - 3,7 + 3,4 lightning.nodes-in-country @@ -5853,18 +5831,6 @@ lightning.top-100-isp-ln - - (Tor nodes excluded) - - src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html - 37,39 - - lightning.tor-nodes-excluded - BTC @@ -5876,13 +5842,13 @@ 190,188 - - lightning ISP + + Lightning ISP src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.html 3,5 - lightning.node + lightning.node-isp Top country @@ -5931,15 +5897,11 @@ lightning.asn - - Top 100 oldest lightning nodes + + Top 100 oldest lightning nodes src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.html - 3,6 + 3,7 lightning.top-100-oldest-nodes From 7b5a5795f67ac4990dfded61482e16dd7a1881dc Mon Sep 17 00:00:00 2001 From: softsimon Date: Thu, 13 Oct 2022 17:40:13 +0400 Subject: [PATCH 28/76] More Lightning i18n fixes --- .../top-nodes-per-capacity.component.ts | 2 +- .../top-nodes-per-channels.component.ts | 6 ---- .../nodes-rankings-dashboard.component.html | 2 +- frontend/src/locale/messages.xlf | 30 +++++-------------- 4 files changed, 10 insertions(+), 30 deletions(-) diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts index bdfd22e1f..766e7f090 100644 --- a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts @@ -26,7 +26,7 @@ export class TopNodesPerCapacity implements OnInit { ngOnInit(): void { if (!this.widget) { - this.seoService.setTitle($localize`Liquidity Ranking`); + this.seoService.setTitle($localize`:@@2d9883d230a47fbbb2ec969e32a186597ea27405:Liquidity Ranking`); } for (let i = 1; i <= (this.widget ? (isMobile() ? 8 : 7) : 100); ++i) { diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts index 719a69663..2c88e4bae 100644 --- a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts @@ -1,7 +1,6 @@ import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; import { map, Observable } from 'rxjs'; import { INodesRanking, ITopNodesPerChannels } from '../../../interfaces/node-api.interface'; -import { SeoService } from '../../../services/seo.service'; import { isMobile } from '../../../shared/common.utils'; import { GeolocationData } from '../../../shared/components/geolocation/geolocation.component'; import { LightningApiService } from '../../lightning-api.service'; @@ -21,14 +20,9 @@ export class TopNodesPerChannels implements OnInit { constructor( private apiService: LightningApiService, - private seoService: SeoService ) {} ngOnInit(): void { - if (!this.widget) { - this.seoService.setTitle($localize`Connectivity Ranking`); - } - for (let i = 1; i <= (this.widget ? (isMobile() ? 8 : 7) : 100); ++i) { this.skeletonRows.push(i); } diff --git a/frontend/src/app/lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component.html b/frontend/src/app/lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component.html index 97802a1d9..9d81cf3c2 100644 --- a/frontend/src/app/lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component.html +++ b/frontend/src/app/lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component.html @@ -19,7 +19,7 @@
-
Connectivity ranking
+
Connectivity Ranking
  diff --git a/frontend/src/locale/messages.xlf b/frontend/src/locale/messages.xlf index 8a5d91038..98835b30b 100644 --- a/frontend/src/locale/messages.xlf +++ b/frontend/src/locale/messages.xlf @@ -5444,6 +5444,10 @@ src/app/lightning/lightning-dashboard/lightning-dashboard.component.html 62 + + src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts + 29 + lightning.liquidity-ranking @@ -5452,6 +5456,10 @@ src/app/lightning/lightning-dashboard/lightning-dashboard.component.html 76 + + src/app/lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component.html + 22 + lightning.connectivity-ranking @@ -5920,13 +5928,6 @@ lightning.top-100-liquidity - - Liquidity Ranking - - src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts - 29 - - Top 100 nodes connectivity ranking @@ -5935,13 +5936,6 @@ lightning.top-100-connectivity - - Connectivity Ranking - - src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts - 29 - - Liquidity ranking @@ -5950,14 +5944,6 @@ lightning.liquidity-ranking - - Connectivity ranking - - src/app/lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component.html - 22 - - lightning.connectivity-ranking - Oldest nodes From 0fb9cfc908dc34f27fa22050acafaf85e3843555 Mon Sep 17 00:00:00 2001 From: softsimon Date: Thu, 13 Oct 2022 17:51:28 +0400 Subject: [PATCH 29/76] More Lightning i18n fixes (2) --- .../nodes-rankings-dashboard.component.html | 2 +- .../lightning-statistics-chart.component.ts | 6 ++-- frontend/src/locale/messages.xlf | 31 ++++++++++--------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/frontend/src/app/lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component.html b/frontend/src/app/lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component.html index 9d81cf3c2..1623d917e 100644 --- a/frontend/src/app/lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component.html +++ b/frontend/src/app/lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component.html @@ -5,7 +5,7 @@
-
Liquidity ranking
+
Liquidity Ranking
  diff --git a/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts b/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts index 24a365a39..7667f57bb 100644 --- a/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts +++ b/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts @@ -191,7 +191,7 @@ export class LightningStatisticsChartComponent implements OnInit { padding: 10, data: [ { - name: 'Channels', + name: $localize`:@@807cf11e6ac1cde912496f764c176bdfdd6b7e19:Channels`, inactiveColor: 'rgb(110, 112, 121)', textStyle: { color: 'white', @@ -199,7 +199,7 @@ export class LightningStatisticsChartComponent implements OnInit { icon: 'roundRect', }, { - name: 'Capacity', + name: $localize`:@@ce9dfdc6dccb28dc75a78c704e09dc18fb02dcfa:Capacity`, inactiveColor: 'rgb(110, 112, 121)', textStyle: { color: 'white', @@ -279,7 +279,7 @@ export class LightningStatisticsChartComponent implements OnInit { { zlevel: 0, yAxisIndex: 1, - name: $localize`Capacity`, + name: $localize`:@@ce9dfdc6dccb28dc75a78c704e09dc18fb02dcfa:Capacity`, showSymbol: false, symbol: 'none', stack: 'Total', diff --git a/frontend/src/locale/messages.xlf b/frontend/src/locale/messages.xlf index 98835b30b..480ed19c0 100644 --- a/frontend/src/locale/messages.xlf +++ b/frontend/src/locale/messages.xlf @@ -4905,6 +4905,14 @@ src/app/lightning/nodes-per-isp/nodes-per-isp.component.html 60,62 + + src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts + 202,201 + + + src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts + 282,279 + lightning.capacity @@ -5360,6 +5368,10 @@ src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html 11,12 + + src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts + 194,193 + lightning.channels @@ -5448,6 +5460,10 @@ src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts 29 + + src/app/lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component.html + 8 + lightning.liquidity-ranking @@ -5936,14 +5952,6 @@ lightning.top-100-connectivity - - Liquidity ranking - - src/app/lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component.html - 8 - - lightning.liquidity-ranking - Oldest nodes @@ -5967,13 +5975,6 @@ lightning.indexing-in-progress - - Capacity - - src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts - 282,279 - - year From 3a322bb752440f1d0020ac45badbe34efdef3c47 Mon Sep 17 00:00:00 2001 From: softsimon Date: Thu, 13 Oct 2022 18:09:56 +0400 Subject: [PATCH 30/76] Lightning pie chart i18n tooltip fix --- .../nodes-per-country-chart.component.ts | 2 +- .../nodes-per-isp-chart/nodes-per-isp-chart.component.ts | 4 ++-- frontend/src/locale/messages.xlf | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts index 03d6967fe..681688842 100644 --- a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts +++ b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts @@ -100,7 +100,7 @@ export class NodesPerCountryChartComponent implements OnInit { borderColor: '#000', formatter: () => { return `${country.name.en} (${country.share}%)
` + - $localize`${country.count.toString()} nodes
` + + $localize`${country.count.toString()} nodes` + `
` + $localize`${this.amountShortenerPipe.transform(country.capacity / 100000000, 2)} BTC capacity` ; } diff --git a/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts b/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts index 3299c529c..caaa350d6 100644 --- a/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts +++ b/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts @@ -154,7 +154,7 @@ export class NodesPerISPChartComponent implements OnInit { borderColor: '#000', formatter: () => { return `${isp[1]} (${this.sortBy === 'capacity' ? isp[7] : isp[6]}%)
` + - $localize`${isp[4].toString()} nodes
` + + $localize`${isp[4].toString()} nodes` + `
` + $localize`${this.amountShortenerPipe.transform(isp[2] / 100000000, 2)} BTC` ; } @@ -186,7 +186,7 @@ export class NodesPerISPChartComponent implements OnInit { borderColor: '#000', formatter: () => { return `Other (${totalShareOther.toFixed(2)}%)
` + - $localize`${nodeCountOther.toString()} nodes
` + + $localize`${nodeCountOther.toString()} nodes` + `
` + $localize`${this.amountShortenerPipe.transform(capacityOther / 100000000, 2)} BTC`; } }, diff --git a/frontend/src/locale/messages.xlf b/frontend/src/locale/messages.xlf index 480ed19c0..1a0fba692 100644 --- a/frontend/src/locale/messages.xlf +++ b/frontend/src/locale/messages.xlf @@ -5734,8 +5734,8 @@
lightning.share
- - nodes<br> + + nodes src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts 103,102 From ed23ac461a4b2b00cbec7e19b54185a397dc37d1 Mon Sep 17 00:00:00 2001 From: softsimon Date: Thu, 13 Oct 2022 18:12:29 +0400 Subject: [PATCH 31/76] More Lightning i18n fixes (4) --- .../lightning-dashboard.component.ts | 2 +- frontend/src/locale/messages.xlf | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts index b14d65ae0..6fa4b454c 100644 --- a/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts +++ b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts @@ -24,7 +24,7 @@ export class LightningDashboardComponent implements OnInit { ) { } ngOnInit(): void { - this.seoService.setTitle($localize`Lightning Network`); + this.seoService.setTitle($localize`:@@142e923d3b04186ac6ba23387265d22a2fa404e0:Lightning Explorer`); this.nodesRanking$ = this.lightningApiService.getNodesRanking$().pipe(share()); this.statistics$ = this.lightningApiService.getLatestStatistics$().pipe(share()); diff --git a/frontend/src/locale/messages.xlf b/frontend/src/locale/messages.xlf index 1a0fba692..43d2dafde 100644 --- a/frontend/src/locale/messages.xlf +++ b/frontend/src/locale/messages.xlf @@ -3259,6 +3259,10 @@ src/app/components/master-page/master-page.component.html 44,45 + + src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts + 27 + master-page.lightning @@ -5478,13 +5482,6 @@ lightning.connectivity-ranking - - Lightning Network - - src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts - 27 - - Percentage change past week From 2f78a166cd92f54dccea3445797d771c96fd8b35 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sat, 15 Oct 2022 00:45:48 +0000 Subject: [PATCH 32/76] API method for node fee histogram data --- backend/src/api/explorer/nodes.api.ts | 50 +++++++++++++++++++ backend/src/api/explorer/nodes.routes.ts | 17 +++++++ .../app/lightning/lightning-api.service.ts | 4 ++ 3 files changed, 71 insertions(+) diff --git a/backend/src/api/explorer/nodes.api.ts b/backend/src/api/explorer/nodes.api.ts index cbd70a34f..d8dceab19 100644 --- a/backend/src/api/explorer/nodes.api.ts +++ b/backend/src/api/explorer/nodes.api.ts @@ -129,6 +129,56 @@ class NodesApi { } } + public async $getFeeHistogram(node_public_key: string): Promise { + try { + const inQuery = ` + SELECT CASE WHEN fee_rate <= 10.0 THEN CEIL(fee_rate) + WHEN (fee_rate > 10.0 and fee_rate <= 100.0) THEN CEIL(fee_rate / 10.0) * 10.0 + WHEN (fee_rate > 100.0 and fee_rate <= 1000.0) THEN CEIL(fee_rate / 100.0) * 100.0 + WHEN fee_rate > 1000.0 THEN CEIL(fee_rate / 1000.0) * 1000.0 + END as bucket, + count(short_id) as count, + sum(capacity) as capacity + FROM ( + SELECT CASE WHEN node1_public_key = ? THEN node2_fee_rate WHEN node2_public_key = ? THEN node1_fee_rate END as fee_rate, + short_id as short_id, + capacity as capacity + FROM channels + WHERE status = 1 AND (channels.node1_public_key = ? OR channels.node2_public_key = ?) + ) as fee_rate_table + GROUP BY bucket; + `; + const [inRows]: any[] = await DB.query(inQuery, [node_public_key, node_public_key, node_public_key, node_public_key]); + + const outQuery = ` + SELECT CASE WHEN fee_rate <= 10.0 THEN CEIL(fee_rate) + WHEN (fee_rate > 10.0 and fee_rate <= 100.0) THEN CEIL(fee_rate / 10.0) * 10.0 + WHEN (fee_rate > 100.0 and fee_rate <= 1000.0) THEN CEIL(fee_rate / 100.0) * 100.0 + WHEN fee_rate > 1000.0 THEN CEIL(fee_rate / 1000.0) * 1000.0 + END as bucket, + count(short_id) as count, + sum(capacity) as capacity + FROM ( + SELECT CASE WHEN node1_public_key = ? THEN node1_fee_rate WHEN node2_public_key = ? THEN node2_fee_rate END as fee_rate, + short_id as short_id, + capacity as capacity + FROM channels + WHERE status = 1 AND (channels.node1_public_key = ? OR channels.node2_public_key = ?) + ) as fee_rate_table + GROUP BY bucket; + `; + const [outRows]: any[] = await DB.query(outQuery, [node_public_key, node_public_key, node_public_key, node_public_key]); + + return { + incoming: inRows.length > 0 ? inRows : [], + outgoing: outRows.length > 0 ? outRows : [], + }; + } catch (e) { + logger.err(`Cannot get node fee distribution for ${node_public_key}. Reason: ${(e instanceof Error ? e.message : e)}`); + throw e; + } + } + public async $getAllNodes(): Promise { try { const query = `SELECT * FROM nodes`; diff --git a/backend/src/api/explorer/nodes.routes.ts b/backend/src/api/explorer/nodes.routes.ts index 589e337cf..c19bde236 100644 --- a/backend/src/api/explorer/nodes.routes.ts +++ b/backend/src/api/explorer/nodes.routes.ts @@ -20,6 +20,7 @@ class NodesRoutes { .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/rankings/connectivity', this.$getTopNodesByChannels) .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/rankings/age', this.$getOldestNodes) .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/:public_key/statistics', this.$getHistoricalNodeStats) + .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/:public_key/fees/histogram', this.$getFeeHistogram) .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/:public_key', this.$getNode) .get(config.MEMPOOL.API_URL_PREFIX + 'lightning/nodes/group/:name', this.$getNodeGroup) ; @@ -95,6 +96,22 @@ class NodesRoutes { } } + private async $getFeeHistogram(req: Request, res: Response) { + try { + const node = await nodesApi.$getFeeHistogram(req.params.public_key); + if (!node) { + res.status(404).send('Node not found'); + return; + } + res.header('Pragma', 'public'); + res.header('Cache-control', 'public'); + res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); + res.json(node); + } catch (e) { + res.status(500).send(e instanceof Error ? e.message : e); + } + } + private async $getNodesRanking(req: Request, res: Response): Promise { try { const topCapacityNodes = await nodesApi.$getTopCapacityNodes(false); diff --git a/frontend/src/app/lightning/lightning-api.service.ts b/frontend/src/app/lightning/lightning-api.service.ts index 7a38538ff..6ea550591 100644 --- a/frontend/src/app/lightning/lightning-api.service.ts +++ b/frontend/src/app/lightning/lightning-api.service.ts @@ -53,6 +53,10 @@ export class LightningApiService { return this.httpClient.get(this.apiBasePath + '/api/v1/lightning/nodes/' + publicKey + '/statistics'); } + getNodeFeeHistogram$(publicKey: string): Observable { + return this.httpClient.get(this.apiBasePath + '/api/v1/lightning/nodes/' + publicKey + '/fees/histogram'); + } + getNodesRanking$(): Observable { return this.httpClient.get(this.apiBasePath + '/api/v1/lightning/nodes/rankings'); } From 70b30edd25cf4bfe2e6e5f663c33fa7e53c7e59a Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sat, 15 Oct 2022 01:19:45 +0000 Subject: [PATCH 33/76] Add fee histogram chart to the node page --- .../src/app/lightning/lightning.module.ts | 3 + .../node-fee-chart.component.html | 7 + .../node-fee-chart.component.scss | 5 + .../node-fee-chart.component.ts | 265 ++++++++++++++++++ .../app/lightning/node/node.component.html | 2 + 5 files changed, 282 insertions(+) create mode 100644 frontend/src/app/lightning/node-fee-chart/node-fee-chart.component.html create mode 100644 frontend/src/app/lightning/node-fee-chart/node-fee-chart.component.scss create mode 100644 frontend/src/app/lightning/node-fee-chart/node-fee-chart.component.ts diff --git a/frontend/src/app/lightning/lightning.module.ts b/frontend/src/app/lightning/lightning.module.ts index 3c06fb023..fa2f1a1ec 100644 --- a/frontend/src/app/lightning/lightning.module.ts +++ b/frontend/src/app/lightning/lightning.module.ts @@ -15,6 +15,7 @@ import { ChannelBoxComponent } from './channel/channel-box/channel-box.component import { ClosingTypeComponent } from './channel/closing-type/closing-type.component'; import { LightningStatisticsChartComponent } from './statistics-chart/lightning-statistics-chart.component'; import { NodeStatisticsChartComponent } from './node-statistics-chart/node-statistics-chart.component'; +import { NodeFeeChartComponent } from './node-fee-chart/node-fee-chart.component'; import { GraphsModule } from '../graphs/graphs.module'; import { NodesNetworksChartComponent } from './nodes-networks-chart/nodes-networks-chart.component'; import { ChannelsStatisticsComponent } from './channels-statistics/channels-statistics.component'; @@ -38,6 +39,7 @@ import { GroupComponent } from './group/group.component'; NodesListComponent, NodeStatisticsComponent, NodeStatisticsChartComponent, + NodeFeeChartComponent, NodeComponent, ChannelsListComponent, ChannelComponent, @@ -73,6 +75,7 @@ import { GroupComponent } from './group/group.component'; NodesListComponent, NodeStatisticsComponent, NodeStatisticsChartComponent, + NodeFeeChartComponent, NodeComponent, ChannelsListComponent, ChannelComponent, diff --git a/frontend/src/app/lightning/node-fee-chart/node-fee-chart.component.html b/frontend/src/app/lightning/node-fee-chart/node-fee-chart.component.html new file mode 100644 index 000000000..c8f674f11 --- /dev/null +++ b/frontend/src/app/lightning/node-fee-chart/node-fee-chart.component.html @@ -0,0 +1,7 @@ +
+

Fee distribution

+
+
+
d +
+
diff --git a/frontend/src/app/lightning/node-fee-chart/node-fee-chart.component.scss b/frontend/src/app/lightning/node-fee-chart/node-fee-chart.component.scss new file mode 100644 index 000000000..d738daa81 --- /dev/null +++ b/frontend/src/app/lightning/node-fee-chart/node-fee-chart.component.scss @@ -0,0 +1,5 @@ +.full-container { + margin-top: 25px; + margin-bottom: 25px; + min-height: 100%; +} diff --git a/frontend/src/app/lightning/node-fee-chart/node-fee-chart.component.ts b/frontend/src/app/lightning/node-fee-chart/node-fee-chart.component.ts new file mode 100644 index 000000000..f0370d3e1 --- /dev/null +++ b/frontend/src/app/lightning/node-fee-chart/node-fee-chart.component.ts @@ -0,0 +1,265 @@ +import { Component, Inject, Input, LOCALE_ID, OnInit, HostBinding } from '@angular/core'; +import { EChartsOption } from 'echarts'; +import { switchMap } from 'rxjs/operators'; +import { download } from '../../shared/graphs.utils'; +import { LightningApiService } from '../lightning-api.service'; +import { ActivatedRoute, ParamMap } from '@angular/router'; +import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe'; + +@Component({ + selector: 'app-node-fee-chart', + templateUrl: './node-fee-chart.component.html', + styleUrls: ['./node-fee-chart.component.scss'], + styles: [` + .loadingGraphs { + position: absolute; + top: 50%; + left: calc(50% - 15px); + z-index: 100; + } + `], +}) +export class NodeFeeChartComponent implements OnInit { + chartOptions: EChartsOption = {}; + chartInitOptions = { + renderer: 'svg', + }; + + @HostBinding('attr.dir') dir = 'ltr'; + + isLoading = true; + chartInstance: any = undefined; + + constructor( + @Inject(LOCALE_ID) public locale: string, + private lightningApiService: LightningApiService, + private activatedRoute: ActivatedRoute, + private amountShortenerPipe: AmountShortenerPipe, + ) { + } + + ngOnInit(): void { + + this.activatedRoute.paramMap + .pipe( + switchMap((params: ParamMap) => { + this.isLoading = true; + return this.lightningApiService.getNodeFeeHistogram$(params.get('public_key')); + }), + ).subscribe((data) => { + if (data && data.incoming && data.outgoing) { + const outgoingHistogram = this.bucketsToHistogram(data.outgoing); + const incomingHistogram = this.bucketsToHistogram(data.incoming); + this.prepareChartOptions(outgoingHistogram, incomingHistogram); + } + this.isLoading = false; + }); + } + + bucketsToHistogram(buckets): { label: string, count: number, capacity: number}[] { + const histogram = []; + let increment = 1; + let lower = -increment; + let upper = 0; + + let nullBucket; + if (buckets.length && buckets[0] && buckets[0].bucket == null) { + nullBucket = buckets.shift(); + } + + while (upper <= 5000) { + let bucket; + if (buckets.length && buckets[0] && upper >= Number(buckets[0].bucket)) { + bucket = buckets.shift(); + } + histogram.push({ + label: upper === 0 ? '0 ppm' : `${lower} - ${upper} ppm`, + count: Number(bucket?.count || 0) + (upper === 0 ? Number(nullBucket?.count || 0) : 0), + capacity: Number(bucket?.capacity || 0) + (upper === 0 ? Number(nullBucket?.capacity || 0) : 0), + }); + + if (upper >= increment * 10) { + increment *= 10; + lower = increment; + upper = increment + increment; + } else { + lower += increment; + upper += increment; + } + } + const rest = buckets.reduce((acc, bucket) => { + acc.count += Number(bucket.count); + acc.capacity += Number(bucket.capacity); + return acc; + }, { count: 0, capacity: 0 }); + histogram.push({ + label: `5000+ ppm`, + count: rest.count, + capacity: rest.capacity, + }); + return histogram; + } + + prepareChartOptions(outgoingData, incomingData): void { + let title: object; + if (outgoingData.length === 0) { + title = { + textStyle: { + color: 'grey', + fontSize: 15 + }, + text: $localize`No data to display yet. Try again later.`, + left: 'center', + top: 'center' + }; + } + + this.chartOptions = { + title: outgoingData.length === 0 ? title : undefined, + animation: false, + grid: { + top: 30, + bottom: 20, + right: 20, + left: 65, + }, + tooltip: { + show: !this.isMobile(), + 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): string => { + return ` + ${ticks[0].data.label}
+
+ ${ticks[0].marker} Outgoing
+ Capacity: ${this.amountShortenerPipe.transform(ticks[0].data.capacity, 2, undefined, true)} sats
+ Channels: ${ticks[0].data.count}
+
+ ${ticks[1].marker} Incoming
+ Capacity: ${this.amountShortenerPipe.transform(ticks[1].data.capacity, 2, undefined, true)} sats
+ Channels: ${ticks[1].data.count}
+ `; + } + }, + xAxis: outgoingData.length === 0 ? undefined : { + type: 'category', + axisLine: { onZero: true }, + axisLabel: { + align: 'center', + fontSize: 11, + lineHeight: 12, + hideOverlap: true, + padding: [0, 5], + }, + data: outgoingData.map(bucket => bucket.label) + }, + legend: outgoingData.length === 0 ? undefined : { + padding: 10, + data: [ + { + name: 'Outgoing Fees', + inactiveColor: 'rgb(110, 112, 121)', + textStyle: { + color: 'white', + }, + icon: 'roundRect', + }, + { + name: 'Incoming Fees', + inactiveColor: 'rgb(110, 112, 121)', + textStyle: { + color: 'white', + }, + icon: 'roundRect', + }, + ], + }, + yAxis: outgoingData.length === 0 ? undefined : [ + { + type: 'value', + axisLabel: { + color: 'rgb(110, 112, 121)', + formatter: (val) => { + return `${this.amountShortenerPipe.transform(Math.abs(val), 2, undefined, true)} sats`; + } + }, + splitLine: { + lineStyle: { + type: 'dotted', + color: '#ffffff66', + opacity: 0.25, + } + }, + }, + ], + series: outgoingData.length === 0 ? undefined : [ + { + zlevel: 0, + name: 'Outgoing Fees', + data: outgoingData.map(bucket => ({ + value: bucket.capacity, + label: bucket.label, + capacity: bucket.capacity, + count: bucket.count, + })), + type: 'bar', + barWidth: '90%', + barMaxWidth: 50, + stack: 'fees', + }, + { + zlevel: 0, + name: 'Incoming Fees', + data: incomingData.map(bucket => ({ + value: -bucket.capacity, + label: bucket.label, + capacity: bucket.capacity, + count: bucket.count, + })), + type: 'bar', + barWidth: '90%', + barMaxWidth: 50, + stack: 'fees', + }, + ], + }; + } + + onChartInit(ec) { + if (this.chartInstance !== undefined) { + return; + } + + this.chartInstance = ec; + } + + isMobile() { + return (window.innerWidth <= 767.98); + } + + onSaveChart() { + // @ts-ignore + const prevBottom = this.chartOptions.grid.bottom; + // @ts-ignore + this.chartOptions.grid.bottom = 40; + this.chartOptions.backgroundColor = '#11131f'; + this.chartInstance.setOption(this.chartOptions); + download(this.chartInstance.getDataURL({ + pixelRatio: 2, + }), `node-fee-chart.svg`); + // @ts-ignore + this.chartOptions.grid.bottom = prevBottom; + this.chartOptions.backgroundColor = 'none'; + this.chartInstance.setOption(this.chartOptions); + } +} diff --git a/frontend/src/app/lightning/node/node.component.html b/frontend/src/app/lightning/node/node.component.html index c6e3e794c..7d506e6b0 100644 --- a/frontend/src/app/lightning/node/node.component.html +++ b/frontend/src/app/lightning/node/node.component.html @@ -140,6 +140,8 @@ + +

Open channels From fb94be1767ae5d28e9eb1407a02f58c716ec4b8e Mon Sep 17 00:00:00 2001 From: softsimon Date: Fri, 14 Oct 2022 05:09:25 +0400 Subject: [PATCH 34/76] Handle instant block, txid and address search fixes #2619 --- .../search-form/search-form.component.ts | 149 +++++++++++------- .../search-results.component.html | 28 +++- .../search-results.component.ts | 2 +- 3 files changed, 119 insertions(+), 60 deletions(-) diff --git a/frontend/src/app/components/search-form/search-form.component.ts b/frontend/src/app/components/search-form/search-form.component.ts index 3cff7c188..dc9ea8940 100644 --- a/frontend/src/app/components/search-form/search-form.component.ts +++ b/frontend/src/app/components/search-form/search-form.component.ts @@ -3,8 +3,8 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Router } from '@angular/router'; import { AssetsService } from '../../services/assets.service'; import { StateService } from '../../services/state.service'; -import { Observable, of, Subject, zip, BehaviorSubject } from 'rxjs'; -import { debounceTime, distinctUntilChanged, switchMap, catchError, map } from 'rxjs/operators'; +import { Observable, of, Subject, zip, BehaviorSubject, combineLatest } from 'rxjs'; +import { debounceTime, distinctUntilChanged, switchMap, catchError, map, startWith, tap } from 'rxjs/operators'; import { ElectrsApiService } from '../../services/electrs-api.service'; import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe'; import { ApiService } from '../../services/api.service'; @@ -33,7 +33,7 @@ export class SearchFormComponent implements OnInit { @Output() searchTriggered = new EventEmitter(); @ViewChild('searchResults') searchResults: SearchResultsComponent; - @HostListener('keydown', ['$event']) keydown($event) { + @HostListener('keydown', ['$event']) keydown($event): void { this.handleKeyDown($event); } @@ -47,7 +47,7 @@ export class SearchFormComponent implements OnInit { private relativeUrlPipe: RelativeUrlPipe, ) { } - ngOnInit() { + ngOnInit(): void { this.stateService.networkChanged$.subscribe((network) => this.network = network); this.searchForm = this.formBuilder.group({ @@ -61,70 +61,111 @@ export class SearchFormComponent implements OnInit { }); } - this.typeAhead$ = this.searchForm.get('searchText').valueChanges - .pipe( - map((text) => { - if (this.network === 'bisq' && text.match(/^(b)[^c]/i)) { - return text.substr(1); - } - return text.trim(); - }), - debounceTime(200), - distinctUntilChanged(), - switchMap((text) => { - if (!text.length) { - return of([ - '', - [], - { - nodes: [], - channels: [], - } - ]); - } - this.isTypeaheading$.next(true); - if (!this.stateService.env.LIGHTNING) { - return zip( - of(text), - this.electrsApiService.getAddressesByPrefix$(text).pipe(catchError(() => of([]))), - [{ nodes: [], channels: [] }], - of(this.regexBlockheight.test(text)), - ); - } + const searchText$ = this.searchForm.get('searchText').valueChanges + .pipe( + map((text) => { + if (this.network === 'bisq' && text.match(/^(b)[^c]/i)) { + return text.substr(1); + } + return text.trim(); + }), + distinctUntilChanged(), + ); + + const searchResults$ = searchText$.pipe( + debounceTime(200), + switchMap((text) => { + if (!text.length) { + return of([ + [], + { nodes: [], channels: [] } + ]); + } + this.isTypeaheading$.next(true); + if (!this.stateService.env.LIGHTNING) { return zip( - of(text), this.electrsApiService.getAddressesByPrefix$(text).pipe(catchError(() => of([]))), - this.apiService.lightningSearch$(text).pipe(catchError(() => of({ + [{ nodes: [], channels: [] }], + ); + } + return zip( + this.electrsApiService.getAddressesByPrefix$(text).pipe(catchError(() => of([]))), + this.apiService.lightningSearch$(text).pipe(catchError(() => of({ + nodes: [], + channels: [], + }))), + ); + }), + tap((result: any[]) => { + this.isTypeaheading$.next(false); + }) + ); + + this.typeAhead$ = combineLatest( + [ + searchText$, + searchResults$.pipe( + startWith([ + [], + { + nodes: [], + channels: [], + } + ])) + ] + ).pipe( + map((latestData) => { + const searchText = latestData[0]; + if (!searchText.length) { + return { + searchText: '', + hashQuickMatch: false, + blockHeight: false, + txId: false, + address: false, + addresses: [], nodes: [], channels: [], - }))), - ); - }), - map((result: any[]) => { - this.isTypeaheading$.next(false); - if (this.network === 'bisq') { - return result[0].map((address: string) => 'B' + address); + }; } + + const result = latestData[1]; + const addressPrefixSearchResults = result[0]; + const lightningResults = result[1]; + + if (this.network === 'bisq') { + return searchText.map((address: string) => 'B' + address); + } + + const matchesBlockHeight = this.regexBlockheight.test(searchText); + const matchesTxId = this.regexTransaction.test(searchText) && !this.regexBlockhash.test(searchText); + const matchesBlockHash = this.regexBlockhash.test(searchText); + const matchesAddress = this.regexAddress.test(searchText); + return { - searchText: result[0], - blockHeight: this.regexBlockheight.test(result[0]) ? [parseInt(result[0], 10)] : [], - addresses: result[1], - nodes: result[2].nodes, - channels: result[2].channels, - totalResults: result[1].length + result[2].nodes.length + result[2].channels.length, + searchText: searchText, + hashQuickMatch: +(matchesBlockHeight || matchesBlockHash || matchesTxId || matchesAddress), + blockHeight: matchesBlockHeight, + txId: matchesTxId, + blockHash: matchesBlockHash, + address: matchesAddress, + addresses: addressPrefixSearchResults, + nodes: lightningResults.nodes, + channels: lightningResults.channels, }; }) ); } - handleKeyDown($event) { + + handleKeyDown($event): void { this.searchResults.handleKeyDown($event); } - itemSelected() { + itemSelected(): void { setTimeout(() => this.search()); } - selectedResult(result: any) { + selectedResult(result: any): void { if (typeof result === 'string') { this.search(result); } else if (typeof result === 'number') { @@ -136,7 +177,7 @@ export class SearchFormComponent implements OnInit { } } - search(result?: string) { + search(result?: string): void { const searchText = result || this.searchForm.value.searchText.trim(); if (searchText) { this.isSearching = true; @@ -170,7 +211,7 @@ export class SearchFormComponent implements OnInit { } } - navigate(url: string, searchText: string, extras?: any) { + navigate(url: string, searchText: string, extras?: any): void { this.router.navigate([this.relativeUrlPipe.transform(url), searchText], extras); this.searchTriggered.emit(); this.searchForm.setValue({ diff --git a/frontend/src/app/components/search-form/search-results/search-results.component.html b/frontend/src/app/components/search-form/search-results/search-results.component.html index 9ed829aff..4e8b9bb7c 100644 --- a/frontend/src/app/components/search-form/search-results/search-results.component.html +++ b/frontend/src/app/components/search-form/search-results/search-results.component.html @@ -1,14 +1,32 @@ - -
+
- - -
+ +
+ + +
+
+
diff --git a/frontend/src/app/components/television/television.component.scss b/frontend/src/app/components/television/television.component.scss index 50ee7b543..9a6cbcc24 100644 --- a/frontend/src/app/components/television/television.component.scss +++ b/frontend/src/app/components/television/television.component.scss @@ -31,8 +31,9 @@ .position-container { position: absolute; - left: 50%; + left: 0; bottom: 170px; + transform: translateX(50vw); } #divider { @@ -47,9 +48,33 @@ top: -28px; } } + + &.time-ltr { + .blocks-wrapper { + transform: scaleX(-1); + } + } } + +:host-context(.ltr-layout) { + .blockchain-wrapper.time-ltr .blocks-wrapper, + .blockchain-wrapper .blocks-wrapper { + direction: ltr; + } +} + +:host-context(.rtl-layout) { + .blockchain-wrapper.time-ltr .blocks-wrapper, + .blockchain-wrapper .blocks-wrapper { + direction: rtl; + } +} + .tv-container { display: flex; margin-top: 0px; flex-direction: column; } + + + diff --git a/frontend/src/app/components/television/television.component.ts b/frontend/src/app/components/television/television.component.ts index ab1770972..5e3888aa4 100644 --- a/frontend/src/app/components/television/television.component.ts +++ b/frontend/src/app/components/television/television.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; import { WebsocketService } from '../../services/websocket.service'; import { OptimizedMempoolStats } from '../../interfaces/node-api.interface'; import { StateService } from '../../services/state.service'; @@ -6,7 +6,7 @@ import { ApiService } from '../../services/api.service'; import { SeoService } from '../../services/seo.service'; import { ActivatedRoute } from '@angular/router'; import { map, scan, startWith, switchMap, tap } from 'rxjs/operators'; -import { interval, merge, Observable } from 'rxjs'; +import { interval, merge, Observable, Subscription } from 'rxjs'; import { ChangeDetectionStrategy } from '@angular/core'; @Component({ @@ -15,11 +15,13 @@ import { ChangeDetectionStrategy } from '@angular/core'; styleUrls: ['./television.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) -export class TelevisionComponent implements OnInit { +export class TelevisionComponent implements OnInit, OnDestroy { mempoolStats: OptimizedMempoolStats[] = []; statsSubscription$: Observable; fragment: string; + timeLtrSubscription: Subscription; + timeLtr: boolean = this.stateService.timeLtr.value; constructor( private websocketService: WebsocketService, @@ -37,6 +39,10 @@ export class TelevisionComponent implements OnInit { this.seoService.setTitle($localize`:@@46ce8155c9ab953edeec97e8950b5a21e67d7c4e:TV view`); this.websocketService.want(['blocks', 'live-2h-chart', 'mempool-blocks']); + this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => { + this.timeLtr = !!ltr; + }); + this.statsSubscription$ = merge( this.stateService.live2Chart$.pipe(map(stats => [stats])), this.route.fragment @@ -70,4 +76,8 @@ export class TelevisionComponent implements OnInit { }) ); } + + ngOnDestroy() { + this.timeLtrSubscription.unsubscribe(); + } } From 4cd77fd580aaecd44efa6a993c3b438b6c18496f Mon Sep 17 00:00:00 2001 From: hunicus <93150691+hunicus@users.noreply.github.com> Date: Wed, 26 Oct 2022 12:33:13 -0400 Subject: [PATCH 59/76] Add electrum rpc doc tab for official instance --- .../src/app/docs/api-docs/api-docs.component.html | 13 +++++++++++++ .../src/app/docs/api-docs/api-docs.component.scss | 12 ++++++++++++ frontend/src/app/docs/docs/docs.component.html | 9 +++++++++ frontend/src/app/docs/docs/docs.component.ts | 7 ++++++- 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/docs/api-docs/api-docs.component.html b/frontend/src/app/docs/api-docs/api-docs.component.html index f106c4bc5..90c35252a 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.html +++ b/frontend/src/app/docs/api-docs/api-docs.component.html @@ -106,6 +106,19 @@

+
+
+
+ +

This part of the API is available to sponsors only—whitelisting is required.

+
+ +

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

+
+
+
+
+
diff --git a/frontend/src/app/docs/api-docs/api-docs.component.scss b/frontend/src/app/docs/api-docs/api-docs.component.scss index 456983657..aebaafe6f 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.scss +++ b/frontend/src/app/docs/api-docs/api-docs.component.scss @@ -1,3 +1,11 @@ +.center { + text-align: center; +} + +.note { + font-style: italic; +} + .text-small { font-size: 12px; } @@ -116,6 +124,10 @@ li.nav-item { float: right; } +.doc-content.no-sidebar { + width: 100% +} + h3 { margin: 2rem 0 0 0; } diff --git a/frontend/src/app/docs/docs/docs.component.html b/frontend/src/app/docs/docs/docs.component.html index 04f9bb8cf..9e7f57c74 100644 --- a/frontend/src/app/docs/docs/docs.component.html +++ b/frontend/src/app/docs/docs/docs.component.html @@ -32,6 +32,15 @@ +
  • + API - Electrum RPC + + + + + +
  • +
    diff --git a/frontend/src/app/docs/docs/docs.component.ts b/frontend/src/app/docs/docs/docs.component.ts index 74cebc88f..c129cd21e 100644 --- a/frontend/src/app/docs/docs/docs.component.ts +++ b/frontend/src/app/docs/docs/docs.component.ts @@ -15,6 +15,7 @@ export class DocsComponent implements OnInit { env: Env; showWebSocketTab = true; showFaqTab = true; + showElectrsTab = true; @HostBinding('attr.dir') dir = 'ltr'; @@ -34,14 +35,18 @@ export class DocsComponent implements OnInit { } else if( url[1].path === "rest" ) { this.activeTab = 1; this.seoService.setTitle($localize`:@@e351b40b3869a5c7d19c3d4918cb1ac7aaab95c4:API`); - } else { + } else if( url[1].path === "websocket" ) { this.activeTab = 2; this.seoService.setTitle($localize`:@@e351b40b3869a5c7d19c3d4918cb1ac7aaab95c4:API`); + } else { + this.activeTab = 3; + this.seoService.setTitle($localize`:@@e351b40b3869a5c7d19c3d4918cb1ac7aaab95c4:API`); } this.env = this.stateService.env; this.showWebSocketTab = ( ! ( ( this.stateService.network === "bisq" ) || ( this.stateService.network === "liquidtestnet" ) ) ); this.showFaqTab = ( this.env.BASE_MODULE === 'mempool' ) ? true : false; + this.showElectrsTab = this.stateService.env.OFFICIAL_MEMPOOL_SPACE && ( this.stateService.network === "" || this.stateService.network === "mainnet" || this.stateService.network === "testnet" || this.stateService.network === "signet" ); document.querySelector( "html" ).style.scrollBehavior = "smooth"; } From cd0e6bae6462d34e781c57e9632c75834fbcbcbe Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 10 Oct 2022 22:13:04 +0000 Subject: [PATCH 60/76] New projected block transaction selection algo --- backend/src/api/mempool-blocks.ts | 229 ++++++++++++++++++++++++++- backend/src/api/websocket-handler.ts | 22 ++- backend/src/mempool.interfaces.ts | 11 +- 3 files changed, 254 insertions(+), 8 deletions(-) diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index 5eb5aa9c8..9b58f4754 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -1,5 +1,5 @@ import logger from '../logger'; -import { MempoolBlock, TransactionExtended, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta } from '../mempool.interfaces'; +import { MempoolBlock, TransactionExtended, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta, TransactionSet, Ancestor } from '../mempool.interfaces'; import { Common } from './common'; import config from '../config'; @@ -99,6 +99,7 @@ class MempoolBlocks { if (transactions.length) { mempoolBlocks.push(this.dataToMempoolBlocks(transactions, blockSize, blockWeight, mempoolBlocks.length)); } + // Calculate change from previous block states for (let i = 0; i < Math.max(mempoolBlocks.length, prevBlocks.length); i++) { let added: TransactionStripped[] = []; @@ -132,12 +133,238 @@ class MempoolBlocks { removed }); } + return { blocks: mempoolBlocks, deltas: mempoolBlockDeltas }; } + /* + * Build projected mempool blocks using an approximation of the transaction selection algorithm from Bitcoin Core + * (see BlockAssembler in https://github.com/bitcoin/bitcoin/blob/master/src/node/miner.cpp) + * + * templateLimit: number of blocks to build using the full algo, + * remaining blocks up to blockLimit will skip the expensive updateDescendants step + * + * blockLimit: number of blocks to build in total. Excess transactions will be ignored. + */ + public makeBlockTemplates(mempool: { [txid: string]: TransactionExtended }, templateLimit: number = Infinity, blockLimit: number = Infinity): MempoolBlockWithTransactions[] { + const start = new Date().getTime(); + const txSets: { [txid: string]: TransactionSet } = {}; + const mempoolArray: TransactionExtended[] = Object.values(mempool); + + mempoolArray.forEach((tx) => { + tx.bestDescendant = null; + tx.ancestors = []; + tx.cpfpChecked = false; + tx.effectiveFeePerVsize = tx.feePerVsize; + txSets[tx.txid] = { + fee: 0, + weight: 1, + score: 0, + children: [], + available: true, + modified: false, + }; + }); + + // Build relatives graph & calculate ancestor scores + mempoolArray.forEach((tx) => { + this.setRelatives(tx, mempool, txSets); + }); + + // Sort by descending ancestor score + const byAncestor = (a, b): number => this.sortByAncestorScore(a, b, txSets); + mempoolArray.sort(byAncestor); + + // Build blocks by greedily choosing the highest feerate package + // (i.e. the package rooted in the transaction with the best ancestor score) + const blocks: MempoolBlockWithTransactions[] = []; + let blockWeight = 4000; + let blockSize = 0; + let transactions: TransactionExtended[] = []; + let modified: TransactionExtended[] = []; + let overflow: TransactionExtended[] = []; + let failures = 0; + while ((mempoolArray.length || modified.length) && blocks.length < blockLimit) { + const simpleMode = blocks.length >= templateLimit; + let anyModified = false; + // Select best next package + let nextTx; + if (mempoolArray.length && (!modified.length || txSets[mempoolArray[0].txid]?.score > txSets[modified[0].txid]?.score)) { + nextTx = mempoolArray.shift(); + if (txSets[nextTx?.txid]?.modified) { + nextTx = null; + } + } else { + nextTx = modified.shift(); + } + + if (nextTx && txSets[nextTx.txid]?.available) { + const nextTxSet = txSets[nextTx.txid]; + // Check if the package fits into this block + if (nextTxSet && blockWeight + nextTxSet.weight < config.MEMPOOL.BLOCK_WEIGHT_UNITS) { + blockWeight += nextTxSet.weight; + // sort txSet by dependency graph (equivalent to sorting by ascending ancestor count) + const sortedTxSet = nextTx.ancestors.sort((a, b) => { + return (mempool[a.txid]?.ancestors?.length || 0) - (mempool[b.txid]?.ancestors?.length || 0); + }); + [...sortedTxSet, nextTx].forEach((ancestor, i, arr) => { + const tx = mempool[ancestor.txid]; + const txSet = txSets[ancestor.txid]; + if (txSet.available) { + txSet.available = false; + tx.effectiveFeePerVsize = nextTxSet.fee / (nextTxSet.weight / 4); + tx.cpfpChecked = true; + if (i < arr.length - 1) { + tx.bestDescendant = { + txid: arr[i + 1].txid, + fee: arr[i + 1].fee, + weight: arr[i + 1].weight, + }; + } + transactions.push(tx); + blockSize += tx.size; + } + }); + + // remove these as valid package ancestors for any remaining descendants + if (!simpleMode) { + sortedTxSet.forEach(tx => { + anyModified = this.updateDescendants(tx, tx, mempool, txSets, modified); + }); + } + + failures = 0; + } else { + // hold this package in an overflow list while we check for smaller options + txSets[nextTx.txid].modified = true; + overflow.push(nextTx); + failures++; + } + } + + // this block is full + const outOfTransactions = !mempoolArray.length && !modified.length; + const exceededPackageTries = failures > 1000 && blockWeight > (config.MEMPOOL.BLOCK_WEIGHT_UNITS - 4000); + const exceededSimpleTries = failures > 0 && simpleMode; + if (outOfTransactions || exceededPackageTries || exceededSimpleTries) { + // construct this block + blocks.push(this.dataToMempoolBlocks(transactions, blockSize, blockWeight, blocks.length)); + // reset for the next block + transactions = []; + blockSize = 0; + blockWeight = 4000; + + // 'overflow' packages didn't fit in this block, but are valid candidates for the next + if (overflow.length) { + modified = modified.concat(overflow); + overflow = []; + anyModified = true; + } + } + + // re-sort modified list if necessary + if (anyModified) { + modified = modified.filter(tx => txSets[tx.txid]?.available).sort(byAncestor); + } + } + + const end = new Date().getTime(); + const time = end - start; + logger.debug('Mempool templates calculated in ' + time / 1000 + ' seconds'); + + return blocks; + } + + private sortByAncestorScore(a, b, txSets): number { + return txSets[b.txid]?.score - txSets[a.txid]?.score; + } + + private setRelatives(tx: TransactionExtended, mempool: { [txid: string]: TransactionExtended }, txSets: { [txid: string]: TransactionSet }): { [txid: string]: Ancestor } { + let ancestors: { [txid: string]: Ancestor } = {}; + tx.vin.forEach((parent) => { + const parentTx = mempool[parent.txid]; + const parentTxSet = txSets[parent.txid]; + if (parentTx && parentTxSet) { + ancestors[parentTx.txid] = parentTx; + if (!parentTxSet.children) { + parentTxSet.children = [tx.txid]; + } else { + parentTxSet.children.push(tx.txid); + } + if (!parentTxSet.score) { + ancestors = { + ...ancestors, + ...this.setRelatives(parentTx, mempool, txSets), + }; + } + } + }); + tx.ancestors = Object.values(ancestors).map(ancestor => { + return { + txid: ancestor.txid, + fee: ancestor.fee, + weight: ancestor.weight + }; + }); + let totalFees = tx.fee; + let totalWeight = tx.weight; + tx.ancestors.forEach(ancestor => { + totalFees += ancestor.fee; + totalWeight += ancestor.weight; + }); + txSets[tx.txid].fee = totalFees; + txSets[tx.txid].weight = totalWeight; + txSets[tx.txid].score = this.calcAncestorScore(tx, totalFees, totalWeight); + + return ancestors; + } + + private calcAncestorScore(tx: TransactionExtended, ancestorFees: number, ancestorWeight: number): number { + return Math.min(tx.fee / tx.weight, ancestorFees / ancestorWeight); + } + + // walk over remaining descendants, removing the root as a valid ancestor & updating the ancestor score + // returns whether any descendants were modified + private updateDescendants( + root: TransactionExtended, + tx: TransactionExtended, + mempool: { [txid: string]: TransactionExtended }, + txSets: { [txid: string]: TransactionSet }, + modified: TransactionExtended[], + ): boolean { + let anyModified = false; + const txSet = txSets[tx.txid]; + if (txSet.children) { + txSet.children.forEach(childId => { + const child = mempool[childId]; + if (child && child.ancestors && txSets[childId]?.available) { + const ancestorIndex = child.ancestors.findIndex(a => a.txid === root.txid); + if (ancestorIndex > -1) { + // remove tx as ancestor + child.ancestors.splice(ancestorIndex, 1); + const childTxSet = txSets[childId]; + childTxSet.fee -= root.fee; + childTxSet.weight -= root.weight; + childTxSet.score = this.calcAncestorScore(child, childTxSet.fee, childTxSet.weight); + anyModified = true; + + if (!childTxSet.modified) { + childTxSet.modified = true; + modified.push(child); + } + + // recursively update grandchildren + anyModified = this.updateDescendants(root, child, mempool, txSets, modified) || anyModified; + } + } + }); + } + return anyModified; + } + private dataToMempoolBlocks(transactions: TransactionExtended[], blockSize: number, blockWeight: number, blocksIndex: number): MempoolBlockWithTransactions { let rangeLength = 4; diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 4896ee058..f183a4799 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -414,15 +414,15 @@ class WebsocketHandler { let mBlockDeltas: undefined | MempoolBlockDelta[]; let matchRate = 0; const _memPool = memPool.getMempool(); - const _mempoolBlocks = mempoolBlocks.getMempoolBlocksWithTransactions(); + const projectedBlocks = mempoolBlocks.makeBlockTemplates(cloneMempool(_memPool), 1, 1); - if (_mempoolBlocks[0]) { + if (projectedBlocks[0]) { const matches: string[] = []; const added: string[] = []; const missing: string[] = []; for (const txId of txIds) { - if (_mempoolBlocks[0].transactionIds.indexOf(txId) > -1) { + if (projectedBlocks[0].transactionIds.indexOf(txId) > -1) { matches.push(txId); } else { added.push(txId); @@ -430,7 +430,7 @@ class WebsocketHandler { delete _memPool[txId]; } - for (const txId of _mempoolBlocks[0].transactionIds) { + for (const txId of projectedBlocks[0].transactionIds) { if (matches.includes(txId) || added.includes(txId)) { continue; } @@ -443,14 +443,14 @@ class WebsocketHandler { mBlockDeltas = mempoolBlocks.getMempoolBlockDeltas(); if (Common.indexingEnabled()) { - const stripped = _mempoolBlocks[0].transactions.map((tx) => { + const stripped = projectedBlocks[0].transactions.map((tx) => { return { txid: tx.txid, vsize: tx.vsize, fee: tx.fee ? Math.round(tx.fee) : 0, value: tx.value, }; - }); + }); BlocksSummariesRepository.$saveSummary({ height: block.height, template: { @@ -580,4 +580,14 @@ class WebsocketHandler { } } +function cloneMempool(mempool: { [txid: string]: TransactionExtended }): { [txid: string]: TransactionExtended } { + const cloned = {}; + Object.keys(mempool).forEach(id => { + cloned[id] = { + ...mempool[id] + }; + }); + return cloned; +} + export default new WebsocketHandler(); diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index d72b13576..fec26c0f3 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -70,12 +70,21 @@ export interface TransactionExtended extends IEsploraApi.Transaction { deleteAfter?: number; } -interface Ancestor { +export interface Ancestor { txid: string; weight: number; fee: number; } +export interface TransactionSet { + fee: number; + weight: number; + score: number; + children?: string[]; + available?: boolean; + modified?: boolean; +} + interface BestDescendant { txid: string; weight: number; From 04718b5e9de19cb8178a1bd92580e1e3bb248cb9 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 18 Oct 2022 21:03:21 +0000 Subject: [PATCH 61/76] Fix errors in block audit tx selection algorithm --- backend/src/api/mempool-blocks.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index 9b58f4754..2fb524b11 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -207,10 +207,10 @@ class MempoolBlocks { if (nextTxSet && blockWeight + nextTxSet.weight < config.MEMPOOL.BLOCK_WEIGHT_UNITS) { blockWeight += nextTxSet.weight; // sort txSet by dependency graph (equivalent to sorting by ascending ancestor count) - const sortedTxSet = nextTx.ancestors.sort((a, b) => { + const sortedTxSet = [...nextTx.ancestors.sort((a, b) => { return (mempool[a.txid]?.ancestors?.length || 0) - (mempool[b.txid]?.ancestors?.length || 0); - }); - [...sortedTxSet, nextTx].forEach((ancestor, i, arr) => { + }), nextTx]; + sortedTxSet.forEach((ancestor, i, arr) => { const tx = mempool[ancestor.txid]; const txSet = txSets[ancestor.txid]; if (txSet.available) { @@ -340,7 +340,7 @@ class MempoolBlocks { if (txSet.children) { txSet.children.forEach(childId => { const child = mempool[childId]; - if (child && child.ancestors && txSets[childId]?.available) { + if (child && child.ancestors) { const ancestorIndex = child.ancestors.findIndex(a => a.txid === root.txid); if (ancestorIndex > -1) { // remove tx as ancestor @@ -355,11 +355,12 @@ class MempoolBlocks { childTxSet.modified = true; modified.push(child); } - - // recursively update grandchildren - anyModified = this.updateDescendants(root, child, mempool, txSets, modified) || anyModified; } } + // recursively update grandchildren + if (child) { + anyModified = this.updateDescendants(root, child, mempool, txSets, modified) || anyModified; + } }); } return anyModified; From 6654107edb8a68b419c9045c1e462f45d17bcaf8 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 19 Oct 2022 17:10:45 +0000 Subject: [PATCH 62/76] improve audit analysis and scoring --- backend/src/api/audit.ts | 114 +++++++++++++++++++++++++++ backend/src/api/websocket-handler.ts | 28 +++---- 2 files changed, 123 insertions(+), 19 deletions(-) create mode 100644 backend/src/api/audit.ts diff --git a/backend/src/api/audit.ts b/backend/src/api/audit.ts new file mode 100644 index 000000000..6efb50938 --- /dev/null +++ b/backend/src/api/audit.ts @@ -0,0 +1,114 @@ +import { BlockExtended, TransactionExtended, MempoolBlockWithTransactions } from '../mempool.interfaces'; + +const PROPAGATION_MARGIN = 180; // in seconds, time since a transaction is first seen after which it is assumed to have propagated to all miners + +class Audit { + auditBlock(block: BlockExtended, txIds: string[], transactions: TransactionExtended[], + projectedBlocks: MempoolBlockWithTransactions[], mempool: { [txId: string]: TransactionExtended }, + ): { censored: string[], added: string[], score: number } { + const matches: string[] = []; // present in both mined block and template + const added: string[] = []; // present in mined block, not in template + const fresh: string[] = []; // missing, but firstSeen within PROPAGATION_MARGIN + const isCensored = {}; // missing, without excuse + const isDisplaced = {}; + let displacedWeight = 0; + + const inBlock = {}; + const inTemplate = {}; + + const now = Math.round((Date.now() / 1000)); + for (const tx of transactions) { + inBlock[tx.txid] = tx; + } + // coinbase is always expected + if (transactions[0]) { + inTemplate[transactions[0].txid] = true; + } + // look for transactions that were expected in the template, but missing from the mined block + for (const txid of projectedBlocks[0].transactionIds) { + if (!inBlock[txid]) { + // tx is recent, may have reached the miner too late for inclusion + if (mempool[txid]?.firstSeen != null && (now - (mempool[txid]?.firstSeen || 0)) <= PROPAGATION_MARGIN) { + fresh.push(txid); + } else { + isCensored[txid] = true; + } + displacedWeight += mempool[txid].weight; + } + inTemplate[txid] = true; + } + + displacedWeight += (4000 - transactions[0].weight); + + logger.warn(`${fresh.length} fresh, ${Object.keys(isCensored).length} possibly censored, ${displacedWeight} displaced weight`); + + // we can expect an honest miner to include 'displaced' transactions in place of recent arrivals and censored txs + // these displaced transactions should occupy the first N weight units of the next projected block + let displacedWeightRemaining = displacedWeight; + let index = 0; + let lastFeeRate = Infinity; + let failures = 0; + while (projectedBlocks[1] && index < projectedBlocks[1].transactionIds.length && failures < 500) { + const txid = projectedBlocks[1].transactionIds[index]; + const fits = (mempool[txid].weight - displacedWeightRemaining) < 4000; + const feeMatches = mempool[txid].effectiveFeePerVsize >= lastFeeRate; + if (fits || feeMatches) { + isDisplaced[txid] = true; + if (fits) { + lastFeeRate = Math.min(lastFeeRate, mempool[txid].effectiveFeePerVsize); + } + if (mempool[txid].firstSeen == null || (now - (mempool[txid]?.firstSeen || 0)) > PROPAGATION_MARGIN) { + displacedWeightRemaining -= mempool[txid].weight; + } + failures = 0; + } else { + failures++; + } + index++; + } + + // mark unexpected transactions in the mined block as 'added' + let overflowWeight = 0; + for (const tx of transactions) { + if (inTemplate[tx.txid]) { + matches.push(tx.txid); + } else { + if (!isDisplaced[tx.txid]) { + added.push(tx.txid); + } + overflowWeight += tx.weight; + } + } + + // transactions missing from near the end of our template are probably not being censored + let overflowWeightRemaining = overflowWeight; + let lastOverflowRate = 1.00; + index = projectedBlocks[0].transactionIds.length - 1; + while (index >= 0) { + const txid = projectedBlocks[0].transactionIds[index]; + if (overflowWeightRemaining > 0) { + if (isCensored[txid]) { + delete isCensored[txid]; + } + lastOverflowRate = mempool[txid].effectiveFeePerVsize; + } else if (Math.floor(mempool[txid].effectiveFeePerVsize * 100) <= Math.ceil(lastOverflowRate * 100)) { // tolerance of 0.01 sat/vb + if (isCensored[txid]) { + delete isCensored[txid]; + } + } + overflowWeightRemaining -= (mempool[txid]?.weight || 0); + index--; + } + + const numCensored = Object.keys(isCensored).length; + const score = matches.length > 0 ? (matches.length / (matches.length + numCensored)) : 0; + + return { + censored: Object.keys(isCensored), + added, + score + }; + } +} + +export default new Audit(); \ No newline at end of file diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index f183a4799..c3d4dad9b 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -18,6 +18,7 @@ import difficultyAdjustment from './difficulty-adjustment'; import feeApi from './fee-api'; import BlocksAuditsRepository from '../repositories/BlocksAuditsRepository'; import BlocksSummariesRepository from '../repositories/BlocksSummariesRepository'; +import Audit from './audit'; class WebsocketHandler { private wss: WebSocket.Server | undefined; @@ -405,7 +406,7 @@ class WebsocketHandler { }); } - handleNewBlock(block: BlockExtended, txIds: string[], transactions: TransactionExtended[]) { + handleNewBlock(block: BlockExtended, txIds: string[], transactions: TransactionExtended[]): void { if (!this.wss) { throw new Error('WebSocket.Server is not set'); } @@ -414,30 +415,19 @@ class WebsocketHandler { let mBlockDeltas: undefined | MempoolBlockDelta[]; let matchRate = 0; const _memPool = memPool.getMempool(); - const projectedBlocks = mempoolBlocks.makeBlockTemplates(cloneMempool(_memPool), 1, 1); + const mempoolCopy = cloneMempool(_memPool); + + const projectedBlocks = mempoolBlocks.makeBlockTemplates(mempoolCopy, 2, 2); if (projectedBlocks[0]) { - const matches: string[] = []; - const added: string[] = []; - const missing: string[] = []; + const { censored, added, score } = Audit.auditBlock(block, txIds, transactions, projectedBlocks, mempoolCopy); + matchRate = Math.round(score * 100 * 100) / 100; + // Update mempool to remove transactions included in the new block for (const txId of txIds) { - if (projectedBlocks[0].transactionIds.indexOf(txId) > -1) { - matches.push(txId); - } else { - added.push(txId); - } delete _memPool[txId]; } - for (const txId of projectedBlocks[0].transactionIds) { - if (matches.includes(txId) || added.includes(txId)) { - continue; - } - missing.push(txId); - } - - matchRate = Math.round((Math.max(0, matches.length - missing.length - added.length) / txIds.length * 100) * 100) / 100; mempoolBlocks.updateMempoolBlocks(_memPool); mBlocks = mempoolBlocks.getMempoolBlocks(); mBlockDeltas = mempoolBlocks.getMempoolBlockDeltas(); @@ -464,7 +454,7 @@ class WebsocketHandler { height: block.height, hash: block.id, addedTxs: added, - missingTxs: missing, + missingTxs: censored, matchRate: matchRate, }); } From c81bee048a84df3e1219f294e9a2aa7f2c67c242 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Thu, 27 Oct 2022 10:21:39 -0600 Subject: [PATCH 63/76] Optimize makeBlockTemplates --- backend/src/api/audit.ts | 1 + backend/src/api/mempool-blocks.ts | 365 +++++++++++++++------------ backend/src/api/websocket-handler.ts | 4 +- backend/src/mempool.interfaces.ts | 22 +- backend/src/utils/pairing-heap.ts | 174 +++++++++++++ 5 files changed, 406 insertions(+), 160 deletions(-) create mode 100644 backend/src/utils/pairing-heap.ts diff --git a/backend/src/api/audit.ts b/backend/src/api/audit.ts index 6efb50938..2d9fbc430 100644 --- a/backend/src/api/audit.ts +++ b/backend/src/api/audit.ts @@ -1,3 +1,4 @@ +import logger from '../logger'; import { BlockExtended, TransactionExtended, MempoolBlockWithTransactions } from '../mempool.interfaces'; const PROPAGATION_MARGIN = 180; // in seconds, time since a transaction is first seen after which it is assumed to have propagated to all miners diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index 2fb524b11..d0c2a4f63 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -1,7 +1,8 @@ import logger from '../logger'; -import { MempoolBlock, TransactionExtended, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta, TransactionSet, Ancestor } from '../mempool.interfaces'; +import { MempoolBlock, TransactionExtended, AuditTransaction, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor } from '../mempool.interfaces'; import { Common } from './common'; import config from '../config'; +import { PairingHeap } from '../utils/pairing-heap'; class MempoolBlocks { private mempoolBlocks: MempoolBlockWithTransactions[] = []; @@ -72,6 +73,7 @@ class MempoolBlocks { logger.debug('Mempool blocks calculated in ' + time / 1000 + ' seconds'); const { blocks, deltas } = this.calculateMempoolBlocks(memPoolArray, this.mempoolBlocks); + this.mempoolBlocks = blocks; this.mempoolBlockDeltas = deltas; } @@ -144,226 +146,273 @@ class MempoolBlocks { * Build projected mempool blocks using an approximation of the transaction selection algorithm from Bitcoin Core * (see BlockAssembler in https://github.com/bitcoin/bitcoin/blob/master/src/node/miner.cpp) * - * templateLimit: number of blocks to build using the full algo, - * remaining blocks up to blockLimit will skip the expensive updateDescendants step - * - * blockLimit: number of blocks to build in total. Excess transactions will be ignored. + * blockLimit: number of blocks to build in total. + * weightLimit: maximum weight of transactions to consider using the selection algorithm. + * if weightLimit is significantly lower than the mempool size, results may start to diverge from getBlockTemplate + * condenseRest: whether to ignore excess transactions or append them to the final block. */ - public makeBlockTemplates(mempool: { [txid: string]: TransactionExtended }, templateLimit: number = Infinity, blockLimit: number = Infinity): MempoolBlockWithTransactions[] { - const start = new Date().getTime(); - const txSets: { [txid: string]: TransactionSet } = {}; - const mempoolArray: TransactionExtended[] = Object.values(mempool); - - mempoolArray.forEach((tx) => { - tx.bestDescendant = null; - tx.ancestors = []; - tx.cpfpChecked = false; - tx.effectiveFeePerVsize = tx.feePerVsize; - txSets[tx.txid] = { - fee: 0, - weight: 1, + public makeBlockTemplates(mempool: { [txid: string]: TransactionExtended }, blockLimit: number, weightLimit: number | null = null, condenseRest = false): MempoolBlockWithTransactions[] { + const start = Date.now(); + const auditPool: { [txid: string]: AuditTransaction } = {}; + const mempoolArray: AuditTransaction[] = []; + const restOfArray: TransactionExtended[] = []; + + let weight = 0; + const maxWeight = weightLimit ? Math.max(4_000_000 * blockLimit, weightLimit) : Infinity; + // grab the top feerate txs up to maxWeight + Object.values(mempool).sort((a, b) => b.feePerVsize - a.feePerVsize).forEach(tx => { + weight += tx.weight; + if (weight >= maxWeight) { + restOfArray.push(tx); + return; + } + // initializing everything up front helps V8 optimize property access later + auditPool[tx.txid] = { + txid: tx.txid, + fee: tx.fee, + size: tx.size, + weight: tx.weight, + feePerVsize: tx.feePerVsize, + vin: tx.vin, + relativesSet: false, + ancestorMap: new Map(), + children: new Set(), + ancestorFee: 0, + ancestorWeight: 0, score: 0, - children: [], - available: true, + used: false, modified: false, - }; - }); + modifiedNode: null, + } + mempoolArray.push(auditPool[tx.txid]); + }) // Build relatives graph & calculate ancestor scores - mempoolArray.forEach((tx) => { - this.setRelatives(tx, mempool, txSets); - }); + for (const tx of mempoolArray) { + if (!tx.relativesSet) { + this.setRelatives(tx, auditPool); + } + } // Sort by descending ancestor score - const byAncestor = (a, b): number => this.sortByAncestorScore(a, b, txSets); - mempoolArray.sort(byAncestor); + mempoolArray.sort((a, b) => (b.score || 0) - (a.score || 0)); // Build blocks by greedily choosing the highest feerate package // (i.e. the package rooted in the transaction with the best ancestor score) const blocks: MempoolBlockWithTransactions[] = []; let blockWeight = 4000; let blockSize = 0; - let transactions: TransactionExtended[] = []; - let modified: TransactionExtended[] = []; - let overflow: TransactionExtended[] = []; + let transactions: AuditTransaction[] = []; + const modified: PairingHeap = new PairingHeap((a, b): boolean => (a.score || 0) > (b.score || 0)); + let overflow: AuditTransaction[] = []; let failures = 0; - while ((mempoolArray.length || modified.length) && blocks.length < blockLimit) { - const simpleMode = blocks.length >= templateLimit; - let anyModified = false; - // Select best next package - let nextTx; - if (mempoolArray.length && (!modified.length || txSets[mempoolArray[0].txid]?.score > txSets[modified[0].txid]?.score)) { - nextTx = mempoolArray.shift(); - if (txSets[nextTx?.txid]?.modified) { - nextTx = null; - } - } else { - nextTx = modified.shift(); + let top = 0; + while ((top < mempoolArray.length || !modified.isEmpty()) && (condenseRest || blocks.length < blockLimit)) { + // skip invalid transactions + while (top < mempoolArray.length && (mempoolArray[top].used || mempoolArray[top].modified)) { + top++; } - if (nextTx && txSets[nextTx.txid]?.available) { - const nextTxSet = txSets[nextTx.txid]; + // Select best next package + let nextTx; + const nextPoolTx = mempoolArray[top]; + const nextModifiedTx = modified.peek(); + if (nextPoolTx && (!nextModifiedTx || (nextPoolTx.score || 0) > (nextModifiedTx.score || 0))) { + nextTx = nextPoolTx; + top++; + } else { + modified.pop(); + if (nextModifiedTx) { + nextTx = nextModifiedTx; + nextTx.modifiedNode = undefined; + } + } + + if (nextTx && !nextTx?.used) { // Check if the package fits into this block - if (nextTxSet && blockWeight + nextTxSet.weight < config.MEMPOOL.BLOCK_WEIGHT_UNITS) { - blockWeight += nextTxSet.weight; - // sort txSet by dependency graph (equivalent to sorting by ascending ancestor count) - const sortedTxSet = [...nextTx.ancestors.sort((a, b) => { - return (mempool[a.txid]?.ancestors?.length || 0) - (mempool[b.txid]?.ancestors?.length || 0); - }), nextTx]; + if (blockWeight + nextTx.ancestorWeight < config.MEMPOOL.BLOCK_WEIGHT_UNITS) { + blockWeight += nextTx.ancestorWeight; + const ancestors: AuditTransaction[] = Array.from(nextTx.ancestorMap.values()); + // sort ancestors by dependency graph (equivalent to sorting by ascending ancestor count) + const sortedTxSet = [...ancestors.sort((a, b) => { return (a.ancestorMap.size || 0) - (b.ancestorMap.size || 0); }), nextTx]; + const effectiveFeeRate = nextTx.ancestorFee / (nextTx.ancestorWeight / 4); sortedTxSet.forEach((ancestor, i, arr) => { - const tx = mempool[ancestor.txid]; - const txSet = txSets[ancestor.txid]; - if (txSet.available) { - txSet.available = false; - tx.effectiveFeePerVsize = nextTxSet.fee / (nextTxSet.weight / 4); - tx.cpfpChecked = true; + const mempoolTx = mempool[ancestor.txid]; + if (ancestor && !ancestor?.used) { + ancestor.used = true; + // update original copy of this tx with effective fee rate & relatives data + mempoolTx.effectiveFeePerVsize = effectiveFeeRate; + mempoolTx.ancestors = (Array.from(ancestor.ancestorMap?.values()) as AuditTransaction[]).map((a) => { + return { + txid: a.txid, + fee: a.fee, + weight: a.weight, + } + }) if (i < arr.length - 1) { - tx.bestDescendant = { - txid: arr[i + 1].txid, - fee: arr[i + 1].fee, - weight: arr[i + 1].weight, + mempoolTx.bestDescendant = { + txid: arr[arr.length - 1].txid, + fee: arr[arr.length - 1].fee, + weight: arr[arr.length - 1].weight, }; } - transactions.push(tx); - blockSize += tx.size; + transactions.push(ancestor); + blockSize += ancestor.size; } }); - // remove these as valid package ancestors for any remaining descendants - if (!simpleMode) { + // remove these as valid package ancestors for any descendants remaining in the mempool + if (sortedTxSet.length) { sortedTxSet.forEach(tx => { - anyModified = this.updateDescendants(tx, tx, mempool, txSets, modified); + this.updateDescendants(tx, auditPool, modified); }); } failures = 0; } else { // hold this package in an overflow list while we check for smaller options - txSets[nextTx.txid].modified = true; overflow.push(nextTx); failures++; } } // this block is full - const outOfTransactions = !mempoolArray.length && !modified.length; const exceededPackageTries = failures > 1000 && blockWeight > (config.MEMPOOL.BLOCK_WEIGHT_UNITS - 4000); - const exceededSimpleTries = failures > 0 && simpleMode; - if (outOfTransactions || exceededPackageTries || exceededSimpleTries) { + if (exceededPackageTries && (!condenseRest || blocks.length < blockLimit - 1)) { // construct this block - blocks.push(this.dataToMempoolBlocks(transactions, blockSize, blockWeight, blocks.length)); + if (transactions.length) { + blocks.push(this.dataToMempoolBlocks(transactions.map(t => mempool[t.txid]), blockSize, blockWeight, blocks.length)); + } // reset for the next block transactions = []; blockSize = 0; blockWeight = 4000; // 'overflow' packages didn't fit in this block, but are valid candidates for the next - if (overflow.length) { - modified = modified.concat(overflow); - overflow = []; - anyModified = true; + for (const overflowTx of overflow.reverse()) { + if (overflowTx.modified) { + overflowTx.modifiedNode = modified.add(overflowTx); + } else { + top--; + mempoolArray[top] = overflowTx; + } } - } - - // re-sort modified list if necessary - if (anyModified) { - modified = modified.filter(tx => txSets[tx.txid]?.available).sort(byAncestor); + overflow = []; } } + if (condenseRest) { + // pack any leftover transactions into the last block + for (const tx of overflow) { + if (!tx || tx?.used) { + continue; + } + blockWeight += tx.weight; + blockSize += tx.size; + transactions.push(tx); + tx.used = true; + } + const blockTransactions = transactions.map(t => mempool[t.txid]) + restOfArray.forEach(tx => { + blockWeight += tx.weight; + blockSize += tx.size; + blockTransactions.push(tx); + }); + if (blockTransactions.length) { + blocks.push(this.dataToMempoolBlocks(blockTransactions, blockSize, blockWeight, blocks.length)); + } + transactions = []; + } else if (transactions.length) { + blocks.push(this.dataToMempoolBlocks(transactions.map(t => mempool[t.txid]), blockSize, blockWeight, blocks.length)); + } - const end = new Date().getTime(); + const end = Date.now(); const time = end - start; logger.debug('Mempool templates calculated in ' + time / 1000 + ' seconds'); return blocks; } - private sortByAncestorScore(a, b, txSets): number { - return txSets[b.txid]?.score - txSets[a.txid]?.score; + // traverse in-mempool ancestors + // recursion unavoidable, but should be limited to depth < 25 by mempool policy + public setRelatives( + tx: AuditTransaction, + mempool: { [txid: string]: AuditTransaction }, + ): void { + for (const parent of tx.vin) { + const parentTx = mempool[parent.txid]; + if (parentTx && !tx.ancestorMap!.has(parent.txid)) { + tx.ancestorMap.set(parent.txid, parentTx); + parentTx.children.add(tx); + // visit each node only once + if (!parentTx.relativesSet) { + this.setRelatives(parentTx, mempool); + } + parentTx.ancestorMap.forEach((ancestor) => { + tx.ancestorMap.set(ancestor.txid, ancestor); + }); + } + }; + tx.ancestorFee = tx.fee || 0; + tx.ancestorWeight = tx.weight || 0; + tx.ancestorMap.forEach((ancestor) => { + tx.ancestorFee += ancestor.fee; + tx.ancestorWeight += ancestor.weight; + }); + tx.score = tx.ancestorFee / (tx.ancestorWeight || 1); + tx.relativesSet = true; } - private setRelatives(tx: TransactionExtended, mempool: { [txid: string]: TransactionExtended }, txSets: { [txid: string]: TransactionSet }): { [txid: string]: Ancestor } { - let ancestors: { [txid: string]: Ancestor } = {}; - tx.vin.forEach((parent) => { - const parentTx = mempool[parent.txid]; - const parentTxSet = txSets[parent.txid]; - if (parentTx && parentTxSet) { - ancestors[parentTx.txid] = parentTx; - if (!parentTxSet.children) { - parentTxSet.children = [tx.txid]; - } else { - parentTxSet.children.push(tx.txid); - } - if (!parentTxSet.score) { - ancestors = { - ...ancestors, - ...this.setRelatives(parentTx, mempool, txSets), - }; - } + // iterate over remaining descendants, removing the root as a valid ancestor & updating the ancestor score + // avoids recursion to limit call stack depth + private updateDescendants( + rootTx: AuditTransaction, + mempool: { [txid: string]: AuditTransaction }, + modified: PairingHeap, + ): void { + const descendantSet: Set = new Set(); + // stack of nodes left to visit + const descendants: AuditTransaction[] = []; + let descendantTx; + let ancestorIndex; + let tmpScore; + rootTx.children.forEach(childTx => { + if (!descendantSet.has(childTx)) { + descendants.push(childTx); + descendantSet.add(childTx); } }); - tx.ancestors = Object.values(ancestors).map(ancestor => { - return { - txid: ancestor.txid, - fee: ancestor.fee, - weight: ancestor.weight - }; - }); - let totalFees = tx.fee; - let totalWeight = tx.weight; - tx.ancestors.forEach(ancestor => { - totalFees += ancestor.fee; - totalWeight += ancestor.weight; - }); - txSets[tx.txid].fee = totalFees; - txSets[tx.txid].weight = totalWeight; - txSets[tx.txid].score = this.calcAncestorScore(tx, totalFees, totalWeight); + while (descendants.length) { + descendantTx = descendants.pop(); + if (descendantTx && descendantTx.ancestorMap && descendantTx.ancestorMap.has(rootTx.txid)) { + // remove tx as ancestor + descendantTx.ancestorMap.delete(rootTx.txid); + descendantTx.ancestorFee -= rootTx.fee; + descendantTx.ancestorWeight -= rootTx.weight; + tmpScore = descendantTx.score; + descendantTx.score = descendantTx.ancestorFee / descendantTx.ancestorWeight; - return ancestors; - } - - private calcAncestorScore(tx: TransactionExtended, ancestorFees: number, ancestorWeight: number): number { - return Math.min(tx.fee / tx.weight, ancestorFees / ancestorWeight); - } - - // walk over remaining descendants, removing the root as a valid ancestor & updating the ancestor score - // returns whether any descendants were modified - private updateDescendants( - root: TransactionExtended, - tx: TransactionExtended, - mempool: { [txid: string]: TransactionExtended }, - txSets: { [txid: string]: TransactionSet }, - modified: TransactionExtended[], - ): boolean { - let anyModified = false; - const txSet = txSets[tx.txid]; - if (txSet.children) { - txSet.children.forEach(childId => { - const child = mempool[childId]; - if (child && child.ancestors) { - const ancestorIndex = child.ancestors.findIndex(a => a.txid === root.txid); - if (ancestorIndex > -1) { - // remove tx as ancestor - child.ancestors.splice(ancestorIndex, 1); - const childTxSet = txSets[childId]; - childTxSet.fee -= root.fee; - childTxSet.weight -= root.weight; - childTxSet.score = this.calcAncestorScore(child, childTxSet.fee, childTxSet.weight); - anyModified = true; - - if (!childTxSet.modified) { - childTxSet.modified = true; - modified.push(child); - } + if (!descendantTx.modifiedNode) { + descendantTx.modified = true; + descendantTx.modifiedNode = modified.add(descendantTx); + } else { + // rebalance modified heap if score has changed + if (descendantTx.score < tmpScore) { + modified.decreasePriority(descendantTx.modifiedNode); + } else if (descendantTx.score > tmpScore) { + modified.increasePriority(descendantTx.modifiedNode); } } - // recursively update grandchildren - if (child) { - anyModified = this.updateDescendants(root, child, mempool, txSets, modified) || anyModified; - } - }); + + // add this node's children to the stack + descendantTx.children.forEach(childTx => { + // visit each node only once + if (!descendantSet.has(childTx)) { + descendants.push(childTx); + descendantSet.add(childTx); + } + }); + } } - return anyModified; } private dataToMempoolBlocks(transactions: TransactionExtended[], diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index c3d4dad9b..9daad3161 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -250,6 +250,8 @@ class WebsocketHandler { throw new Error('WebSocket.Server is not set'); } + logger.debug("mempool changed!"); + mempoolBlocks.updateMempoolBlocks(newMempool); const mBlocks = mempoolBlocks.getMempoolBlocks(); const mBlockDeltas = mempoolBlocks.getMempoolBlockDeltas(); @@ -417,7 +419,7 @@ class WebsocketHandler { const _memPool = memPool.getMempool(); const mempoolCopy = cloneMempool(_memPool); - const projectedBlocks = mempoolBlocks.makeBlockTemplates(mempoolCopy, 2, 2); + const projectedBlocks = mempoolBlocks.makeBlockTemplates(mempoolCopy, 2); if (projectedBlocks[0]) { const { censored, added, score } = Audit.auditBlock(block, txIds, transactions, projectedBlocks, mempoolCopy); diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index fec26c0f3..32d87f3dc 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -1,4 +1,5 @@ import { IEsploraApi } from './api/bitcoin/esplora-api.interface'; +import { HeapNode } from "./utils/pairing-heap"; export interface PoolTag { id: number; // mysql row id @@ -70,6 +71,24 @@ export interface TransactionExtended extends IEsploraApi.Transaction { deleteAfter?: number; } +export interface AuditTransaction { + txid: string; + fee: number; + size: number; + weight: number; + feePerVsize: number; + vin: IEsploraApi.Vin[]; + relativesSet: boolean; + ancestorMap: Map; + children: Set; + ancestorFee: number; + ancestorWeight: number; + score: number; + used: boolean; + modified: boolean; + modifiedNode: HeapNode; +} + export interface Ancestor { txid: string; weight: number; @@ -80,9 +99,10 @@ export interface TransactionSet { fee: number; weight: number; score: number; - children?: string[]; + children?: Set; available?: boolean; modified?: boolean; + modifiedNode?: HeapNode; } interface BestDescendant { diff --git a/backend/src/utils/pairing-heap.ts b/backend/src/utils/pairing-heap.ts new file mode 100644 index 000000000..876e056c4 --- /dev/null +++ b/backend/src/utils/pairing-heap.ts @@ -0,0 +1,174 @@ +export type HeapNode = { + element: T + child?: HeapNode + next?: HeapNode + prev?: HeapNode +} | null | undefined; + +// minimal pairing heap priority queue implementation +export class PairingHeap { + private root: HeapNode = null; + private comparator: (a: T, b: T) => boolean; + + // comparator function should return 'true' if a is higher priority than b + constructor(comparator: (a: T, b: T) => boolean) { + this.comparator = comparator; + } + + isEmpty(): boolean { + return !this.root; + } + + add(element: T): HeapNode { + const node: HeapNode = { + element + }; + + this.root = this.meld(this.root, node); + + return node; + } + + // returns the top priority element without modifying the queue + peek(): T | void { + return this.root?.element; + } + + // removes and returns the top priority element + pop(): T | void { + let element; + if (this.root) { + const node = this.root; + element = node.element; + this.root = this.mergePairs(node.child); + } + return element; + } + + deleteNode(node: HeapNode): void { + if (!node) { + return; + } + + if (node === this.root) { + this.root = this.mergePairs(node.child); + } + else { + if (node.prev) { + if (node.prev.child === node) { + node.prev.child = node.next; + } + else { + node.prev.next = node.next; + } + } + if (node.next) { + node.next.prev = node.prev; + } + this.root = this.meld(this.root, this.mergePairs(node.child)); + } + + node.child = null; + node.prev = null; + node.next = null; + } + + // fix the heap after increasing the priority of a given node + increasePriority(node: HeapNode): void { + // already the top priority element + if (!node || node === this.root) { + return; + } + // extract from siblings + if (node.prev) { + if (node.prev?.child === node) { + if (this.comparator(node.prev.element, node.element)) { + // already in a valid position + return; + } + node.prev.child = node.next; + } + else { + node.prev.next = node.next; + } + } + if (node.next) { + node.next.prev = node.prev; + } + + this.root = this.meld(this.root, node); + } + + decreasePriority(node: HeapNode): void { + this.deleteNode(node); + this.root = this.meld(this.root, node); + } + + meld(a: HeapNode, b: HeapNode): HeapNode { + if (!a) { + return b; + } + if (!b || a === b) { + return a; + } + + let parent: HeapNode = b; + let child: HeapNode = a; + if (this.comparator(a.element, b.element)) { + parent = a; + child = b; + } + + child.next = parent.child; + if (parent.child) { + parent.child.prev = child; + } + child.prev = parent; + parent.child = child; + + parent.next = null; + parent.prev = null; + + return parent; + } + + mergePairs(node: HeapNode): HeapNode { + if (!node) { + return null; + } + + let current: HeapNode = node; + let next: HeapNode; + let nextCurrent: HeapNode; + let pairs: HeapNode; + let melded: HeapNode; + while (current) { + next = current.next; + if (next) { + nextCurrent = next.next; + melded = this.meld(current, next); + if (melded) { + melded.prev = pairs; + } + pairs = melded; + } + else { + nextCurrent = null; + current.prev = pairs; + pairs = current; + break; + } + current = nextCurrent; + } + + melded = null; + let prev: HeapNode; + while (pairs) { + prev = pairs.prev; + melded = this.meld(melded, pairs); + pairs = prev; + } + + return melded; + } +} \ No newline at end of file From 8102657b2af2d1147d40ea251cd671e0a8d8d550 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 28 Oct 2022 15:16:03 -0600 Subject: [PATCH 64/76] disable block audits unless indexing is enabled --- backend/src/api/audit.ts | 9 ++-- backend/src/api/websocket-handler.ts | 75 +++++++++++++--------------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/backend/src/api/audit.ts b/backend/src/api/audit.ts index 2d9fbc430..77a6e7459 100644 --- a/backend/src/api/audit.ts +++ b/backend/src/api/audit.ts @@ -4,9 +4,12 @@ import { BlockExtended, TransactionExtended, MempoolBlockWithTransactions } from const PROPAGATION_MARGIN = 180; // in seconds, time since a transaction is first seen after which it is assumed to have propagated to all miners class Audit { - auditBlock(block: BlockExtended, txIds: string[], transactions: TransactionExtended[], - projectedBlocks: MempoolBlockWithTransactions[], mempool: { [txId: string]: TransactionExtended }, - ): { censored: string[], added: string[], score: number } { + auditBlock(transactions: TransactionExtended[], projectedBlocks: MempoolBlockWithTransactions[], mempool: { [txId: string]: TransactionExtended }) + : { censored: string[], added: string[], score: number } { + if (!projectedBlocks?.[0]?.transactionIds || !mempool) { + return { censored: [], added: [], score: 0 }; + } + const matches: string[] = []; // present in both mined block and template const added: string[] = []; // present in mined block, not in template const fresh: string[] = []; // missing, but firstSeen within PROPAGATION_MARGIN diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 9daad3161..60560b93c 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -250,8 +250,6 @@ class WebsocketHandler { throw new Error('WebSocket.Server is not set'); } - logger.debug("mempool changed!"); - mempoolBlocks.updateMempoolBlocks(newMempool); const mBlocks = mempoolBlocks.getMempoolBlocks(); const mBlockDeltas = mempoolBlocks.getMempoolBlockDeltas(); @@ -417,55 +415,54 @@ class WebsocketHandler { let mBlockDeltas: undefined | MempoolBlockDelta[]; let matchRate = 0; const _memPool = memPool.getMempool(); - const mempoolCopy = cloneMempool(_memPool); - const projectedBlocks = mempoolBlocks.makeBlockTemplates(mempoolCopy, 2); + if (Common.indexingEnabled()) { + const mempoolCopy = cloneMempool(_memPool); + const projectedBlocks = mempoolBlocks.makeBlockTemplates(mempoolCopy, 2); - if (projectedBlocks[0]) { - const { censored, added, score } = Audit.auditBlock(block, txIds, transactions, projectedBlocks, mempoolCopy); + const { censored, added, score } = Audit.auditBlock(transactions, projectedBlocks, mempoolCopy); matchRate = Math.round(score * 100 * 100) / 100; - // Update mempool to remove transactions included in the new block - for (const txId of txIds) { - delete _memPool[txId]; - } + const stripped = projectedBlocks[0]?.transactions ? projectedBlocks[0].transactions.map((tx) => { + return { + txid: tx.txid, + vsize: tx.vsize, + fee: tx.fee ? Math.round(tx.fee) : 0, + value: tx.value, + }; + }) : []; - mempoolBlocks.updateMempoolBlocks(_memPool); - mBlocks = mempoolBlocks.getMempoolBlocks(); - mBlockDeltas = mempoolBlocks.getMempoolBlockDeltas(); + BlocksSummariesRepository.$saveSummary({ + height: block.height, + template: { + id: block.id, + transactions: stripped + } + }); - if (Common.indexingEnabled()) { - const stripped = projectedBlocks[0].transactions.map((tx) => { - return { - txid: tx.txid, - vsize: tx.vsize, - fee: tx.fee ? Math.round(tx.fee) : 0, - value: tx.value, - }; - }); - BlocksSummariesRepository.$saveSummary({ - height: block.height, - template: { - id: block.id, - transactions: stripped - } - }); + BlocksAuditsRepository.$saveAudit({ + time: block.timestamp, + height: block.height, + hash: block.id, + addedTxs: added, + missingTxs: censored, + matchRate: matchRate, + }); - BlocksAuditsRepository.$saveAudit({ - time: block.timestamp, - height: block.height, - hash: block.id, - addedTxs: added, - missingTxs: censored, - matchRate: matchRate, - }); + if (block.extras) { + block.extras.matchRate = matchRate; } } - if (block.extras) { - block.extras.matchRate = matchRate; + // Update mempool to remove transactions included in the new block + for (const txId of txIds) { + delete _memPool[txId]; } + mempoolBlocks.updateMempoolBlocks(_memPool); + mBlocks = mempoolBlocks.getMempoolBlocks(); + mBlockDeltas = mempoolBlocks.getMempoolBlockDeltas(); + const da = difficultyAdjustment.getDifficultyAdjustment(); const fees = feeApi.getRecommendedFee(); From 8c3bc3ad0b1214585697b7ddca1611de4ecc634a Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 12 Jul 2022 16:13:41 +0000 Subject: [PATCH 65/76] Fix block audit mobile toggle buttons --- .../block-audit/block-audit.component.ts | 69 +++++++++++++------ 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/frontend/src/app/components/block-audit/block-audit.component.ts b/frontend/src/app/components/block-audit/block-audit.component.ts index ff6c0ea7f..8a43a32c1 100644 --- a/frontend/src/app/components/block-audit/block-audit.component.ts +++ b/frontend/src/app/components/block-audit/block-audit.component.ts @@ -1,12 +1,12 @@ -import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { Component, OnDestroy, OnInit, AfterViewInit, ViewChildren, QueryList } from '@angular/core'; import { ActivatedRoute, ParamMap, Router } from '@angular/router'; -import { Observable } from 'rxjs'; -import { map, share, switchMap, tap } from 'rxjs/operators'; -import { BlockAudit, TransactionStripped } from '../../interfaces/node-api.interface'; -import { ApiService } from '../../services/api.service'; -import { StateService } from '../../services/state.service'; -import { detectWebGL } from '../../shared/graphs.utils'; -import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe'; +import { Observable, Subscription, combineLatest } from 'rxjs'; +import { map, share, switchMap, tap, startWith } from 'rxjs/operators'; +import { BlockAudit, TransactionStripped } from 'src/app/interfaces/node-api.interface'; +import { ApiService } from 'src/app/services/api.service'; +import { StateService } from 'src/app/services/state.service'; +import { detectWebGL } from 'src/app/shared/graphs.utils'; +import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe'; import { BlockOverviewGraphComponent } from '../block-overview-graph/block-overview-graph.component'; @Component({ @@ -22,7 +22,7 @@ import { BlockOverviewGraphComponent } from '../block-overview-graph/block-overv } `], }) -export class BlockAuditComponent implements OnInit, OnDestroy { +export class BlockAuditComponent implements OnInit, AfterViewInit, OnDestroy { blockAudit: BlockAudit = undefined; transactions: string[]; auditObservable$: Observable; @@ -36,8 +36,10 @@ export class BlockAuditComponent implements OnInit, OnDestroy { webGlEnabled = true; isMobile = window.innerWidth <= 767.98; - @ViewChild('blockGraphTemplate') blockGraphTemplate: BlockOverviewGraphComponent; - @ViewChild('blockGraphMined') blockGraphMined: BlockOverviewGraphComponent; + childChangeSubscription: Subscription; + + @ViewChildren('blockGraphTemplate') blockGraphTemplate: QueryList; + @ViewChildren('blockGraphMined') blockGraphMined: QueryList; constructor( private route: ActivatedRoute, @@ -49,6 +51,7 @@ export class BlockAuditComponent implements OnInit, OnDestroy { } ngOnDestroy(): void { + this.childChangeSubscription.unsubscribe(); } ngOnInit(): void { @@ -83,15 +86,8 @@ export class BlockAuditComponent implements OnInit, OnDestroy { return blockAudit; }), tap((blockAudit) => { + this.blockAudit = blockAudit; this.changeMode(this.mode); - if (this.blockGraphTemplate) { - this.blockGraphTemplate.destroy(); - this.blockGraphTemplate.setup(blockAudit.template); - } - if (this.blockGraphMined) { - this.blockGraphMined.destroy(); - this.blockGraphMined.setup(blockAudit.transactions); - } this.isLoading = false; }), ); @@ -100,14 +96,47 @@ export class BlockAuditComponent implements OnInit, OnDestroy { ); } + ngAfterViewInit() { + this.childChangeSubscription = combineLatest([this.blockGraphTemplate.changes.pipe(startWith(null)), this.blockGraphMined.changes.pipe(startWith(null))]).subscribe(() => { + console.log('changed!'); + this.setupBlockGraphs(); + }) + } + + setupBlockGraphs() { + console.log('setting up block graphs') + if (this.blockAudit) { + this.blockGraphTemplate.forEach(graph => { + graph.destroy(); + if (this.isMobile && this.mode === 'added') { + graph.setup(this.blockAudit.transactions); + } else { + graph.setup(this.blockAudit.template); + } + }) + this.blockGraphMined.forEach(graph => { + graph.destroy(); + graph.setup(this.blockAudit.transactions); + }) + } + } + onResize(event: any) { - this.isMobile = event.target.innerWidth <= 767.98; + const isMobile = event.target.innerWidth <= 767.98; + const changed = isMobile !== this.isMobile; + this.isMobile = isMobile; this.paginationMaxSize = event.target.innerWidth < 670 ? 3 : 5; + + if (changed) { + this.changeMode(this.mode); + } } changeMode(mode: 'missing' | 'added') { this.router.navigate([], { fragment: mode }); this.mode = mode; + + this.setupBlockGraphs(); } onTxClick(event: TransactionStripped): void { From a9a4b217c906f0824da6c1098043a76994f10451 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 19 Oct 2022 00:23:45 +0000 Subject: [PATCH 66/76] differentiate censored/missing txs in block audit --- .../block-audit/block-audit.component.ts | 48 ++++++++++++++----- .../block-overview-graph/tx-view.ts | 25 ++++++---- .../block-overview-tooltip.component.html | 10 ++++ .../src/app/interfaces/node-api.interface.ts | 2 +- .../src/app/interfaces/websocket.interface.ts | 2 +- 5 files changed, 63 insertions(+), 24 deletions(-) diff --git a/frontend/src/app/components/block-audit/block-audit.component.ts b/frontend/src/app/components/block-audit/block-audit.component.ts index 8a43a32c1..ed884e728 100644 --- a/frontend/src/app/components/block-audit/block-audit.component.ts +++ b/frontend/src/app/components/block-audit/block-audit.component.ts @@ -65,24 +65,48 @@ export class BlockAuditComponent implements OnInit, AfterViewInit, OnDestroy { .pipe( map((response) => { const blockAudit = response.body; - for (let i = 0; i < blockAudit.template.length; ++i) { - if (blockAudit.missingTxs.includes(blockAudit.template[i].txid)) { - blockAudit.template[i].status = 'missing'; - } else if (blockAudit.addedTxs.includes(blockAudit.template[i].txid)) { - blockAudit.template[i].status = 'added'; + const inTemplate = {}; + const inBlock = {}; + const isAdded = {}; + const isCensored = {}; + const isMissing = {}; + const isSelected = {}; + for (const tx of blockAudit.template) { + inTemplate[tx.txid] = true; + } + for (const tx of blockAudit.transactions) { + inBlock[tx.txid] = true; + } + for (const txid of blockAudit.addedTxs) { + isAdded[txid] = true; + } + for (const txid of blockAudit.missingTxs) { + isCensored[txid] = true; + } + // set transaction statuses + for (const tx of blockAudit.template) { + if (isCensored[tx.txid]) { + tx.status = 'censored'; + } else if (inBlock[tx.txid]) { + tx.status = 'found'; } else { - blockAudit.template[i].status = 'found'; + tx.status = 'missing'; + isMissing[tx.txid] = true; } } - for (let i = 0; i < blockAudit.transactions.length; ++i) { - if (blockAudit.missingTxs.includes(blockAudit.transactions[i].txid)) { - blockAudit.transactions[i].status = 'missing'; - } else if (blockAudit.addedTxs.includes(blockAudit.transactions[i].txid)) { - blockAudit.transactions[i].status = 'added'; + for (const [index, tx] of blockAudit.transactions.entries()) { + if (isAdded[tx.txid]) { + tx.status = 'added'; + } else if (index === 0 || inTemplate[tx.txid]) { + tx.status = 'found'; } else { - blockAudit.transactions[i].status = 'found'; + tx.status = 'selected'; + isSelected[tx.txid] = true; } } + for (const tx of blockAudit.transactions) { + inBlock[tx.txid] = true; + } return blockAudit; }), tap((blockAudit) => { diff --git a/frontend/src/app/components/block-overview-graph/tx-view.ts b/frontend/src/app/components/block-overview-graph/tx-view.ts index 5f2ebf898..1ddc55630 100644 --- a/frontend/src/app/components/block-overview-graph/tx-view.ts +++ b/frontend/src/app/components/block-overview-graph/tx-view.ts @@ -25,7 +25,7 @@ export default class TxView implements TransactionStripped { vsize: number; value: number; feerate: number; - status?: 'found' | 'missing' | 'added'; + status?: 'found' | 'missing' | 'added' | 'censored' | 'selected'; initialised: boolean; vertexArray: FastVertexArray; @@ -142,16 +142,21 @@ export default class TxView implements TransactionStripped { } getColor(): Color { - // Block audit - if (this.status === 'missing') { - return hexToColor('039BE5'); - } else if (this.status === 'added') { - return hexToColor('D81B60'); - } - - // Block component const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, this.feerate) < feeLvl) - 1; - return hexToColor(mempoolFeeColors[feeLevelIndex] || mempoolFeeColors[mempoolFeeColors.length - 1]); + const feeLevelColor = hexToColor(mempoolFeeColors[feeLevelIndex] || mempoolFeeColors[mempoolFeeColors.length - 1]); + // Block audit + switch(this.status) { + case 'censored': + return hexToColor('D81BC2'); + case 'missing': + return hexToColor('8C1BD8'); + case 'added': + return hexToColor('03E1E5'); + case 'selected': + return hexToColor('039BE5'); + default: + return feeLevelColor; + } } } diff --git a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html index 03d7fc1e9..83fc627be 100644 --- a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html +++ b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html @@ -32,6 +32,16 @@
    + + + + + + + + + +
    AliasCapacityChannelsCapacityChannels
    AliasFirst seenFirst seen Last update Capacity Channels
    AliasFirst seenFirst seen Last update Capacity Channels
    AliasFirst seenFirst seen Liquidity Channels Last updateAlias Liquidity ChannelsFirst seenFirst seen Last update Location
    Alias Channels LiquidityFirst seenFirst seen Last update Location TXID Amount USDFeeFee
    Alias   StatusFee RateFee rate Closing date Capacity Channel IDVirtual size
    Audit statusmatchcensoredmissingprioritizedunexpected
    diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index d9670936d..8e04c8635 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -141,7 +141,7 @@ export interface TransactionStripped { fee: number; vsize: number; value: number; - status?: 'found' | 'missing' | 'added'; + status?: 'found' | 'missing' | 'added' | 'censored' | 'selected'; } export interface RewardStats { diff --git a/frontend/src/app/interfaces/websocket.interface.ts b/frontend/src/app/interfaces/websocket.interface.ts index e4ceefb44..67cc0ffc7 100644 --- a/frontend/src/app/interfaces/websocket.interface.ts +++ b/frontend/src/app/interfaces/websocket.interface.ts @@ -70,7 +70,7 @@ export interface TransactionStripped { fee: number; vsize: number; value: number; - status?: 'found' | 'missing' | 'added'; + status?: 'found' | 'missing' | 'added' | 'censored' | 'selected'; } export interface IBackendInfo { From ba275c87d3898fb834175a503eec765634ac8fd1 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Thu, 27 Oct 2022 18:39:26 -0600 Subject: [PATCH 67/76] Clean up block audit page & tweak color scheme --- backend/src/api/mining/mining-routes.ts | 6 + backend/src/api/websocket-handler.ts | 2 +- .../repositories/BlocksAuditsRepository.ts | 10 +- .../block-audit/block-audit.component.html | 150 ++++++++++++++---- .../block-audit/block-audit.component.scss | 4 + .../block-audit/block-audit.component.ts | 79 +++++---- .../block-overview-graph/tx-view.ts | 40 ++++- .../block-overview-tooltip.component.html | 6 +- 8 files changed, 222 insertions(+), 75 deletions(-) diff --git a/backend/src/api/mining/mining-routes.ts b/backend/src/api/mining/mining-routes.ts index f52d42d1f..47704f993 100644 --- a/backend/src/api/mining/mining-routes.ts +++ b/backend/src/api/mining/mining-routes.ts @@ -238,6 +238,12 @@ class MiningRoutes { public async $getBlockAudit(req: Request, res: Response) { try { const audit = await BlocksAuditsRepository.$getBlockAudit(req.params.hash); + + if (!audit) { + res.status(404).send(`This block has not been audited.`); + return; + } + res.header('Pragma', 'public'); res.header('Cache-control', 'public'); res.setHeader('Expires', new Date(Date.now() + 1000 * 3600 * 24).toUTCString()); diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 60560b93c..4bd7cfc8d 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -413,7 +413,7 @@ class WebsocketHandler { let mBlocks: undefined | MempoolBlock[]; let mBlockDeltas: undefined | MempoolBlockDelta[]; - let matchRate = 0; + let matchRate; const _memPool = memPool.getMempool(); if (Common.indexingEnabled()) { diff --git a/backend/src/repositories/BlocksAuditsRepository.ts b/backend/src/repositories/BlocksAuditsRepository.ts index be85b22b9..4ddd7d761 100644 --- a/backend/src/repositories/BlocksAuditsRepository.ts +++ b/backend/src/repositories/BlocksAuditsRepository.ts @@ -58,10 +58,12 @@ class BlocksAuditRepositories { WHERE blocks_audits.hash = "${hash}" `); - rows[0].missingTxs = JSON.parse(rows[0].missingTxs); - rows[0].addedTxs = JSON.parse(rows[0].addedTxs); - rows[0].transactions = JSON.parse(rows[0].transactions); - rows[0].template = JSON.parse(rows[0].template); + if (rows.length) { + rows[0].missingTxs = JSON.parse(rows[0].missingTxs); + rows[0].addedTxs = JSON.parse(rows[0].addedTxs); + rows[0].transactions = JSON.parse(rows[0].transactions); + rows[0].template = JSON.parse(rows[0].template); + } return rows[0]; } catch (e: any) { diff --git a/frontend/src/app/components/block-audit/block-audit.component.html b/frontend/src/app/components/block-audit/block-audit.component.html index 0ee6bef44..543dbb705 100644 --- a/frontend/src/app/components/block-audit/block-audit.component.html +++ b/frontend/src/app/components/block-audit/block-audit.component.html @@ -1,21 +1,22 @@
    -
    -
    -

    - - Block -   - {{ blockAudit.height }} -   - Template vs Mined - -

    +
    +

    + + Block Audit +   + {{ blockAudit.height }} +   + +

    -
    +
    - -
    + +
    + +
    +
    @@ -26,8 +27,8 @@ Hash - {{ blockAudit.id | shortenString : 13 }} - + {{ blockHash | shortenString : 13 }} + @@ -40,6 +41,10 @@
    + + Transactions + {{ blockAudit.tx_count }} + Size @@ -57,21 +62,25 @@ - - - - - + - + + + + + + + + +
    Transactions{{ blockAudit.tx_count }}
    Match rateBlock health {{ blockAudit.matchRate }}%
    Missing txsRemoved txs {{ blockAudit.missingTxs.length }}
    Omitted txs{{ numMissing }}
    Added txs {{ blockAudit.addedTxs.length }}
    Included txs{{ numUnexpected }}
    @@ -79,33 +88,110 @@
    - + +
    +

    + + Block Audit +   + {{ blockAudit.height }} +   + +

    + +
    + + +
    + + +
    +
    + +
    + + + + + + + + +
    +
    + + +
    + + + + + + + + +
    +
    +
    +
    + + + +
    + + +
    +
    + audit unavailable +

    + {{ error.error }} +
    +
    +
    + +
    +
    + Error loading data. +

    + {{ error }} +
    +
    +
    +
    +
    + -
    +
    - Projected Block +
    - Actual Block +
    - -
    \ No newline at end of file diff --git a/frontend/src/app/components/block-audit/block-audit.component.scss b/frontend/src/app/components/block-audit/block-audit.component.scss index 7ec503891..1e35b7c63 100644 --- a/frontend/src/app/components/block-audit/block-audit.component.scss +++ b/frontend/src/app/components/block-audit/block-audit.component.scss @@ -37,4 +37,8 @@ @media (min-width: 768px) { max-width: 150px; } +} + +.block-subtitle { + text-align: center; } \ No newline at end of file diff --git a/frontend/src/app/components/block-audit/block-audit.component.ts b/frontend/src/app/components/block-audit/block-audit.component.ts index ed884e728..a7eb879b4 100644 --- a/frontend/src/app/components/block-audit/block-audit.component.ts +++ b/frontend/src/app/components/block-audit/block-audit.component.ts @@ -1,7 +1,7 @@ import { Component, OnDestroy, OnInit, AfterViewInit, ViewChildren, QueryList } from '@angular/core'; import { ActivatedRoute, ParamMap, Router } from '@angular/router'; import { Observable, Subscription, combineLatest } from 'rxjs'; -import { map, share, switchMap, tap, startWith } from 'rxjs/operators'; +import { map, switchMap, startWith, catchError } from 'rxjs/operators'; import { BlockAudit, TransactionStripped } from 'src/app/interfaces/node-api.interface'; import { ApiService } from 'src/app/services/api.service'; import { StateService } from 'src/app/services/state.service'; @@ -25,21 +25,27 @@ import { BlockOverviewGraphComponent } from '../block-overview-graph/block-overv export class BlockAuditComponent implements OnInit, AfterViewInit, OnDestroy { blockAudit: BlockAudit = undefined; transactions: string[]; - auditObservable$: Observable; + auditSubscription: Subscription; + urlFragmentSubscription: Subscription; paginationMaxSize: number; page = 1; itemsPerPage: number; - mode: 'missing' | 'added' = 'missing'; + mode: 'projected' | 'actual' = 'projected'; + error: any; isLoading = true; webGlEnabled = true; isMobile = window.innerWidth <= 767.98; childChangeSubscription: Subscription; - @ViewChildren('blockGraphTemplate') blockGraphTemplate: QueryList; - @ViewChildren('blockGraphMined') blockGraphMined: QueryList; + blockHash: string; + numMissing: number = 0; + numUnexpected: number = 0; + + @ViewChildren('blockGraphProjected') blockGraphProjected: QueryList; + @ViewChildren('blockGraphActual') blockGraphActual: QueryList; constructor( private route: ActivatedRoute, @@ -50,18 +56,31 @@ export class BlockAuditComponent implements OnInit, AfterViewInit, OnDestroy { this.webGlEnabled = detectWebGL(); } - ngOnDestroy(): void { + ngOnDestroy() { this.childChangeSubscription.unsubscribe(); + this.urlFragmentSubscription.unsubscribe(); } ngOnInit(): void { this.paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5; this.itemsPerPage = this.stateService.env.ITEMS_PER_PAGE; - this.auditObservable$ = this.route.paramMap.pipe( + this.urlFragmentSubscription = this.route.fragment.subscribe((fragment) => { + if (fragment === 'actual') { + this.mode = 'actual'; + } else { + this.mode = 'projected' + } + this.setupBlockGraphs(); + }); + + this.auditSubscription = this.route.paramMap.pipe( switchMap((params: ParamMap) => { - const blockHash: string = params.get('id') || ''; - return this.apiService.getBlockAudit$(blockHash) + this.blockHash = params.get('id') || null; + if (!this.blockHash) { + return null; + } + return this.apiService.getBlockAudit$(this.blockHash) .pipe( map((response) => { const blockAudit = response.body; @@ -71,6 +90,8 @@ export class BlockAuditComponent implements OnInit, AfterViewInit, OnDestroy { const isCensored = {}; const isMissing = {}; const isSelected = {}; + this.numMissing = 0; + this.numUnexpected = 0; for (const tx of blockAudit.template) { inTemplate[tx.txid] = true; } @@ -92,6 +113,7 @@ export class BlockAuditComponent implements OnInit, AfterViewInit, OnDestroy { } else { tx.status = 'missing'; isMissing[tx.txid] = true; + this.numMissing++; } } for (const [index, tx] of blockAudit.transactions.entries()) { @@ -102,43 +124,46 @@ export class BlockAuditComponent implements OnInit, AfterViewInit, OnDestroy { } else { tx.status = 'selected'; isSelected[tx.txid] = true; + this.numUnexpected++; } } for (const tx of blockAudit.transactions) { inBlock[tx.txid] = true; } return blockAudit; - }), - tap((blockAudit) => { - this.blockAudit = blockAudit; - this.changeMode(this.mode); - this.isLoading = false; - }), + }) ); }), - share() - ); + catchError((err) => { + console.log(err); + this.error = err; + this.isLoading = false; + return null; + }), + ).subscribe((blockAudit) => { + this.blockAudit = blockAudit; + this.setupBlockGraphs(); + this.isLoading = false; + }); } ngAfterViewInit() { - this.childChangeSubscription = combineLatest([this.blockGraphTemplate.changes.pipe(startWith(null)), this.blockGraphMined.changes.pipe(startWith(null))]).subscribe(() => { - console.log('changed!'); + this.childChangeSubscription = combineLatest([this.blockGraphProjected.changes.pipe(startWith(null)), this.blockGraphActual.changes.pipe(startWith(null))]).subscribe(() => { this.setupBlockGraphs(); }) } setupBlockGraphs() { - console.log('setting up block graphs') if (this.blockAudit) { - this.blockGraphTemplate.forEach(graph => { + this.blockGraphProjected.forEach(graph => { graph.destroy(); - if (this.isMobile && this.mode === 'added') { + if (this.isMobile && this.mode === 'actual') { graph.setup(this.blockAudit.transactions); } else { graph.setup(this.blockAudit.template); } }) - this.blockGraphMined.forEach(graph => { + this.blockGraphActual.forEach(graph => { graph.destroy(); graph.setup(this.blockAudit.transactions); }) @@ -156,18 +181,12 @@ export class BlockAuditComponent implements OnInit, AfterViewInit, OnDestroy { } } - changeMode(mode: 'missing' | 'added') { + changeMode(mode: 'projected' | 'actual') { this.router.navigate([], { fragment: mode }); - this.mode = mode; - - this.setupBlockGraphs(); } onTxClick(event: TransactionStripped): void { const url = new RelativeUrlPipe(this.stateService).transform(`/tx/${event.txid}`); this.router.navigate([url]); } - - pageChange(page: number, target: HTMLElement) { - } } diff --git a/frontend/src/app/components/block-overview-graph/tx-view.ts b/frontend/src/app/components/block-overview-graph/tx-view.ts index 1ddc55630..ac2a4655a 100644 --- a/frontend/src/app/components/block-overview-graph/tx-view.ts +++ b/frontend/src/app/components/block-overview-graph/tx-view.ts @@ -7,6 +7,15 @@ import { feeLevels, mempoolFeeColors } from '../../app.constants'; const hoverTransitionTime = 300; const defaultHoverColor = hexToColor('1bd8f4'); +const feeColors = mempoolFeeColors.map(hexToColor); +const auditFeeColors = feeColors.map((color) => desaturate(color, 0.3)); +const auditColors = { + censored: hexToColor('f344df'), + missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7), + added: hexToColor('03E1E5'), + selected: darken(desaturate(hexToColor('039BE5'), 0.3), 0.7), +} + // convert from this class's update format to TxSprite's update format function toSpriteUpdate(params: ViewUpdateParams): SpriteUpdateParams { return { @@ -143,17 +152,19 @@ export default class TxView implements TransactionStripped { getColor(): Color { const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, this.feerate) < feeLvl) - 1; - const feeLevelColor = hexToColor(mempoolFeeColors[feeLevelIndex] || mempoolFeeColors[mempoolFeeColors.length - 1]); + const feeLevelColor = feeColors[feeLevelIndex] || feeColors[mempoolFeeColors.length - 1]; // Block audit switch(this.status) { case 'censored': - return hexToColor('D81BC2'); + return auditColors.censored; case 'missing': - return hexToColor('8C1BD8'); + return auditColors.missing; case 'added': - return hexToColor('03E1E5'); + return auditColors.added; case 'selected': - return hexToColor('039BE5'); + return auditColors.selected; + case 'found': + return auditFeeColors[feeLevelIndex] || auditFeeColors[mempoolFeeColors.length - 1]; default: return feeLevelColor; } @@ -168,3 +179,22 @@ function hexToColor(hex: string): Color { a: 1 }; } + +function desaturate(color: Color, amount: number): Color { + const gray = (color.r + color.g + color.b) / 6; + return { + r: color.r + ((gray - color.r) * amount), + g: color.g + ((gray - color.g) * amount), + b: color.b + ((gray - color.b) * amount), + a: color.a, + }; +} + +function darken(color: Color, amount: number): Color { + return { + r: color.r * amount, + g: color.g * amount, + b: color.b * amount, + a: color.a, + } +} diff --git a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html index 83fc627be..b19b67b06 100644 --- a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html +++ b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html @@ -36,10 +36,10 @@ Audit status match - censored + removed missing - prioritized - unexpected + added + included From 8e4f996200d3b502244624ff10d853ba447f5cb8 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 28 Oct 2022 10:31:55 -0600 Subject: [PATCH 68/76] Add match rate to block page --- backend/src/api/blocks.ts | 17 +++++++++++------ .../src/repositories/BlocksAuditsRepository.ts | 14 ++++++++++++++ .../app/components/block/block.component.html | 7 +++++++ .../src/app/components/block/block.component.ts | 4 ++++ 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index d702b4927..f536ce3d5 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -20,6 +20,7 @@ import indexer from '../indexer'; import fiatConversion from './fiat-conversion'; import poolsParser from './pools-parser'; import BlocksSummariesRepository from '../repositories/BlocksSummariesRepository'; +import BlocksAuditsRepository from '../repositories/BlocksAuditsRepository'; import mining from './mining/mining'; import DifficultyAdjustmentsRepository from '../repositories/DifficultyAdjustmentsRepository'; import PricesRepository from '../repositories/PricesRepository'; @@ -186,14 +187,18 @@ class Blocks { if (!pool) { // We should never have this situation in practise logger.warn(`Cannot assign pool to block ${blockExtended.height} and 'unknown' pool does not exist. ` + `Check your "pools" table entries`); - return blockExtended; + } else { + blockExtended.extras.pool = { + id: pool.id, + name: pool.name, + slug: pool.slug, + }; } - blockExtended.extras.pool = { - id: pool.id, - name: pool.name, - slug: pool.slug, - }; + const auditSummary = await BlocksAuditsRepository.$getShortBlockAudit(block.id); + if (auditSummary) { + blockExtended.extras.matchRate = auditSummary.matchRate; + } } return blockExtended; diff --git a/backend/src/repositories/BlocksAuditsRepository.ts b/backend/src/repositories/BlocksAuditsRepository.ts index 4ddd7d761..188cf4c38 100644 --- a/backend/src/repositories/BlocksAuditsRepository.ts +++ b/backend/src/repositories/BlocksAuditsRepository.ts @@ -71,6 +71,20 @@ class BlocksAuditRepositories { throw e; } } + + public async $getShortBlockAudit(hash: string): Promise { + try { + const [rows]: any[] = await DB.query( + `SELECT hash as id, match_rate as matchRate + FROM blocks_audits + WHERE blocks_audits.hash = "${hash}" + `); + return rows[0]; + } catch (e: any) { + logger.err(`Cannot fetch block audit from db. Reason: ` + (e instanceof Error ? e.message : e)); + throw e; + } + } } export default new BlocksAuditRepositories(); diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html index a44abe3a0..819b05c81 100644 --- a/frontend/src/app/components/block/block.component.html +++ b/frontend/src/app/components/block/block.component.html @@ -110,6 +110,13 @@ + + Block health + + {{ block.extras.matchRate }}% + Unknown + + diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts index 2e6a73c62..8f977b81d 100644 --- a/frontend/src/app/components/block/block.component.ts +++ b/frontend/src/app/components/block/block.component.ts @@ -47,6 +47,7 @@ export class BlockComponent implements OnInit, OnDestroy { transactionsError: any = null; overviewError: any = null; webGlEnabled = true; + indexingAvailable = false; transactionSubscription: Subscription; overviewSubscription: Subscription; @@ -86,6 +87,9 @@ export class BlockComponent implements OnInit, OnDestroy { this.timeLtr = !!ltr; }); + this.indexingAvailable = (this.stateService.env.BASE_MODULE === 'mempool' && + this.stateService.env.MINING_DASHBOARD === true); + this.txsLoadingStatus$ = this.route.paramMap .pipe( switchMap(() => this.stateService.loadingIndicators$), From d97347d6bf33598c898e8cfad137f909d6e8ea99 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 28 Oct 2022 10:33:15 -0600 Subject: [PATCH 69/76] Add match rate to blocks list page --- .../blocks-list/blocks-list.component.html | 25 ++++++++- .../blocks-list/blocks-list.component.scss | 56 ++++++++++++++++--- frontend/src/styles.scss | 9 +++ 3 files changed, 81 insertions(+), 9 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 660481ecd..68acf71ea 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.html +++ b/frontend/src/app/components/blocks-list/blocks-list.component.html @@ -14,6 +14,8 @@ i18n-ngbTooltip="mining.pool-name" ngbTooltip="Pool" placement="bottom" #miningpool [disableTooltip]="!isEllipsisActive(miningpool)">Pool Timestamp Mined + Health Reward Fees @@ -37,12 +39,30 @@ {{ block.extras.coinbaseRaw | hex2ascii }}
    - + ‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }} + + +
    +
    +
    + {{ block.extras.matchRate }}% +
    +
    +
    +
    +
    +
    + ~ +
    +
    + @@ -77,6 +97,9 @@ + + + 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 5dc265017..6617cec58 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.scss +++ b/frontend/src/app/components/blocks-list/blocks-list.component.scss @@ -63,7 +63,7 @@ tr, td, th { } .height { - width: 10%; + width: 8%; } .height.widget { width: 15%; @@ -77,12 +77,18 @@ tr, td, th { .timestamp { width: 18%; - @media (max-width: 900px) { + @media (max-width: 1100px) { display: none; } } .timestamp.legacy { width: 20%; + @media (max-width: 1100px) { + display: table-cell; + } + @media (max-width: 850px) { + display: none; + } } .mined { @@ -93,6 +99,10 @@ tr, td, th { } .mined.legacy { width: 15%; + @media (max-width: 1000px) { + padding-right: 20px; + width: 20%; + } @media (max-width: 576px) { display: table-cell; } @@ -100,6 +110,7 @@ tr, td, th { .txs { padding-right: 40px; + width: 8%; @media (max-width: 1100px) { padding-right: 10px; } @@ -113,17 +124,21 @@ tr, td, th { } .txs.widget { padding-right: 0; + display: none; @media (max-width: 650px) { display: none; } } .txs.legacy { - padding-right: 80px; - width: 10%; + width: 18%; + display: table-cell; + @media (max-width: 1000px) { + padding-right: 20px; + } } .fees { - width: 10%; + width: 8%; @media (max-width: 650px) { display: none; } @@ -133,7 +148,7 @@ tr, td, th { } .reward { - width: 10%; + width: 8%; @media (max-width: 576px) { width: 7%; padding-right: 30px; @@ -152,8 +167,11 @@ tr, td, th { } .size { - width: 12%; + width: 10%; @media (max-width: 1000px) { + width: 13%; + } + @media (max-width: 950px) { width: 15%; } @media (max-width: 650px) { @@ -164,12 +182,34 @@ tr, td, th { } } .size.legacy { - width: 20%; + width: 30%; @media (max-width: 576px) { display: table-cell; } } +.health { + width: 10%; + @media (max-width: 1000px) { + width: 13%; + } + @media (max-width: 950px) { + display: none; + } +} +.health.widget { + width: 25%; + @media (max-width: 1000px) { + display: none; + } + @media (max-width: 767px) { + display: table-cell; + } + @media (max-width: 500px) { + display: none; + } +} + /* Tooltip text */ .tooltip-custom { position: relative; diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index 65293098d..dd9de6aae 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -668,6 +668,15 @@ h1, h2, h3 { background-color: #2e324e; } +.progress.progress-health { + background: repeating-linear-gradient(to right, #2d3348, #2d3348 0%, #105fb0 0%, #1a9436 100%); + justify-content: flex-end; +} + +.progress-bar.progress-bar-health { + background: #2d3348; +} + .mt-2-5, .my-2-5 { margin-top: 0.75rem !important; } From 5a7e8e0ec3789e897eabf21e871092359cd48fcc Mon Sep 17 00:00:00 2001 From: hunicus <93150691+hunicus@users.noreply.github.com> Date: Sun, 30 Oct 2022 12:39:20 -0400 Subject: [PATCH 70/76] Add electrum rpc port numbers and update note --- .../app/docs/api-docs/api-docs.component.html | 13 +++++++------ .../src/app/docs/api-docs/api-docs.component.ts | 16 ++++++++++++++++ frontend/src/app/docs/docs/docs.component.ts | 2 +- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/docs/api-docs/api-docs.component.html b/frontend/src/app/docs/api-docs/api-docs.component.html index 90c35252a..e2524a27d 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.html +++ b/frontend/src/app/docs/api-docs/api-docs.component.html @@ -109,12 +109,13 @@
    - -

    This part of the API is available to sponsors only—whitelisting is required.

    -
    - -

    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

    -
    +

    Hostname

    +

    {{plainHostname}}

    +

    Port

    +

    {{electrsPort}}

    +

    SSL

    +

    Enabled

    +

    Electrum RPC interface for Bitcoin Signet is publicly available. Electrum RPC interface for all other networks is available to sponsors only—whitelisting is required.

    diff --git a/frontend/src/app/docs/api-docs/api-docs.component.ts b/frontend/src/app/docs/api-docs/api-docs.component.ts index ed0ecb0a2..7b78d187b 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.ts +++ b/frontend/src/app/docs/api-docs/api-docs.component.ts @@ -12,6 +12,8 @@ import { FaqTemplateDirective } from '../faq-template/faq-template.component'; styleUrls: ['./api-docs.component.scss'] }) export class ApiDocsComponent implements OnInit, AfterViewInit { + plainHostname = document.location.hostname; + electrsPort = 0; hostname = document.location.hostname; network$: Observable; active = 0; @@ -82,6 +84,20 @@ export class ApiDocsComponent implements OnInit, AfterViewInit { this.network$.subscribe((network) => { this.active = (network === 'liquid' || network === 'liquidtestnet') ? 2 : 0; + switch( network ) { + case "": + this.electrsPort = 50002; break; + case "mainnet": + this.electrsPort = 50002; break; + case "testnet": + this.electrsPort = 60002; break; + case "signet": + this.electrsPort = 60602; break; + case "liquid": + this.electrsPort = 51002; break; + case "liquidtestnet": + this.electrsPort = 51302; break; + } }); } diff --git a/frontend/src/app/docs/docs/docs.component.ts b/frontend/src/app/docs/docs/docs.component.ts index c129cd21e..3e74ba959 100644 --- a/frontend/src/app/docs/docs/docs.component.ts +++ b/frontend/src/app/docs/docs/docs.component.ts @@ -46,7 +46,7 @@ export class DocsComponent implements OnInit { this.env = this.stateService.env; this.showWebSocketTab = ( ! ( ( this.stateService.network === "bisq" ) || ( this.stateService.network === "liquidtestnet" ) ) ); this.showFaqTab = ( this.env.BASE_MODULE === 'mempool' ) ? true : false; - this.showElectrsTab = this.stateService.env.OFFICIAL_MEMPOOL_SPACE && ( this.stateService.network === "" || this.stateService.network === "mainnet" || this.stateService.network === "testnet" || this.stateService.network === "signet" ); + this.showElectrsTab = this.stateService.env.OFFICIAL_MEMPOOL_SPACE && ( this.stateService.network !== "bisq" ); document.querySelector( "html" ).style.scrollBehavior = "smooth"; } From cfa2a48185854136151f61d120ff4bd5ef2f3232 Mon Sep 17 00:00:00 2001 From: hunicus <93150691+hunicus@users.noreply.github.com> Date: Sun, 30 Oct 2022 13:08:26 -0400 Subject: [PATCH 71/76] Position docs footer on bottom For short docs pages (like electrum rpc). --- frontend/src/app/docs/api-docs/api-docs.component.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/src/app/docs/api-docs/api-docs.component.scss b/frontend/src/app/docs/api-docs/api-docs.component.scss index aebaafe6f..acfc209e5 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.scss +++ b/frontend/src/app/docs/api-docs/api-docs.component.scss @@ -10,6 +10,12 @@ font-size: 12px; } +.container-xl { + display: flex; + min-height: 75vh; + flex-direction: column; +} + code { background-color: #1d1f31; font-family: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New; From 90450f9570614b3c66ee05b2b0e11180a72aa59b Mon Sep 17 00:00:00 2001 From: softsimon Date: Mon, 7 Nov 2022 04:28:23 +0400 Subject: [PATCH 72/76] Use relative import paths in the frontend --- .../components/block-audit/block-audit.component.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/components/block-audit/block-audit.component.ts b/frontend/src/app/components/block-audit/block-audit.component.ts index a7eb879b4..f8ce8d9bb 100644 --- a/frontend/src/app/components/block-audit/block-audit.component.ts +++ b/frontend/src/app/components/block-audit/block-audit.component.ts @@ -1,12 +1,12 @@ import { Component, OnDestroy, OnInit, AfterViewInit, ViewChildren, QueryList } from '@angular/core'; import { ActivatedRoute, ParamMap, Router } from '@angular/router'; -import { Observable, Subscription, combineLatest } from 'rxjs'; +import { Subscription, combineLatest } from 'rxjs'; import { map, switchMap, startWith, catchError } from 'rxjs/operators'; -import { BlockAudit, TransactionStripped } from 'src/app/interfaces/node-api.interface'; -import { ApiService } from 'src/app/services/api.service'; -import { StateService } from 'src/app/services/state.service'; -import { detectWebGL } from 'src/app/shared/graphs.utils'; -import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe'; +import { BlockAudit, TransactionStripped } from '../../interfaces/node-api.interface'; +import { ApiService } from '../../services/api.service'; +import { StateService } from '../../services/state.service'; +import { detectWebGL } from '../../shared/graphs.utils'; +import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe'; import { BlockOverviewGraphComponent } from '../block-overview-graph/block-overview-graph.component'; @Component({ From e8dfc67ff4e97d2958cda53d227f689e5a764189 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sun, 6 Nov 2022 20:24:12 -0800 Subject: [PATCH 73/76] Update staging hosts for testing --- frontend/proxy.conf.staging.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/proxy.conf.staging.js b/frontend/proxy.conf.staging.js index 098edb619..0cf366ca7 100644 --- a/frontend/proxy.conf.staging.js +++ b/frontend/proxy.conf.staging.js @@ -3,9 +3,9 @@ const fs = require('fs'); let PROXY_CONFIG = require('./proxy.conf'); PROXY_CONFIG.forEach(entry => { - entry.target = entry.target.replace("mempool.space", "mempool.ninja"); - entry.target = entry.target.replace("liquid.network", "liquid.place"); - entry.target = entry.target.replace("bisq.markets", "bisq.ninja"); + entry.target = entry.target.replace("mempool.space", "mempool-staging.fra.mempool.space"); + entry.target = entry.target.replace("liquid.network", "liquid-staging.fra.mempool.space"); + entry.target = entry.target.replace("bisq.markets", "bisq-staging.fra.mempool.space"); }); module.exports = PROXY_CONFIG; From c50f51b02ec47d3a1ac3a1b3fcaf5605d78d254c Mon Sep 17 00:00:00 2001 From: wiz Date: Mon, 7 Nov 2022 15:44:25 +0900 Subject: [PATCH 74/76] [ops] Fix nvidia-driver package name --- production/install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/production/install b/production/install index 96bb4b623..9bab3a418 100755 --- a/production/install +++ b/production/install @@ -401,7 +401,7 @@ FREEBSD_PKG+=(nginx rsync py39-certbot-nginx mariadb105-server keybase) FREEBSD_PKG+=(geoipupdate) FREEBSD_UNFURL_PKG=() -FREEBSD_UNFURL_PKG+=(nvidia-driver-470-470.129.06 chromium xinit xterm twm ja-sourcehansans-otf) +FREEBSD_UNFURL_PKG+=(nvidia-driver-470 chromium xinit xterm twm ja-sourcehansans-otf) FREEBSD_UNFURL_PKG+=(zh-sourcehansans-sc-otf ko-aleefonts-ttf lohit tlwg-ttf) ############################# From 149c4e2bf048eb45ac4bf52353eabf042a39b32a Mon Sep 17 00:00:00 2001 From: softsimon Date: Sat, 19 Nov 2022 15:57:24 +0900 Subject: [PATCH 75/76] Proxy config for running esplora locally --- frontend/angular.json | 4 + frontend/package.json | 1 + frontend/proxy.conf.local-esplora.js | 137 +++++++++++++++++++++++++++ 3 files changed, 142 insertions(+) create mode 100644 frontend/proxy.conf.local-esplora.js diff --git a/frontend/angular.json b/frontend/angular.json index 1ed29cad9..a94b3b85f 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -222,6 +222,10 @@ "proxyConfig": "proxy.conf.local.js", "verbose": true }, + "local-esplora": { + "proxyConfig": "proxy.conf.local-esplora.js", + "verbose": true + }, "mixed": { "proxyConfig": "proxy.conf.mixed.js", "verbose": true diff --git a/frontend/package.json b/frontend/package.json index c5a062e01..9423eb901 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -29,6 +29,7 @@ "serve:local-prod": "npm run generate-config && npm run ng -- serve -c local-prod", "serve:local-staging": "npm run generate-config && npm run ng -- serve -c local-staging", "start": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local", + "start:local-esplora": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-esplora", "start:stg": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c staging", "start:local-prod": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-prod", "start:local-staging": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-staging", diff --git a/frontend/proxy.conf.local-esplora.js b/frontend/proxy.conf.local-esplora.js new file mode 100644 index 000000000..8bb57e623 --- /dev/null +++ b/frontend/proxy.conf.local-esplora.js @@ -0,0 +1,137 @@ +const fs = require('fs'); + +const FRONTEND_CONFIG_FILE_NAME = 'mempool-frontend-config.json'; + +let configContent; + +// Read frontend config +try { + const rawConfig = fs.readFileSync(FRONTEND_CONFIG_FILE_NAME); + configContent = JSON.parse(rawConfig); + console.log(`${FRONTEND_CONFIG_FILE_NAME} file found, using provided config`); +} catch (e) { + console.log(e); + if (e.code !== 'ENOENT') { + throw new Error(e); + } else { + console.log(`${FRONTEND_CONFIG_FILE_NAME} file not found, using default config`); + } +} + +let PROXY_CONFIG = []; + +if (configContent && configContent.BASE_MODULE === 'liquid') { + PROXY_CONFIG.push(...[ + { + context: ['/liquid/api/v1/**'], + target: `http://127.0.0.1:8999`, + secure: false, + ws: true, + changeOrigin: true, + proxyTimeout: 30000, + pathRewrite: { + "^/liquid": "" + }, + }, + { + context: ['/liquid/api/**'], + target: `http://127.0.0.1:3000`, + secure: false, + changeOrigin: true, + proxyTimeout: 30000, + pathRewrite: { + "^/liquid/api/": "" + }, + }, + { + context: ['/liquidtestnet/api/v1/**'], + target: `http://127.0.0.1:8999`, + secure: false, + ws: true, + changeOrigin: true, + proxyTimeout: 30000, + pathRewrite: { + "^/liquidtestnet": "" + }, + }, + { + context: ['/liquidtestnet/api/**'], + target: `http://127.0.0.1:3000`, + secure: false, + changeOrigin: true, + proxyTimeout: 30000, + pathRewrite: { + "^/liquidtestnet/api/": "/" + }, + }, + ]); +} + + +if (configContent && configContent.BASE_MODULE === 'bisq') { + PROXY_CONFIG.push(...[ + { + context: ['/bisq/api/v1/ws'], + target: `http://127.0.0.1:8999`, + secure: false, + ws: true, + changeOrigin: true, + proxyTimeout: 30000, + pathRewrite: { + "^/bisq": "" + }, + }, + { + context: ['/bisq/api/v1/**'], + target: `http://127.0.0.1:8999`, + secure: false, + changeOrigin: true, + proxyTimeout: 30000, + }, + { + context: ['/bisq/api/**'], + target: `http://127.0.0.1:8999`, + secure: false, + changeOrigin: true, + proxyTimeout: 30000, + pathRewrite: { + "^/bisq/api/": "/api/v1/bisq/" + }, + } + ]); +} + +PROXY_CONFIG.push(...[ + { + context: ['/testnet/api/v1/lightning/**'], + target: `http://127.0.0.1:8999`, + secure: false, + changeOrigin: true, + proxyTimeout: 30000, + pathRewrite: { + "^/testnet": "" + }, + }, + { + context: ['/api/v1/**'], + target: `http://127.0.0.1:8999`, + secure: false, + ws: true, + changeOrigin: true, + proxyTimeout: 30000, + }, + { + context: ['/api/**'], + target: `http://127.0.0.1:3000`, + secure: false, + changeOrigin: true, + proxyTimeout: 30000, + pathRewrite: { + "^/api": "" + }, + } +]); + +console.log(PROXY_CONFIG); + +module.exports = PROXY_CONFIG; \ No newline at end of file From 263c13b1c4d7271c59ad6a4a46832f2596179f32 Mon Sep 17 00:00:00 2001 From: softsimon Date: Mon, 21 Nov 2022 17:26:56 +0900 Subject: [PATCH 76/76] Set mempool enabled to false in production. --- production/mempool-config.mainnet-lightning.json | 1 + production/mempool-config.signet-lightning.json | 1 + production/mempool-config.testnet-lightning.json | 1 + 3 files changed, 3 insertions(+) diff --git a/production/mempool-config.mainnet-lightning.json b/production/mempool-config.mainnet-lightning.json index 99ce8d518..785a55ae6 100644 --- a/production/mempool-config.mainnet-lightning.json +++ b/production/mempool-config.mainnet-lightning.json @@ -1,5 +1,6 @@ { "MEMPOOL": { + "ENABLED": false, "NETWORK": "mainnet", "BACKEND": "esplora", "HTTP_PORT": 8993, diff --git a/production/mempool-config.signet-lightning.json b/production/mempool-config.signet-lightning.json index 08250ffbd..5e3daa625 100644 --- a/production/mempool-config.signet-lightning.json +++ b/production/mempool-config.signet-lightning.json @@ -1,5 +1,6 @@ { "MEMPOOL": { + "ENABLED": false, "NETWORK": "signet", "BACKEND": "esplora", "HTTP_PORT": 8991, diff --git a/production/mempool-config.testnet-lightning.json b/production/mempool-config.testnet-lightning.json index c2a42e4bc..e6f954385 100644 --- a/production/mempool-config.testnet-lightning.json +++ b/production/mempool-config.testnet-lightning.json @@ -1,5 +1,6 @@ { "MEMPOOL": { + "ENABLED": false, "NETWORK": "testnet", "BACKEND": "esplora", "HTTP_PORT": 8992,