From 8ea7bb907c519002ae17ccfaae52075ed6a2525f Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 22 Aug 2023 02:40:00 +0900 Subject: [PATCH 01/14] Fix js error on mouseover on difficulty skeleton --- .../src/app/components/difficulty/difficulty.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/difficulty/difficulty.component.ts b/frontend/src/app/components/difficulty/difficulty.component.ts index 7f305416f..81084f524 100644 --- a/frontend/src/app/components/difficulty/difficulty.component.ts +++ b/frontend/src/app/components/difficulty/difficulty.component.ts @@ -194,7 +194,7 @@ export class DifficultyComponent implements OnInit { @HostListener('pointerdown', ['$event']) onPointerDown(event): void { - if (this.epochSvgElement.nativeElement?.contains(event.target)) { + if (this.epochSvgElement?.nativeElement?.contains(event.target)) { this.onPointerMove(event); event.preventDefault(); } @@ -202,7 +202,7 @@ export class DifficultyComponent implements OnInit { @HostListener('pointermove', ['$event']) onPointerMove(event): void { - if (this.epochSvgElement.nativeElement?.contains(event.target)) { + if (this.epochSvgElement?.nativeElement?.contains(event.target)) { this.tooltipPosition = { x: event.clientX, y: event.clientY }; this.cd.markForCheck(); } From 71d4aa9ddd2a91274ebe31d30329df0b5cc6aee8 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 6 Sep 2023 08:19:41 +0900 Subject: [PATCH 02/14] Add mempool count line to graph --- backend/src/api/statistics/statistics-api.ts | 2 + .../mempool-graph/mempool-graph.component.ts | 98 ++++++++++++++++--- .../statistics/statistics.component.html | 10 +- .../statistics/statistics.component.ts | 2 + .../src/app/interfaces/node-api.interface.ts | 1 + 5 files changed, 100 insertions(+), 13 deletions(-) diff --git a/backend/src/api/statistics/statistics-api.ts b/backend/src/api/statistics/statistics-api.ts index 9df12d704..ecc0222c1 100644 --- a/backend/src/api/statistics/statistics-api.ts +++ b/backend/src/api/statistics/statistics-api.ts @@ -171,6 +171,7 @@ class StatisticsApi { private getQueryForDaysAvg(div: number, interval: string) { return `SELECT UNIX_TIMESTAMP(added) as added, + CAST(avg(unconfirmed_transactions) as DOUBLE) as unconfirmed_transactions, CAST(avg(vbytes_per_second) as DOUBLE) as vbytes_per_second, CAST(avg(vsize_1) as DOUBLE) as vsize_1, CAST(avg(vsize_2) as DOUBLE) as vsize_2, @@ -401,6 +402,7 @@ class StatisticsApi { return statistic.map((s) => { return { added: s.added, + count: s.unconfirmed_transactions, vbytes_per_second: s.vbytes_per_second, mempool_byte_weight: s.mempool_byte_weight, total_fee: s.total_fee, diff --git a/frontend/src/app/components/mempool-graph/mempool-graph.component.ts b/frontend/src/app/components/mempool-graph/mempool-graph.component.ts index 6c9795c89..d31044be9 100644 --- a/frontend/src/app/components/mempool-graph/mempool-graph.component.ts +++ b/frontend/src/app/components/mempool-graph/mempool-graph.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnChanges } from '@angular/core'; import { VbytesPipe } from '../../shared/pipes/bytes-pipe/vbytes.pipe'; import { WuBytesPipe } from '../../shared/pipes/bytes-pipe/wubytes.pipe'; +import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe'; import { formatNumber } from '@angular/common'; import { OptimizedMempoolStats } from '../../interfaces/node-api.interface'; import { StateService } from '../../services/state.service'; @@ -26,6 +27,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges { @Input() data: any[]; @Input() filterSize = 100000; @Input() limitFilterFee = 1; + @Input() hideCount: boolean = false; @Input() height: number | string = 200; @Input() top: number | string = 20; @Input() right: number | string = 10; @@ -50,10 +52,13 @@ export class MempoolGraphComponent implements OnInit, OnChanges { inverted: boolean; chartInstance: any = undefined; weightMode: boolean = false; + isWidget: boolean = false; + showCount: boolean = true; constructor( private vbytesPipe: VbytesPipe, private wubytesPipe: WuBytesPipe, + private amountShortenerPipe: AmountShortenerPipe, private stateService: StateService, private storageService: StorageService, @Inject(LOCALE_ID) private locale: string, @@ -62,12 +67,16 @@ export class MempoolGraphComponent implements OnInit, OnChanges { ngOnInit(): void { this.isLoading = true; this.inverted = this.storageService.getValue('inverted-graph') === 'true'; + this.isWidget = this.template === 'widget'; + this.showCount = !this.isWidget && !this.hideCount; } - ngOnChanges() { + ngOnChanges(changes) { if (!this.data) { return; } + this.isWidget = this.template === 'widget'; + this.showCount = !this.isWidget && !this.hideCount; this.windowPreference = this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference'); this.mempoolVsizeFeesData = this.handleNewMempoolData(this.data.concat([])); this.mountFeeChart(); @@ -96,10 +105,12 @@ export class MempoolGraphComponent implements OnInit, OnChanges { mempoolStats.reverse(); const labels = mempoolStats.map(stats => stats.added); const finalArrayVByte = this.generateArray(mempoolStats); + const finalArrayCount = this.generateCountArray(mempoolStats); return { labels: labels, - series: finalArrayVByte + series: finalArrayVByte, + countSeries: finalArrayCount, }; } @@ -124,9 +135,13 @@ export class MempoolGraphComponent implements OnInit, OnChanges { return finalArray; } + generateCountArray(mempoolStats: OptimizedMempoolStats[]) { + return mempoolStats.map(stats => [stats.added * 1000, stats.count]); + } + mountFeeChart() { this.orderLevels(); - const { series } = this.mempoolVsizeFeesData; + const { series, countSeries } = this.mempoolVsizeFeesData; const seriesGraph = []; const newColors = []; @@ -178,6 +193,29 @@ export class MempoolGraphComponent implements OnInit, OnChanges { }); } } + if (this.showCount) { + newColors.push('white'); + seriesGraph.push({ + zlevel: 1, + yAxisIndex: 1, + name: 'count', + type: 'line', + stack: 'count', + smooth: false, + markPoint: false, + lineStyle: { + width: 2, + opacity: 1, + }, + symbol: 'none', + silent: true, + areaStyle: { + color: null, + opacity: 0, + }, + data: countSeries, + }); + } this.mempoolVsizeFeesOptions = { series: this.inverted ? [...seriesGraph].reverse() : seriesGraph, @@ -201,7 +239,11 @@ export class MempoolGraphComponent implements OnInit, OnChanges { label: { formatter: (params: any) => { if (params.axisDimension === 'y') { - return this.vbytesPipe.transform(params.value, 2, 'vB', 'MvB', true) + if (params.axisIndex === 0) { + return this.vbytesPipe.transform(params.value, 2, 'vB', 'MvB', true); + } else { + return this.amountShortenerPipe.transform(params.value, 2, undefined, true); + } } else { return formatterXAxis(this.locale, this.windowPreference, params.value); } @@ -214,7 +256,11 @@ export class MempoolGraphComponent implements OnInit, OnChanges { const itemFormatted = []; let totalParcial = 0; let progressPercentageText = ''; - const items = this.inverted ? [...params].reverse() : params; + let countItem; + let items = this.inverted ? [...params].reverse() : params; + if (items[items.length - 1].seriesName === 'count') { + countItem = items.pop(); + } items.map((item: any, index: number) => { totalParcial += item.value[1]; const progressPercentage = (item.value[1] / totalValue) * 100; @@ -276,6 +322,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges { `); }); const classActive = (this.template === 'advanced') ? 'fees-wrapper-tooltip-chart-advanced' : ''; + const titleCount = $localize`Count`; const titleRange = $localize`Range`; const titleSize = $localize`:@@7faaaa08f56427999f3be41df1093ce4089bbd75:Size`; const titleSum = $localize`Sum`; @@ -286,6 +333,25 @@ export class MempoolGraphComponent implements OnInit, OnChanges { ${this.vbytesPipe.transform(totalValue, 2, 'vB', 'MvB', false)} + ` + + (this.showCount && countItem ? ` + + + + + + + +
+ + + ${titleCount} + + + ${this.amountShortenerPipe.transform(countItem.value[1], 2, undefined, true)} +
+ ` : '') + + ` @@ -305,12 +371,12 @@ export class MempoolGraphComponent implements OnInit, OnChanges { `; } }, - dataZoom: (this.template === 'widget' && this.isMobile()) ? null : [{ + dataZoom: (this.isWidget && this.isMobile()) ? null : [{ type: 'inside', realtime: true, - zoomLock: (this.template === 'widget') ? true : false, + zoomLock: (this.isWidget) ? true : false, zoomOnMouseWheel: (this.template === 'advanced') ? true : false, - moveOnMouseMove: (this.template === 'widget') ? true : false, + moveOnMouseMove: (this.isWidget) ? true : false, maxSpan: 100, minSpan: 10, }, { @@ -339,7 +405,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges { }, xAxis: [ { - name: this.template === 'widget' ? '' : formatterXAxisLabel(this.locale, this.windowPreference), + name: this.isWidget ? '' : formatterXAxisLabel(this.locale, this.windowPreference), nameLocation: 'middle', nameTextStyle: { padding: [20, 0, 0, 0], @@ -357,7 +423,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges { }, } ], - yAxis: { + yAxis: [{ type: 'value', axisLine: { onZero: false }, axisLabel: { @@ -371,7 +437,17 @@ export class MempoolGraphComponent implements OnInit, OnChanges { opacity: 0.25, } } - }, + }, this.showCount ? { + type: 'value', + position: 'right', + axisLine: { onZero: false }, + axisLabel: { + formatter: (value: number) => (`${this.amountShortenerPipe.transform(value, 2, undefined, true)}`), + }, + splitLine: { + show: false, + } + } : null], }; } diff --git a/frontend/src/app/components/statistics/statistics.component.html b/frontend/src/app/components/statistics/statistics.component.html index 29089e43d..02a26ed52 100644 --- a/frontend/src/app/components/statistics/statistics.component.html +++ b/frontend/src/app/components/statistics/statistics.component.html @@ -69,6 +69,12 @@
-
The Mempool Open Source Project
+
The Mempool Open Source Project ®

Our mempool and blockchain explorer for the Bitcoin community, focusing on the transaction fee market and multi-layer ecosystem, completely self-hosted without any trusted third-parties.

From 0ba4e98f52cc539cea112317b54f60c6388304f4 Mon Sep 17 00:00:00 2001 From: orangesurf Date: Tue, 12 Sep 2023 19:55:00 +0100 Subject: [PATCH 05/14] Replace other instances of tm use for The Mempool Open Source Project --- frontend/src/app/components/about/about.component.ts | 2 +- .../components/privacy-policy/privacy-policy.component.ts | 2 +- .../trademark-policy/trademark-policy.component.ts | 2 +- frontend/src/index.mempool.html | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/components/about/about.component.ts b/frontend/src/app/components/about/about.component.ts index 3cda0dc20..8aa0422e8 100644 --- a/frontend/src/app/components/about/about.component.ts +++ b/frontend/src/app/components/about/about.component.ts @@ -43,7 +43,7 @@ export class AboutComponent implements OnInit { ngOnInit() { this.backendInfo$ = this.stateService.backendInfo$; this.seoService.setTitle($localize`:@@004b222ff9ef9dd4771b777950ca1d0e4cd4348a:About`); - this.seoService.setDescription($localize`:@@meta.description.about:Learn more about The Mempool Open Source Project™\: enterprise sponsors, individual sponsors, integrations, who contributes, FOSS licensing, and more.`); + this.seoService.setDescription($localize`:@@meta.description.about:Learn more about The Mempool Open Source Project®\: enterprise sponsors, individual sponsors, integrations, who contributes, FOSS licensing, and more.`); this.websocketService.want(['blocks']); this.profiles$ = this.apiService.getAboutPageProfiles$().pipe( diff --git a/frontend/src/app/components/privacy-policy/privacy-policy.component.ts b/frontend/src/app/components/privacy-policy/privacy-policy.component.ts index 7a44070eb..b98390731 100644 --- a/frontend/src/app/components/privacy-policy/privacy-policy.component.ts +++ b/frontend/src/app/components/privacy-policy/privacy-policy.component.ts @@ -17,6 +17,6 @@ export class PrivacyPolicyComponent { ngOnInit(): void { this.seoService.setTitle('Privacy Policy'); - this.seoService.setDescription('Trusted third parties are security holes, as are trusted first parties...you should only trust your own self-hosted instance of The Mempool Open Source Project™.'); + this.seoService.setDescription('Trusted third parties are security holes, as are trusted first parties...you should only trust your own self-hosted instance of The Mempool Open Source Project®.'); } } diff --git a/frontend/src/app/components/trademark-policy/trademark-policy.component.ts b/frontend/src/app/components/trademark-policy/trademark-policy.component.ts index 08f16264a..b8f53afcf 100644 --- a/frontend/src/app/components/trademark-policy/trademark-policy.component.ts +++ b/frontend/src/app/components/trademark-policy/trademark-policy.component.ts @@ -17,6 +17,6 @@ export class TrademarkPolicyComponent { ngOnInit(): void { this.seoService.setTitle('Trademark Policy'); - this.seoService.setDescription('An overview of the trademarks registered by Mempool Space K.K. and The Mempool Open Source Project™ and what we consider to be lawful usage of those trademarks.'); + this.seoService.setDescription('An overview of the trademarks registered by Mempool Space K.K. and The Mempool Open Source Project® and what we consider to be lawful usage of those trademarks.'); } } diff --git a/frontend/src/index.mempool.html b/frontend/src/index.mempool.html index 03edc08ef..def14434e 100644 --- a/frontend/src/index.mempool.html +++ b/frontend/src/index.mempool.html @@ -7,17 +7,17 @@ - + - + - + From 9b17c163257ecc0bae4fddc907e8b42c3e7f2907 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 25 Aug 2023 00:57:47 +0900 Subject: [PATCH 06/14] Notify address page of removed transactions --- backend/src/api/websocket-handler.ts | 9 ++++-- .../components/address/address.component.ts | 29 +++++++++++++++++++ frontend/src/app/services/state.service.ts | 1 + .../src/app/services/websocket.service.ts | 6 ++++ 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 41cb6b99c..c50941f39 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -486,6 +486,7 @@ class WebsocketHandler { // pre-compute address transactions const addressCache = this.makeAddressCache(newTransactions); + const removedAddressCache = this.makeAddressCache(deletedTransactions); this.wss.clients.forEach(async (client) => { if (client.readyState !== WebSocket.OPEN) { @@ -526,11 +527,15 @@ class WebsocketHandler { } if (client['track-address']) { - const foundTransactions = Array.from(addressCache[client['track-address']]?.values() || []); + const newTransactions = Array.from(addressCache[client['track-address']]?.values() || []); + const removedTransactions = Array.from(removedAddressCache[client['track-address']]?.values() || []); // txs may be missing prevouts in non-esplora backends // so fetch the full transactions now - const fullTransactions = (config.MEMPOOL.BACKEND !== 'esplora') ? await this.getFullTransactions(foundTransactions) : foundTransactions; + const fullTransactions = (config.MEMPOOL.BACKEND !== 'esplora') ? await this.getFullTransactions(newTransactions) : newTransactions; + if (removedTransactions.length) { + response['address-removed-transactions'] = JSON.stringify(removedTransactions); + } if (fullTransactions.length) { response['address-transactions'] = JSON.stringify(fullTransactions); } diff --git a/frontend/src/app/components/address/address.component.ts b/frontend/src/app/components/address/address.component.ts index e9cd0189b..0e10b207f 100644 --- a/frontend/src/app/components/address/address.component.ts +++ b/frontend/src/app/components/address/address.component.ts @@ -174,6 +174,11 @@ export class AddressComponent implements OnInit, OnDestroy { this.addTransaction(tx); }); + this.stateService.mempoolRemovedTransactions$ + .subscribe(tx => { + this.removeTransaction(tx); + }); + this.stateService.blockTransactions$ .subscribe((transaction) => { const tx = this.transactions.find((t) => t.txid === transaction.txid); @@ -222,6 +227,30 @@ export class AddressComponent implements OnInit, OnDestroy { return true; } + removeTransaction(transaction: Transaction): boolean { + const index = this.transactions.findIndex(((tx) => tx.txid === transaction.txid)); + if (index === -1) { + return false; + } + + this.transactions.splice(index, 1); + this.transactions = this.transactions.slice(); + this.txCount--; + + transaction.vin.forEach((vin) => { + if (vin?.prevout?.scriptpubkey_address === this.address.address) { + this.sent -= vin.prevout.value; + } + }); + transaction.vout.forEach((vout) => { + if (vout?.scriptpubkey_address === this.address.address) { + this.received -= vout.value; + } + }); + + return true; + } + loadMore() { if (this.isLoadingTransactions || !this.totalConfirmedTxCount || this.loadedConfirmedTxCount >= this.totalConfirmedTxCount) { return; diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index 878edf359..db1268379 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -117,6 +117,7 @@ export class StateService { difficultyAdjustment$ = new ReplaySubject(1); mempoolTransactions$ = new Subject(); mempoolTxPosition$ = new Subject<{ txid: string, position: MempoolPosition, cpfp: CpfpInfo | null}>(); + mempoolRemovedTransactions$ = new Subject(); blockTransactions$ = new Subject(); isLoadingWebSocket$ = new ReplaySubject(1); isLoadingMempool$ = new BehaviorSubject(true); diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts index af2a15e8c..22da49f06 100644 --- a/frontend/src/app/services/websocket.service.ts +++ b/frontend/src/app/services/websocket.service.ts @@ -358,6 +358,12 @@ export class WebsocketService { }); } + if (response['address-removed-transactions']) { + response['address-removed-transactions'].forEach((addressTransaction: Transaction) => { + this.stateService.mempoolRemovedTransactions$.next(addressTransaction); + }); + } + if (response['block-transactions']) { response['block-transactions'].forEach((addressTransaction: Transaction) => { this.stateService.blockTransactions$.next(addressTransaction); From 47dd2cbd5d0e6b8fa61318b8c0daf62b10b46214 Mon Sep 17 00:00:00 2001 From: wiz Date: Sun, 17 Sep 2023 19:56:33 -0400 Subject: [PATCH 07/14] ops: Add VA1 servers to prod config --- production/mempool-config.liquid.json | 6 ++++ production/mempool-config.liquidtestnet.json | 6 ++++ .../mempool-config.mainnet-lightning.json | 29 +++++++++++++++++-- production/mempool-config.mainnet.json | 12 ++++++++ .../mempool-config.signet-lightning.json | 29 +++++++++++++++++-- production/mempool-config.signet.json | 6 ++++ .../mempool-config.testnet-lightning.json | 29 +++++++++++++++++-- production/mempool-config.testnet.json | 6 ++++ 8 files changed, 117 insertions(+), 6 deletions(-) diff --git a/production/mempool-config.liquid.json b/production/mempool-config.liquid.json index d67d7b794..a4f4bcd81 100644 --- a/production/mempool-config.liquid.json +++ b/production/mempool-config.liquid.json @@ -25,6 +25,12 @@ "ESPLORA": { "UNIX_SOCKET_PATH": "/elements/socket/esplora-liquid-mainnet", "FALLBACK": [ + "http://node201.va1.mempool.space:3001", + "http://node202.va1.mempool.space:3001", + "http://node203.va1.mempool.space:3001", + "http://node204.va1.mempool.space:3001", + "http://node205.va1.mempool.space:3001", + "http://node206.va1.mempool.space:3001", "http://node201.fmt.mempool.space:3001", "http://node202.fmt.mempool.space:3001", "http://node203.fmt.mempool.space:3001", diff --git a/production/mempool-config.liquidtestnet.json b/production/mempool-config.liquidtestnet.json index 3a76b4c86..cf2d70045 100644 --- a/production/mempool-config.liquidtestnet.json +++ b/production/mempool-config.liquidtestnet.json @@ -25,6 +25,12 @@ "ESPLORA": { "UNIX_SOCKET_PATH": "/elements/socket/esplora-liquid-testnet", "FALLBACK": [ + "http://node201.va1.mempool.space:3004", + "http://node202.va1.mempool.space:3004", + "http://node203.va1.mempool.space:3004", + "http://node204.va1.mempool.space:3004", + "http://node205.va1.mempool.space:3004", + "http://node206.va1.mempool.space:3004", "http://node201.fmt.mempool.space:3004", "http://node202.fmt.mempool.space:3004", "http://node203.fmt.mempool.space:3004", diff --git a/production/mempool-config.mainnet-lightning.json b/production/mempool-config.mainnet-lightning.json index 41e42a5bd..6bd326bfa 100644 --- a/production/mempool-config.mainnet-lightning.json +++ b/production/mempool-config.mainnet-lightning.json @@ -16,8 +16,33 @@ "PASSWORD": "__BITCOIN_RPC_PASS__" }, "ESPLORA": { - "REST_API_URL": "http://127.0.0.1:5000", - "UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-mainnet" + "UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-mainnet", + "FALLBACK": [ + "http://node201.va1.mempool.space:3000", + "http://node202.va1.mempool.space:3000", + "http://node203.va1.mempool.space:3000", + "http://node204.va1.mempool.space:3000", + "http://node205.va1.mempool.space:3000", + "http://node206.va1.mempool.space:3000", + "http://node201.fmt.mempool.space:3000", + "http://node202.fmt.mempool.space:3000", + "http://node203.fmt.mempool.space:3000", + "http://node204.fmt.mempool.space:3000", + "http://node205.fmt.mempool.space:3000", + "http://node206.fmt.mempool.space:3000", + "http://node201.fra.mempool.space:3000", + "http://node202.fra.mempool.space:3000", + "http://node203.fra.mempool.space:3000", + "http://node204.fra.mempool.space:3000", + "http://node205.fra.mempool.space:3000", + "http://node206.fra.mempool.space:3000", + "http://node201.tk7.mempool.space:3000", + "http://node202.tk7.mempool.space:3000", + "http://node203.tk7.mempool.space:3000", + "http://node204.tk7.mempool.space:3000", + "http://node205.tk7.mempool.space:3000", + "http://node206.tk7.mempool.space:3000" + ] }, "LIGHTNING": { "ENABLED": true, diff --git a/production/mempool-config.mainnet.json b/production/mempool-config.mainnet.json index d4222bd05..b2878ceef 100644 --- a/production/mempool-config.mainnet.json +++ b/production/mempool-config.mainnet.json @@ -37,6 +37,12 @@ "ESPLORA": { "UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-mainnet", "FALLBACK": [ + "http://node201.va1.mempool.space:3000", + "http://node202.va1.mempool.space:3000", + "http://node203.va1.mempool.space:3000", + "http://node204.va1.mempool.space:3000", + "http://node205.va1.mempool.space:3000", + "http://node206.va1.mempool.space:3000", "http://node201.fmt.mempool.space:3000", "http://node202.fmt.mempool.space:3000", "http://node203.fmt.mempool.space:3000", @@ -74,6 +80,12 @@ "AUDIT": true, "AUDIT_START_HEIGHT": 774000, "SERVERS": [ + "node201.va1.mempool.space", + "node202.va1.mempool.space", + "node203.va1.mempool.space", + "node204.va1.mempool.space", + "node205.va1.mempool.space", + "node206.va1.mempool.space", "node201.fmt.mempool.space", "node202.fmt.mempool.space", "node203.fmt.mempool.space", diff --git a/production/mempool-config.signet-lightning.json b/production/mempool-config.signet-lightning.json index 9971729e2..229b226be 100644 --- a/production/mempool-config.signet-lightning.json +++ b/production/mempool-config.signet-lightning.json @@ -16,8 +16,33 @@ "PASSWORD": "__BITCOIN_RPC_PASS__" }, "ESPLORA": { - "REST_API_URL": "http://127.0.0.1:5003", - "UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-signet" + "UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-signet", + "FALLBACK": [ + "http://node201.va1.mempool.space:3003", + "http://node202.va1.mempool.space:3003", + "http://node203.va1.mempool.space:3003", + "http://node204.va1.mempool.space:3003", + "http://node205.va1.mempool.space:3003", + "http://node206.va1.mempool.space:3003", + "http://node201.fmt.mempool.space:3003", + "http://node202.fmt.mempool.space:3003", + "http://node203.fmt.mempool.space:3003", + "http://node204.fmt.mempool.space:3003", + "http://node205.fmt.mempool.space:3003", + "http://node206.fmt.mempool.space:3003", + "http://node201.fra.mempool.space:3003", + "http://node202.fra.mempool.space:3003", + "http://node203.fra.mempool.space:3003", + "http://node204.fra.mempool.space:3003", + "http://node205.fra.mempool.space:3003", + "http://node206.fra.mempool.space:3003", + "http://node201.tk7.mempool.space:3003", + "http://node202.tk7.mempool.space:3003", + "http://node203.tk7.mempool.space:3003", + "http://node204.tk7.mempool.space:3003", + "http://node205.tk7.mempool.space:3003", + "http://node206.tk7.mempool.space:3003" + ] }, "LIGHTNING": { "ENABLED": true, diff --git a/production/mempool-config.signet.json b/production/mempool-config.signet.json index 38d59c0e9..c4b84f11b 100644 --- a/production/mempool-config.signet.json +++ b/production/mempool-config.signet.json @@ -27,6 +27,12 @@ "ESPLORA": { "UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-signet", "FALLBACK": [ + "http://node201.va1.mempool.space:3003", + "http://node202.va1.mempool.space:3003", + "http://node203.va1.mempool.space:3003", + "http://node204.va1.mempool.space:3003", + "http://node205.va1.mempool.space:3003", + "http://node206.va1.mempool.space:3003", "http://node201.fmt.mempool.space:3003", "http://node202.fmt.mempool.space:3003", "http://node203.fmt.mempool.space:3003", diff --git a/production/mempool-config.testnet-lightning.json b/production/mempool-config.testnet-lightning.json index ff7d4766f..60ee128a2 100644 --- a/production/mempool-config.testnet-lightning.json +++ b/production/mempool-config.testnet-lightning.json @@ -16,8 +16,33 @@ "PASSWORD": "__BITCOIN_RPC_PASS__" }, "ESPLORA": { - "REST_API_URL": "http://127.0.0.1:5002", - "UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-testnet" + "UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-testnet", + "FALLBACK": [ + "http://node201.va1.mempool.space:3002", + "http://node202.va1.mempool.space:3002", + "http://node203.va1.mempool.space:3002", + "http://node204.va1.mempool.space:3002", + "http://node205.va1.mempool.space:3002", + "http://node206.va1.mempool.space:3002", + "http://node201.fmt.mempool.space:3002", + "http://node202.fmt.mempool.space:3002", + "http://node203.fmt.mempool.space:3002", + "http://node204.fmt.mempool.space:3002", + "http://node205.fmt.mempool.space:3002", + "http://node206.fmt.mempool.space:3002", + "http://node201.fra.mempool.space:3002", + "http://node202.fra.mempool.space:3002", + "http://node203.fra.mempool.space:3002", + "http://node204.fra.mempool.space:3002", + "http://node205.fra.mempool.space:3002", + "http://node206.fra.mempool.space:3002", + "http://node201.tk7.mempool.space:3002", + "http://node202.tk7.mempool.space:3002", + "http://node203.tk7.mempool.space:3002", + "http://node204.tk7.mempool.space:3002", + "http://node205.tk7.mempool.space:3002", + "http://node206.tk7.mempool.space:3002" + ] }, "LIGHTNING": { "ENABLED": true, diff --git a/production/mempool-config.testnet.json b/production/mempool-config.testnet.json index c5bdfc8d7..b3d7cfadd 100644 --- a/production/mempool-config.testnet.json +++ b/production/mempool-config.testnet.json @@ -27,6 +27,12 @@ "ESPLORA": { "UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-testnet", "FALLBACK": [ + "http://node201.va1.mempool.space:3002", + "http://node202.va1.mempool.space:3002", + "http://node203.va1.mempool.space:3002", + "http://node204.va1.mempool.space:3002", + "http://node205.va1.mempool.space:3002", + "http://node206.va1.mempool.space:3002", "http://node201.fmt.mempool.space:3002", "http://node202.fmt.mempool.space:3002", "http://node203.fmt.mempool.space:3002", From 38d30d23e5eb8c3ca40f9a0969620daa7558ec13 Mon Sep 17 00:00:00 2001 From: wiz Date: Sun, 17 Sep 2023 20:57:47 -0400 Subject: [PATCH 08/14] Add VA1 mempool.space lightning node pubkeys --- backend/src/api/explorer/nodes.routes.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/backend/src/api/explorer/nodes.routes.ts b/backend/src/api/explorer/nodes.routes.ts index e2dbcb0b6..3636abe2b 100644 --- a/backend/src/api/explorer/nodes.routes.ts +++ b/backend/src/api/explorer/nodes.routes.ts @@ -42,6 +42,12 @@ class NodesRoutes { switch (config.MEMPOOL.NETWORK) { case 'testnet': nodesList = [ + '0259db43b4e4ac0ff12a805f2d81e521253ba2317f6739bc611d8e2fa156d64256', + '0352b9944b9a52bd2116c91f1ba70c4ef851ac5ba27e1b20f1d92da3ade010dd10', + '03424f5a7601eaa47482cb17100b31a84a04d14fb44b83a57eeceffd8e299878e3', + '032850492ee61a5f7006a2fda6925e4b4ec3782f2b6de2ff0e439ef5a38c3b2470', + '022c80bace98831c44c32fb69755f2b353434e0ee9e7fbda29507f7ef8abea1421', + '02c3559c833e6f99f9ca05fe503e0b4e7524dea9121344edfd3e811101e0c28680', '032c7c7819276c4f706a04df1a0f1e10a5495994a7be4c1d3d28ca766e5a2b957b', '025a7e38c2834dd843591a4d23d5f09cdeb77ddca85f673c2d944a14220ff14cf7', '0395e2731a1673ef21d7a16a727c4fc4d4c35a861c428ce2c819c53d2b81c8bd55', @@ -64,6 +70,12 @@ class NodesRoutes { break; case 'signet': nodesList = [ + '029fe3621fc0c6e08056a14b868f8fb9acca1aa28a129512f6cea0f0d7654d9f92', + '02f60cd7a3a4f1c953dd9554a6ebd51a34f8b10b8124b7fc43a0b381139b55c883', + '03cbbf581774700865eebd1be42d022bc004ba30881274ab304e088a25d70e773d', + '0243348cb3741cfe2d8485fa8375c29c7bc7cbb67577c363cb6987a5e5fd0052cc', + '02cb73e631af44bee600d80f8488a9194c9dc5c7590e575c421a070d1be05bc8e9', + '0306f55ee631aa1e2cd4d9b2bfcbc14404faec5c541cef8b2e6f779061029d09c4', '03ddab321b760433cbf561b615ef62ac7d318630c5f51d523aaf5395b90b751956', '033d92c7bfd213ef1b34c90e985fb5dc77f9ec2409d391492484e57a44c4aca1de', '02ad010dda54253c1eb9efe38b0760657a3b43ecad62198c359c051c9d99d45781', @@ -86,6 +98,12 @@ class NodesRoutes { break; default: nodesList = [ + '02b12b889fe3c943cb05645921040ef13d6d397a2e7a4ad000e28500c505ff26d6', + '0302240ac9d71b39617cbde2764837ec3d6198bd6074b15b75d2ff33108e89d2e1', + '03364a8ace313376e5e4b68c954e287c6388e16df9e9fdbaf0363ecac41105cbf6', + '03229ab4b7f692753e094b93df90530150680f86b535b5183b0cffd75b3df583fc', + '03a696eb7acde991c1be97a58a9daef416659539ae462b897f5e9ae361f990228e', + '0248bf26cf3a63ab8870f34dc0ec9e6c8c6288cdba96ba3f026f34ec0f13ac4055', '03fbc17549ec667bccf397ababbcb4cdc0e3394345e4773079ab2774612ec9be61', '03da9a8623241ccf95f19cd645c6cecd4019ac91570e976eb0a128bebbc4d8a437', '03ca5340cf85cb2e7cf076e489f785410838de174e40be62723e8a60972ad75144', From e79c32010dcef264e05bec909a2b730f1f318f23 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Mon, 18 Sep 2023 09:44:22 +0200 Subject: [PATCH 09/14] [lightning] fix js error when there is no geolocation available --- frontend/src/app/lightning/nodes-map/nodes-map.component.ts | 2 +- .../shared/components/geolocation/geolocation.component.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) 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 4f74723cc..ea80d8799 100644 --- a/frontend/src/app/lightning/nodes-map/nodes-map.component.ts +++ b/frontend/src/app/lightning/nodes-map/nodes-map.component.ts @@ -114,7 +114,7 @@ export class NodesMap implements OnInit, OnChanges { node[3], // Alias node[2], // Public key node[5], // Channels - node[6].en, // Country + node[6]?.en, // Country node[7], // ISO Code ]); } diff --git a/frontend/src/app/shared/components/geolocation/geolocation.component.ts b/frontend/src/app/shared/components/geolocation/geolocation.component.ts index 9cce1ea08..1a498a1b2 100644 --- a/frontend/src/app/shared/components/geolocation/geolocation.component.ts +++ b/frontend/src/app/shared/components/geolocation/geolocation.component.ts @@ -20,6 +20,11 @@ export class GeolocationComponent implements OnChanges { formattedLocation: string = ''; ngOnChanges(): void { + if (!this.data) { + this.formattedLocation = '-'; + return; + } + const city = this.data.city ? this.data.city : ''; const subdivisionLikeCity = this.data.city === this.data.subdivision; let subdivision = this.data.subdivision; From ef5d2606b76b6e383346592ac5ffd4badec08efe Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 18 Sep 2023 21:07:24 +0000 Subject: [PATCH 10/14] Add missing count data to longer statistics timespans --- backend/src/api/statistics/statistics-api.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/api/statistics/statistics-api.ts b/backend/src/api/statistics/statistics-api.ts index ecc0222c1..9518c4a0d 100644 --- a/backend/src/api/statistics/statistics-api.ts +++ b/backend/src/api/statistics/statistics-api.ts @@ -220,6 +220,7 @@ class StatisticsApi { private getQueryForDays(div: number, interval: string) { return `SELECT UNIX_TIMESTAMP(added) as added, + CAST(avg(unconfirmed_transactions) as DOUBLE) as unconfirmed_transactions, CAST(avg(vbytes_per_second) as DOUBLE) as vbytes_per_second, vsize_1, vsize_2, From c80201e3dbb551ad480b71f824912e6d4cba377f Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 18 Sep 2023 21:13:11 +0000 Subject: [PATCH 11/14] hide mempool count line before start of data --- .../src/app/components/mempool-graph/mempool-graph.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/components/mempool-graph/mempool-graph.component.ts b/frontend/src/app/components/mempool-graph/mempool-graph.component.ts index d31044be9..935e79b2c 100644 --- a/frontend/src/app/components/mempool-graph/mempool-graph.component.ts +++ b/frontend/src/app/components/mempool-graph/mempool-graph.component.ts @@ -136,7 +136,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges { } generateCountArray(mempoolStats: OptimizedMempoolStats[]) { - return mempoolStats.map(stats => [stats.added * 1000, stats.count]); + return mempoolStats.filter(stats => stats.count > 0).map(stats => [stats.added * 1000, stats.count]); } mountFeeChart() { From b645ad3fd89ffc269fb8195ede2147a3fc2daf75 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 19 Sep 2023 22:55:57 +0000 Subject: [PATCH 12/14] Reduce thrashing while initializing blockchain scroll position --- .../blockchain/blockchain.component.ts | 2 +- .../app/components/start/start.component.ts | 144 ++++++++++-------- 2 files changed, 85 insertions(+), 61 deletions(-) diff --git a/frontend/src/app/components/blockchain/blockchain.component.ts b/frontend/src/app/components/blockchain/blockchain.component.ts index 56b7c39e6..f8f2966dd 100644 --- a/frontend/src/app/components/blockchain/blockchain.component.ts +++ b/frontend/src/app/components/blockchain/blockchain.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, Output, EventEmitter, HostListener, ChangeDetectorRef, OnChanges, SimpleChanges } from '@angular/core'; +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, Output, EventEmitter, ChangeDetectorRef, OnChanges, SimpleChanges } from '@angular/core'; import { firstValueFrom, Subscription } from 'rxjs'; import { StateService } from '../../services/state.service'; diff --git a/frontend/src/app/components/start/start.component.ts b/frontend/src/app/components/start/start.component.ts index 18cd0ed30..86748bc8c 100644 --- a/frontend/src/app/components/start/start.component.ts +++ b/frontend/src/app/components/start/start.component.ts @@ -1,4 +1,4 @@ -import { Component, ElementRef, HostListener, OnInit, OnDestroy, ViewChild, Input, DoCheck } from '@angular/core'; +import { Component, ElementRef, HostListener, OnInit, OnDestroy, ViewChild, Input, ChangeDetectorRef, ChangeDetectionStrategy, AfterViewChecked } from '@angular/core'; import { Subscription } from 'rxjs'; import { MarkBlockState, StateService } from '../../services/state.service'; import { specialBlocks } from '../../app.constants'; @@ -8,8 +8,9 @@ import { BlockExtended } from '../../interfaces/node-api.interface'; selector: 'app-start', templateUrl: './start.component.html', styleUrls: ['./start.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush }) -export class StartComponent implements OnInit, OnDestroy, DoCheck { +export class StartComponent implements OnInit, AfterViewChecked, OnDestroy { @Input() showLoadingIndicator = false; interval = 60; @@ -42,6 +43,7 @@ export class StartComponent implements OnInit, OnDestroy, DoCheck { pageWidth: number; firstPageWidth: number; minScrollWidth: number; + currentScrollWidth: number = null; pageIndex: number = 0; pages: any[] = []; pendingMark: number | null = null; @@ -49,9 +51,10 @@ export class StartComponent implements OnInit, OnDestroy, DoCheck { lastUpdate: number = 0; lastMouseX: number; velocity: number = 0; - mempoolOffset: number = 0; + mempoolOffset: number | null = null; + mempoolWidth: number = 0; + scrollLeft: number = null; - private resizeObserver: ResizeObserver; chainWidth: number = window.innerWidth; menuOpen: boolean = false; menuSliding: boolean = false; @@ -59,19 +62,12 @@ export class StartComponent implements OnInit, OnDestroy, DoCheck { constructor( private stateService: StateService, + private cd: ChangeDetectorRef, ) { this.isiOS = ['iPhone','iPod','iPad'].includes((navigator as any)?.userAgentData?.platform || navigator.platform); } - ngDoCheck(): void { - if (this.pendingOffset != null) { - const offset = this.pendingOffset; - this.pendingOffset = null; - this.addConvertedScrollOffset(offset); - } - } - - ngOnInit() { + ngOnInit(): void { this.firstPageWidth = 40 + (this.blockWidth * this.dynamicBlocksAmount); this.blockCounterSubscription = this.stateService.blocks$.subscribe((blocks) => { this.blockCount = blocks.length; @@ -122,7 +118,7 @@ export class StartComponent implements OnInit, OnDestroy, DoCheck { this.scrollToBlock(scrollToHeight); } } - if (!this.tipIsSet || (blockHeight < 0 && !this.mempoolOffset)) { + if (!this.tipIsSet || (blockHeight < 0 && this.mempoolOffset == null)) { this.pendingMark = blockHeight; } } @@ -168,11 +164,41 @@ export class StartComponent implements OnInit, OnDestroy, DoCheck { }); } + ngAfterViewChecked(): void { + if (this.currentScrollWidth !== this.blockchainContainer?.nativeElement?.scrollWidth) { + this.currentScrollWidth = this.blockchainContainer?.nativeElement?.scrollWidth; + if (this.pendingOffset != null) { + const delta = this.pendingOffset - (this.mempoolOffset || 0); + this.mempoolOffset = this.pendingOffset; + this.currentScrollWidth = this.blockchainContainer?.nativeElement?.scrollWidth; + this.pendingOffset = null; + this.addConvertedScrollOffset(delta); + this.applyPendingMarkArrow(); + } else { + this.applyScrollLeft(); + } + } + } + onMempoolOffsetChange(offset): void { - const delta = offset - this.mempoolOffset; - this.addConvertedScrollOffset(delta); - this.mempoolOffset = offset; - this.applyPendingMarkArrow(); + this.pendingOffset = offset; + } + + applyScrollLeft(): void { + if (this.blockchainContainer?.nativeElement?.scrollWidth) { + let lastScrollLeft = null; + while (this.scrollLeft < 0 && this.shiftPagesForward() && lastScrollLeft !== this.scrollLeft) { + lastScrollLeft = this.scrollLeft; + this.scrollLeft += this.pageWidth; + } + lastScrollLeft = null; + while (this.scrollLeft > this.blockchainContainer.nativeElement.scrollWidth && this.shiftPagesBack() && lastScrollLeft !== this.scrollLeft) { + lastScrollLeft = this.scrollLeft; + this.scrollLeft -= this.pageWidth; + } + this.blockchainContainer.nativeElement.scrollLeft = this.scrollLeft; + } + this.cd.markForCheck(); } applyPendingMarkArrow(): void { @@ -191,6 +217,7 @@ export class StartComponent implements OnInit, OnDestroy, DoCheck { window.clearTimeout(this.menuTimeout); this.menuTimeout = window.setTimeout(() => { this.menuSliding = false; + this.cd.markForCheck(); }, 300); } @@ -200,24 +227,22 @@ export class StartComponent implements OnInit, OnDestroy, DoCheck { this.isMobile = this.chainWidth <= 767.98; let firstVisibleBlock; let offset; - if (this.blockchainContainer?.nativeElement != null) { - this.pages.forEach(page => { - const left = page.offset - this.getConvertedScrollOffset(); - const right = left + this.pageWidth; - if (left <= 0 && right > 0) { - const blockIndex = Math.max(0, Math.floor(left / -this.blockWidth)); - firstVisibleBlock = page.height - blockIndex; - offset = left + (blockIndex * this.blockWidth); - } - }); - } + this.pages.forEach(page => { + const left = page.offset - this.getConvertedScrollOffset(this.scrollLeft); + const right = left + this.pageWidth; + if (left <= 0 && right > 0) { + const blockIndex = Math.max(0, Math.floor(left / -this.blockWidth)); + firstVisibleBlock = page.height - blockIndex; + offset = left + (blockIndex * this.blockWidth); + } + }); this.blocksPerPage = Math.ceil(this.chainWidth / this.blockWidth); this.pageWidth = this.blocksPerPage * this.blockWidth; this.minScrollWidth = this.firstPageWidth + (this.pageWidth * 2); if (firstVisibleBlock != null) { - this.scrollToBlock(firstVisibleBlock, offset + (this.isMobile ? this.blockWidth : 0)); + this.scrollToBlock(firstVisibleBlock, offset); } else { this.updatePages(); } @@ -227,7 +252,7 @@ export class StartComponent implements OnInit, OnDestroy, DoCheck { if (!(event.which > 1 || event.button > 0)) { this.mouseDragStartX = event.clientX; this.resetMomentum(event.clientX); - this.blockchainScrollLeftInit = this.blockchainContainer.nativeElement.scrollLeft; + this.blockchainScrollLeftInit = this.scrollLeft; } } onPointerDown(event: PointerEvent) { @@ -253,8 +278,8 @@ export class StartComponent implements OnInit, OnDestroy, DoCheck { if (this.mouseDragStartX != null) { this.updateVelocity(event.clientX); this.stateService.setBlockScrollingInProgress(true); - this.blockchainContainer.nativeElement.scrollLeft = - this.blockchainScrollLeftInit + this.mouseDragStartX - event.clientX; + this.scrollLeft = this.blockchainScrollLeftInit + this.mouseDragStartX - event.clientX; + this.applyScrollLeft(); } } @HostListener('document:mouseup', []) @@ -310,25 +335,31 @@ export class StartComponent implements OnInit, OnDestroy, DoCheck { } else { this.velocity += dv; } - this.blockchainContainer.nativeElement.scrollLeft -= displacement; + this.scrollLeft -= displacement; + this.applyScrollLeft(); this.animateMomentum(); } }); } onScroll(e) { + if (this.blockchainContainer?.nativeElement?.scrollLeft == null) { + return; + } + this.scrollLeft = this.blockchainContainer?.nativeElement?.scrollLeft; const middlePage = this.pageIndex === 0 ? this.pages[0] : this.pages[1]; // compensate for css transform const translation = (this.isMobile ? this.chainWidth * 0.95 : this.chainWidth * 0.5); const backThreshold = middlePage.offset + (this.pageWidth * 0.5) + translation; const forwardThreshold = middlePage.offset - (this.pageWidth * 0.5) + translation; - const scrollLeft = this.getConvertedScrollOffset(); - if (scrollLeft > backThreshold) { + this.scrollLeft = this.blockchainContainer.nativeElement.scrollLeft; + const offsetScroll = this.getConvertedScrollOffset(this.scrollLeft); + if (offsetScroll > backThreshold) { if (this.shiftPagesBack()) { this.addConvertedScrollOffset(-this.pageWidth); this.blockchainScrollLeftInit -= this.pageWidth; } - } else if (scrollLeft < forwardThreshold) { + } else if (offsetScroll < forwardThreshold) { if (this.shiftPagesForward()) { this.addConvertedScrollOffset(this.pageWidth); this.blockchainScrollLeftInit += this.pageWidth; @@ -337,10 +368,6 @@ export class StartComponent implements OnInit, OnDestroy, DoCheck { } scrollToBlock(height, blockOffset = 0) { - if (!this.blockchainContainer?.nativeElement) { - setTimeout(() => { this.scrollToBlock(height, blockOffset); }, 50); - return; - } if (this.isMobile) { blockOffset -= this.blockWidth; } @@ -348,15 +375,15 @@ export class StartComponent implements OnInit, OnDestroy, DoCheck { const pages = []; this.pageIndex = Math.max(viewingPageIndex - 1, 0); let viewingPage = this.getPageAt(viewingPageIndex); - const isLastPage = viewingPage.height < this.blocksPerPage; + const isLastPage = viewingPage.height <= 0; if (isLastPage) { this.pageIndex = Math.max(viewingPageIndex - 2, 0); viewingPage = this.getPageAt(viewingPageIndex); } - const left = viewingPage.offset - this.getConvertedScrollOffset(); + const left = viewingPage.offset - this.getConvertedScrollOffset(this.scrollLeft); const blockIndex = viewingPage.height - height; const targetOffset = (this.blockWidth * blockIndex) + left; - let deltaOffset = targetOffset - blockOffset; + const deltaOffset = targetOffset - blockOffset; if (isLastPage) { pages.push(this.getPageAt(viewingPageIndex - 2)); @@ -386,6 +413,7 @@ export class StartComponent implements OnInit, OnDestroy, DoCheck { pages.push(this.getPageAt(this.pageIndex + 1)); pages.push(this.getPageAt(this.pageIndex + 2)); this.pages = pages; + this.cd.markForCheck(); } shiftPagesBack(): boolean { @@ -439,44 +467,40 @@ export class StartComponent implements OnInit, OnDestroy, DoCheck { blockInViewport(height: number): boolean { const firstHeight = this.pages[0].height; const translation = (this.isMobile ? this.chainWidth * 0.95 : this.chainWidth * 0.5); - const firstX = this.pages[0].offset - this.getConvertedScrollOffset() + translation; + const firstX = this.pages[0].offset - this.getConvertedScrollOffset(this.scrollLeft) + translation; const xPos = firstX + ((firstHeight - height) * 155); return xPos > -55 && xPos < (this.chainWidth - 100); } - getConvertedScrollOffset(): number { + getConvertedScrollOffset(scrollLeft): number { if (this.timeLtr) { - return -(this.blockchainContainer?.nativeElement?.scrollLeft || 0) - this.mempoolOffset; + return -(scrollLeft || 0) - (this.mempoolOffset || 0); } else { - return (this.blockchainContainer?.nativeElement?.scrollLeft || 0) - this.mempoolOffset; + return (scrollLeft || 0) - (this.mempoolOffset || 0); } } setScrollLeft(offset: number): void { if (this.timeLtr) { - this.blockchainContainer.nativeElement.scrollLeft = offset - this.mempoolOffset; + this.scrollLeft = offset - (this.mempoolOffset || 0); } else { - this.blockchainContainer.nativeElement.scrollLeft = offset + this.mempoolOffset; + this.scrollLeft = offset + (this.mempoolOffset || 0); } + this.applyScrollLeft(); } addConvertedScrollOffset(offset: number): void { - if (!this.blockchainContainer?.nativeElement) { - this.pendingOffset = offset; - return; - } if (this.timeLtr) { - this.blockchainContainer.nativeElement.scrollLeft -= offset; + this.scrollLeft -= offset; } else { - this.blockchainContainer.nativeElement.scrollLeft += offset; + this.scrollLeft += offset; } + this.applyScrollLeft(); } ngOnDestroy() { - if (this.blockchainContainer?.nativeElement) { - // clean up scroll position to prevent caching wrong scroll in Firefox - this.setScrollLeft(0); - } + // clean up scroll position to prevent caching wrong scroll in Firefox + this.setScrollLeft(0); this.timeLtrSubscription.unsubscribe(); this.chainTipSubscription.unsubscribe(); this.markBlockSubscription.unsubscribe(); From 1a6a0c12ec84f8e3ee8751c4f576127ddb028e4b Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 20 Sep 2023 03:07:35 +0000 Subject: [PATCH 13/14] Fix blockchain scroll jumping --- .../app/components/block/block.component.ts | 2 +- .../blockchain/blockchain.component.html | 2 +- .../blockchain/blockchain.component.scss | 10 +----- .../blockchain/blockchain.component.ts | 36 +++++++++++++++---- .../mempool-blocks.component.ts | 10 +++++- .../app/components/start/start.component.ts | 20 ++++++----- 6 files changed, 53 insertions(+), 27 deletions(-) diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts index e226807e3..cbe56ca15 100644 --- a/frontend/src/app/components/block/block.component.ts +++ b/frontend/src/app/components/block/block.component.ts @@ -166,7 +166,6 @@ export class BlockComponent implements OnInit, OnDestroy { this.page = 1; this.error = undefined; this.fees = undefined; - this.stateService.markBlock$.next({}); if (history.state.data && history.state.data.blockHeight) { this.blockHeight = history.state.data.blockHeight; @@ -176,6 +175,7 @@ export class BlockComponent implements OnInit, OnDestroy { let isBlockHeight = false; if (/^[0-9]+$/.test(blockHash)) { isBlockHeight = true; + this.stateService.markBlock$.next({ blockHeight: parseInt(blockHash, 10)}); } else { this.blockHash = blockHash; } diff --git a/frontend/src/app/components/blockchain/blockchain.component.html b/frontend/src/app/components/blockchain/blockchain.component.html index 2c3f1ad8c..5f625e4b3 100644 --- a/frontend/src/app/components/blockchain/blockchain.component.html +++ b/frontend/src/app/components/blockchain/blockchain.component.html @@ -1,5 +1,5 @@
-
+
diff --git a/frontend/src/app/components/blockchain/blockchain.component.scss b/frontend/src/app/components/blockchain/blockchain.component.scss index 135a8b842..eacd16118 100644 --- a/frontend/src/app/components/blockchain/blockchain.component.scss +++ b/frontend/src/app/components/blockchain/blockchain.component.scss @@ -26,15 +26,7 @@ position: absolute; left: 0; top: 75px; - --divider-offset: 50vw; - --mempool-offset: 0px; - transform: translateX(calc(var(--divider-offset) + var(--mempool-offset))); -} - -.blockchain-wrapper.time-ltr { - .position-container { - transform: translateX(calc(100vw - var(--divider-offset) - var(--mempool-offset))); - } + transform: translateX(1280px); } .black-background { diff --git a/frontend/src/app/components/blockchain/blockchain.component.ts b/frontend/src/app/components/blockchain/blockchain.component.ts index f8f2966dd..2293b9479 100644 --- a/frontend/src/app/components/blockchain/blockchain.component.ts +++ b/frontend/src/app/components/blockchain/blockchain.component.ts @@ -27,8 +27,11 @@ export class BlockchainComponent implements OnInit, OnDestroy, OnChanges { loadingTip: boolean = true; connected: boolean = true; - dividerOffset: number = 0; - mempoolOffset: number = 0; + dividerOffset: number | null = null; + mempoolOffset: number | null = null; + positionStyle = { + transform: "translateX(1280px)", + }; constructor( public stateService: StateService, @@ -40,6 +43,7 @@ export class BlockchainComponent implements OnInit, OnDestroy, OnChanges { this.network = this.stateService.network; this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => { this.timeLtr = !!ltr; + this.updateStyle(); }); this.connectionStateSubscription = this.stateService.connectionState$.subscribe(state => { this.connected = (state === 2); @@ -63,29 +67,47 @@ export class BlockchainComponent implements OnInit, OnDestroy, OnChanges { const prevOffset = this.mempoolOffset; this.mempoolOffset = 0; this.mempoolOffsetChange.emit(0); + this.updateStyle(); setTimeout(() => { this.ltrTransitionEnabled = true; this.flipping = true; this.stateService.timeLtr.next(!this.timeLtr); + this.cd.markForCheck(); setTimeout(() => { this.ltrTransitionEnabled = false; this.flipping = false; this.mempoolOffset = prevOffset; - this.mempoolOffsetChange.emit(this.mempoolOffset); + this.mempoolOffsetChange.emit((this.mempoolOffset || 0)); + this.updateStyle(); + this.cd.markForCheck(); }, 1000); }, 0); - this.cd.markForCheck(); } onMempoolWidthChange(width): void { if (this.flipping) { return; } - this.mempoolOffset = Math.max(0, width - this.dividerOffset); - this.cd.markForCheck(); + this.mempoolOffset = Math.max(0, width - (this.dividerOffset || 0)); + this.updateStyle(); this.mempoolOffsetChange.emit(this.mempoolOffset); } + updateStyle(): void { + if (this.dividerOffset == null || this.mempoolOffset == null) { + return; + } + const oldTransform = this.positionStyle.transform; + this.positionStyle = this.timeLtr ? { + transform: `translateX(calc(100vw - ${this.dividerOffset + this.mempoolOffset}px)`, + } : { + transform: `translateX(${this.dividerOffset + this.mempoolOffset}px)`, + }; + if (oldTransform !== this.positionStyle.transform) { + this.cd.detectChanges(); + } + } + ngOnChanges(changes: SimpleChanges): void { if (changes.containerWidth) { this.onResize(); @@ -107,6 +129,6 @@ export class BlockchainComponent implements OnInit, OnDestroy, OnChanges { this.dividerOffset = width * 0.95; } } - this.cd.markForCheck(); + this.updateStyle(); } } 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 484389cd3..0ddbbd4b7 100644 --- a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts +++ b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts @@ -97,6 +97,10 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { ngOnInit() { this.chainTip = this.stateService.latestBlockHeight; + const width = this.containerOffset + (this.stateService.env.MEMPOOL_BLOCKS_AMOUNT) * this.blockOffset; + this.mempoolWidth = width; + this.widthChange.emit(this.mempoolWidth); + if (['', 'testnet', 'signet'].includes(this.stateService.network)) { this.enabledMiningInfoIfNeeded(this.location.path()); this.location.onUrlChange((url) => this.enabledMiningInfoIfNeeded(url)); @@ -161,11 +165,11 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { return this.mempoolBlocks; }), tap(() => { - this.cd.markForCheck(); const width = this.containerOffset + this.mempoolBlocks.length * this.blockOffset; if (this.mempoolWidth !== width) { this.mempoolWidth = width; this.widthChange.emit(this.mempoolWidth); + this.cd.markForCheck(); } }) ); @@ -215,11 +219,13 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { if (isNewBlock && (block?.extras?.similarity == null || block?.extras?.similarity > 0.5) && !this.tabHidden) { this.blockIndex++; } + this.cd.markForCheck(); }); this.chainTipSubscription = this.stateService.chainTip$.subscribe((height) => { if (this.chainTip === -1) { this.chainTip = height; + this.cd.markForCheck(); } }); @@ -257,6 +263,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { this.blockPadding = 0.24 * this.blockWidth; this.containerOffset = 0.32 * this.blockWidth; this.blockOffset = this.blockWidth + this.blockPadding; + this.cd.markForCheck(); } } @@ -275,6 +282,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { onResize(): void { this.animateEntry = false; this.reduceEmptyBlocksToFitScreen(this.mempoolEmptyBlocks); + this.cd.markForCheck(); } trackByFn(index: number, block: MempoolBlock) { diff --git a/frontend/src/app/components/start/start.component.ts b/frontend/src/app/components/start/start.component.ts index 86748bc8c..4a15157f0 100644 --- a/frontend/src/app/components/start/start.component.ts +++ b/frontend/src/app/components/start/start.component.ts @@ -24,7 +24,7 @@ export class StartComponent implements OnInit, AfterViewChecked, OnDestroy { timeLtrSubscription: Subscription; timeLtr: boolean = this.stateService.timeLtr.value; chainTipSubscription: Subscription; - chainTip: number = -1; + chainTip: number = 100; tipIsSet: boolean = false; lastMark: MarkBlockState; markBlockSubscription: Subscription; @@ -42,7 +42,7 @@ export class StartComponent implements OnInit, AfterViewChecked, OnDestroy { blocksPerPage: number = 1; pageWidth: number; firstPageWidth: number; - minScrollWidth: number; + minScrollWidth: number = 40 + (155 * (8 + (2 * Math.ceil(window.innerWidth / 155)))); currentScrollWidth: number = null; pageIndex: number = 0; pages: any[] = []; @@ -51,7 +51,7 @@ export class StartComponent implements OnInit, AfterViewChecked, OnDestroy { lastUpdate: number = 0; lastMouseX: number; velocity: number = 0; - mempoolOffset: number | null = null; + mempoolOffset: number = null; mempoolWidth: number = 0; scrollLeft: number = null; @@ -67,12 +67,13 @@ export class StartComponent implements OnInit, AfterViewChecked, OnDestroy { this.isiOS = ['iPhone','iPod','iPad'].includes((navigator as any)?.userAgentData?.platform || navigator.platform); } - ngOnInit(): void { + ngOnInit() { this.firstPageWidth = 40 + (this.blockWidth * this.dynamicBlocksAmount); this.blockCounterSubscription = this.stateService.blocks$.subscribe((blocks) => { this.blockCount = blocks.length; this.dynamicBlocksAmount = Math.min(this.blockCount, this.stateService.env.KEEP_BLOCKS_AMOUNT, 8); this.firstPageWidth = 40 + (this.blockWidth * this.dynamicBlocksAmount); + this.minScrollWidth = 40 + (8 * this.blockWidth) + (this.pageWidth * 2); if (this.blockCount <= Math.min(8, this.stateService.env.KEEP_BLOCKS_AMOUNT)) { this.onResize(); } @@ -181,7 +182,9 @@ export class StartComponent implements OnInit, AfterViewChecked, OnDestroy { } onMempoolOffsetChange(offset): void { - this.pendingOffset = offset; + if (offset !== this.mempoolOffset) { + this.pendingOffset = offset; + } } applyScrollLeft(): void { @@ -198,11 +201,11 @@ export class StartComponent implements OnInit, AfterViewChecked, OnDestroy { } this.blockchainContainer.nativeElement.scrollLeft = this.scrollLeft; } - this.cd.markForCheck(); + this.cd.detectChanges(); } applyPendingMarkArrow(): void { - if (this.pendingMark != null) { + if (this.pendingMark != null && this.pendingMark <= this.chainTip) { if (this.pendingMark < 0) { this.scrollToBlock(this.chainTip - this.pendingMark); } else { @@ -239,13 +242,14 @@ export class StartComponent implements OnInit, AfterViewChecked, OnDestroy { this.blocksPerPage = Math.ceil(this.chainWidth / this.blockWidth); this.pageWidth = this.blocksPerPage * this.blockWidth; - this.minScrollWidth = this.firstPageWidth + (this.pageWidth * 2); + this.minScrollWidth = 40 + (8 * this.blockWidth) + (this.pageWidth * 2); if (firstVisibleBlock != null) { this.scrollToBlock(firstVisibleBlock, offset); } else { this.updatePages(); } + this.cd.markForCheck(); } onMouseDown(event: MouseEvent) { From 368ab1dc668c52b0c0e1ca1348a0ec1312326262 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 22 Aug 2023 02:54:23 +0900 Subject: [PATCH 14/14] Update canonical link with block hash --- .../src/app/bisq/bisq-block/bisq-block.component.ts | 1 + frontend/src/app/components/block/block.component.ts | 1 + frontend/src/app/services/seo.service.ts | 12 ++++++++++++ 3 files changed, 14 insertions(+) diff --git a/frontend/src/app/bisq/bisq-block/bisq-block.component.ts b/frontend/src/app/bisq/bisq-block/bisq-block.component.ts index 42f62fbc0..59bb16a9e 100644 --- a/frontend/src/app/bisq/bisq-block/bisq-block.component.ts +++ b/frontend/src/app/bisq/bisq-block/bisq-block.component.ts @@ -69,6 +69,7 @@ export class BisqBlockComponent implements OnInit, OnDestroy { this.location.replaceState( this.router.createUrlTree(['/bisq/block/', hash]).toString() ); + this.seoService.updateCanonical(this.location.path()); return this.bisqApiService.getBlock$(this.blockHash) .pipe(catchError(this.caughtHttpError.bind(this))); }), diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts index cbe56ca15..bb83494c5 100644 --- a/frontend/src/app/components/block/block.component.ts +++ b/frontend/src/app/components/block/block.component.ts @@ -202,6 +202,7 @@ export class BlockComponent implements OnInit, OnDestroy { this.location.replaceState( this.router.createUrlTree([(this.network ? '/' + this.network : '') + '/block/', hash]).toString() ); + this.seoService.updateCanonical(this.location.path()); return this.apiService.getBlock$(hash).pipe( catchError((err) => { this.error = err; diff --git a/frontend/src/app/services/seo.service.ts b/frontend/src/app/services/seo.service.ts index 2584cbaed..3d095e1c3 100644 --- a/frontend/src/app/services/seo.service.ts +++ b/frontend/src/app/services/seo.service.ts @@ -12,6 +12,8 @@ export class SeoService { baseTitle = 'mempool'; baseDescription = 'Explore the full Bitcoin ecosystem with The Mempool Open Project™.'; + canonicalLink: HTMLElement = document.getElementById('canonical'); + constructor( private titleService: Title, private metaService: Meta, @@ -65,6 +67,16 @@ export class SeoService { this.metaService.updateTag({ property: 'og:description', content: this.getDescription()}); } + updateCanonical(path) { + let domain = 'mempool.space'; + if (this.stateService.env.BASE_MODULE === 'liquid') { + domain = 'liquid.network'; + } else if (this.stateService.env.BASE_MODULE === 'bisq') { + domain = 'bisq.markets'; + } + this.canonicalLink.setAttribute('href', 'https://' + domain + path); + } + getTitle(): string { if (this.network === 'testnet') return this.baseTitle + ' - Bitcoin Testnet';