From f63f1b177354d48dcf5bbccf6a7c9052e008b0d2 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 3 Mar 2024 20:29:54 +0000 Subject: [PATCH 1/5] Websocket subscription for fallback server health status --- .../bitcoin/bitcoin-api-abstract-factory.ts | 12 ++++++++++++ backend/src/api/bitcoin/bitcoin-api.ts | 6 +++++- backend/src/api/bitcoin/esplora-api.ts | 19 ++++++++++++++++--- backend/src/api/websocket-handler.ts | 16 +++++++++++++++- 4 files changed, 48 insertions(+), 5 deletions(-) diff --git a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts index 02640efc0..abd4c47a5 100644 --- a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts +++ b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts @@ -29,6 +29,7 @@ export interface AbstractBitcoinApi { $getOutSpendsByOutpoint(outpoints: { txid: string, vout: number }[]): Promise; startHealthChecks(): void; + getHealthStatus(): HealthCheckHost[]; } export interface BitcoinRpcCredentials { host: string; @@ -38,3 +39,14 @@ export interface BitcoinRpcCredentials { timeout: number; cookie?: string; } + +export interface HealthCheckHost { + host: string; + active: boolean; + rtt: number; + latestHeight: number; + socket: boolean; + outOfSync: boolean; + unreachable: boolean; + checked: boolean; +} diff --git a/backend/src/api/bitcoin/bitcoin-api.ts b/backend/src/api/bitcoin/bitcoin-api.ts index f54c836f8..d19eb06ac 100644 --- a/backend/src/api/bitcoin/bitcoin-api.ts +++ b/backend/src/api/bitcoin/bitcoin-api.ts @@ -1,5 +1,5 @@ import * as bitcoinjs from 'bitcoinjs-lib'; -import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory'; +import { AbstractBitcoinApi, HealthCheckHost } from './bitcoin-api-abstract-factory'; import { IBitcoinApi } from './bitcoin-api.interface'; import { IEsploraApi } from './esplora-api.interface'; import blocks from '../blocks'; @@ -382,6 +382,10 @@ class BitcoinApi implements AbstractBitcoinApi { } public startHealthChecks(): void {}; + + public getHealthStatus() { + return []; + } } export default BitcoinApi; diff --git a/backend/src/api/bitcoin/esplora-api.ts b/backend/src/api/bitcoin/esplora-api.ts index 2f4bcee85..209a6ceec 100644 --- a/backend/src/api/bitcoin/esplora-api.ts +++ b/backend/src/api/bitcoin/esplora-api.ts @@ -1,7 +1,7 @@ import config from '../../config'; -import axios, { AxiosResponse } from 'axios'; +import axios from 'axios'; import http from 'http'; -import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory'; +import { AbstractBitcoinApi, HealthCheckHost } from './bitcoin-api-abstract-factory'; import { IEsploraApi } from './esplora-api.interface'; import logger from '../../logger'; import { Common } from '../common'; @@ -157,7 +157,7 @@ class FailoverRouter { } // sort hosts by connection quality, and update default fallback - private sortHosts(): FailoverHost[] { + public sortHosts(): FailoverHost[] { // sort by connection quality return this.hosts.slice().sort((a, b) => { if ((a.unreachable || a.outOfSync) === (b.unreachable || b.outOfSync)) { @@ -342,6 +342,19 @@ class ElectrsApi implements AbstractBitcoinApi { public startHealthChecks(): void { this.failoverRouter.startHealthChecks(); } + + public getHealthStatus(): HealthCheckHost[] { + return this.failoverRouter.sortHosts().map(host => ({ + host: host.host, + active: host === this.failoverRouter.activeHost, + rtt: host.rtt, + latestHeight: host.latestHeight || 0, + socket: !!host.socket, + outOfSync: !!host.outOfSync, + unreachable: !!host.unreachable, + checked: !!host.checked, + })); + } } export default ElectrsApi; diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 5c007fadd..b2507122f 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -26,6 +26,7 @@ import mempool from './mempool'; import statistics from './statistics/statistics'; import accelerationCosts from './acceleration'; import accelerationRepository from '../repositories/AccelerationRepository'; +import bitcoinApi from './bitcoin/bitcoin-api-factory'; interface AddressTransactions { mempool: MempoolTransactionExtended[], @@ -39,6 +40,7 @@ const wantable = [ 'mempool-blocks', 'live-2h-chart', 'stats', + 'tomahawk', ]; class WebsocketHandler { @@ -123,7 +125,7 @@ class WebsocketHandler { for (const sub of wantable) { const key = `want-${sub}`; const wants = parsedMessage.data.includes(sub); - if (wants && client['wants'] && !client[key]) { + if (wants && !client[key]) { wantNow[key] = true; } client[key] = wants; @@ -147,6 +149,10 @@ class WebsocketHandler { response['da'] = this.socketData['da']; } + if (wantNow['want-tomahawk'] && config.MEMPOOL.BACKEND === 'esplora' && config.ESPLORA.FALLBACK?.length) { + response['tomahawk'] = JSON.stringify(bitcoinApi.getHealthStatus()); + } + if (parsedMessage && parsedMessage['track-tx']) { if (/^[a-fA-F0-9]{64}$/.test(parsedMessage['track-tx'])) { client['track-tx'] = parsedMessage['track-tx']; @@ -546,6 +552,10 @@ class WebsocketHandler { response['mempool-blocks'] = getCachedResponse('mempool-blocks', mBlocks); } + if (client['want-tomahawk'] && config.MEMPOOL.BACKEND === 'esplora' && config.ESPLORA.FALLBACK?.length) { + response['tomahawk'] = getCachedResponse('tomahawk', bitcoinApi.getHealthStatus()); + } + if (client['track-mempool-tx']) { const tx = newTransactions.find((t) => t.txid === client['track-mempool-tx']); if (tx) { @@ -909,6 +919,10 @@ class WebsocketHandler { response['mempool-blocks'] = getCachedResponse('mempool-blocks', mBlocks); } + if (client['want-tomahawk'] && config.MEMPOOL.BACKEND === 'esplora' && config.ESPLORA.FALLBACK?.length) { + response['tomahawk'] = getCachedResponse('tomahawk', bitcoinApi.getHealthStatus()); + } + if (client['track-tx']) { const trackTxid = client['track-tx']; if (trackTxid && confirmedTxids[trackTxid]) { From 5898143f66bf3dd99d176815995e8317389c3656 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 3 Mar 2024 20:31:02 +0000 Subject: [PATCH 2/5] Electrs server status page --- frontend/src/app/app-routing.module.ts | 26 +++++++ .../server-health.component.html | 29 ++++++++ .../server-health.component.scss | 35 ++++++++++ .../server-health/server-health.component.ts | 68 +++++++++++++++++++ .../src/app/interfaces/websocket.interface.ts | 14 ++++ frontend/src/app/services/state.service.ts | 4 +- .../src/app/services/websocket.service.ts | 4 ++ frontend/src/app/shared/shared.module.ts | 3 + 8 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 frontend/src/app/components/server-health/server-health.component.html create mode 100644 frontend/src/app/components/server-health/server-health.component.scss create mode 100644 frontend/src/app/components/server-health/server-health.component.ts diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index e123a1525..18949876e 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -6,6 +6,7 @@ import { EightBlocksComponent } from './components/eight-blocks/eight-blocks.com import { MempoolBlockViewComponent } from './components/mempool-block-view/mempool-block-view.component'; import { ClockComponent } from './components/clock/clock.component'; import { StatusViewComponent } from './components/status-view/status-view.component'; +import { ServerHealthComponent } from './components/server-health/server-health.component'; const browserWindow = window || {}; // @ts-ignore @@ -31,6 +32,11 @@ let routes: Routes = [ data: { networks: ['bitcoin', 'liquid'] }, component: StatusViewComponent }, + { + path: 'nodes', + data: { networks: ['bitcoin', 'liquid'] }, + component: ServerHealthComponent + }, { path: '', loadChildren: () => import('./bitcoin-graphs.module').then(m => m.BitcoinGraphsModule), @@ -66,6 +72,11 @@ let routes: Routes = [ data: { networks: ['bitcoin', 'liquid'] }, component: StatusViewComponent }, + { + path: 'nodes', + data: { networks: ['bitcoin', 'liquid'] }, + component: ServerHealthComponent + }, { path: '', loadChildren: () => import('./bitcoin-graphs.module').then(m => m.BitcoinGraphsModule), @@ -134,6 +145,11 @@ let routes: Routes = [ data: { networks: ['bitcoin', 'liquid'] }, component: StatusViewComponent }, + { + path: 'nodes', + data: { networks: ['bitcoin', 'liquid'] }, + component: ServerHealthComponent + }, { path: '', loadChildren: () => import('./bitcoin-graphs.module').then(m => m.BitcoinGraphsModule), @@ -173,6 +189,11 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { data: { networks: ['bitcoin', 'liquid'] }, component: StatusViewComponent }, + { + path: 'nodes', + data: { networks: ['bitcoin', 'liquid'] }, + component: ServerHealthComponent + }, { path: '', loadChildren: () => import('./liquid/liquid-graphs.module').then(m => m.LiquidGraphsModule), @@ -213,6 +234,11 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { data: { networks: ['bitcoin', 'liquid']}, component: StatusViewComponent }, + { + path: 'nodes', + data: { networks: ['bitcoin', 'liquid'] }, + component: ServerHealthComponent + }, { path: '', loadChildren: () => import('./liquid/liquid-graphs.module').then(m => m.LiquidGraphsModule), diff --git a/frontend/src/app/components/server-health/server-health.component.html b/frontend/src/app/components/server-health/server-health.component.html new file mode 100644 index 000000000..dd8d61444 --- /dev/null +++ b/frontend/src/app/components/server-health/server-health.component.html @@ -0,0 +1,29 @@ +
+ +
+ + + + + + + + + + + + + + + +
HostRTTHeight
⭐️{{ host.host }}{{ host.rtt | number : '1.0-0' }} {{ host.rtt == null ? '' : 'ms'}} {{ !host.checked ? '⏳' : (host.unreachable ? '🔥' : '✅') }}{{ host.latestHeight }} {{ !host.checked ? '⏳' : (host.outOfSync ? '🚫' : (host.latestHeight && host.latestHeight < tip ? '🟧' : '✅')) }}
+
+
+ + + + + +
diff --git a/frontend/src/app/components/server-health/server-health.component.scss b/frontend/src/app/components/server-health/server-health.component.scss new file mode 100644 index 000000000..e403e5824 --- /dev/null +++ b/frontend/src/app/components/server-health/server-health.component.scss @@ -0,0 +1,35 @@ +.tomahawk { + .status-panel { + max-width: 720px; + margin: auto; + margin-top: 2em; + padding: 1em; + background: #24273e; + } + + .status-table { + width: 100%; + + td, th { + padding: 0.25em; + &.rtt, &.height { + text-align: right; + } + } + + td { + cursor: pointer; + } + } + + .mempoolStatus { + width: 100%; + height: 270px; + } + + .hostLink { + text-align: center; + margin: auto; + margin-top: 1em; + } +} \ No newline at end of file diff --git a/frontend/src/app/components/server-health/server-health.component.ts b/frontend/src/app/components/server-health/server-health.component.ts new file mode 100644 index 000000000..bd7cc57e8 --- /dev/null +++ b/frontend/src/app/components/server-health/server-health.component.ts @@ -0,0 +1,68 @@ +import { Component, OnInit, ChangeDetectionStrategy, SecurityContext, OnDestroy } from '@angular/core'; +import { WebsocketService } from '../../services/websocket.service'; +import { Observable, Subject, map, tap } from 'rxjs'; +import { StateService } from '../../services/state.service'; +import { HealthCheckHost } from '../../interfaces/websocket.interface'; +import { DomSanitizer } from '@angular/platform-browser'; + +@Component({ + selector: 'app-server-health', + templateUrl: './server-health.component.html', + styleUrls: ['./server-health.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ServerHealthComponent implements OnInit, OnDestroy { + hosts$: Observable; + tip$: Subject; + hosts: HealthCheckHost[] = []; + + constructor( + private websocketService: WebsocketService, + private stateService: StateService, + public sanitizer: DomSanitizer, + ) {} + + ngOnInit(): void { + this.hosts$ = this.stateService.serverHealth$.pipe( + map((hosts) => { + const subpath = window.location.pathname.slice(0, -6); + for (const host of hosts) { + let statusUrl = ''; + let linkHost = ''; + if (host.socket) { + statusUrl = window.location.host + subpath + '/status'; + linkHost = window.location.host + subpath; + } else { + statusUrl = host.host.slice(0, -4) + subpath + '/status'; + linkHost = host.host.slice(0, -4) + subpath; + } + host.statusPage = this.sanitizer.bypassSecurityTrustResourceUrl(this.sanitizer.sanitize(SecurityContext.URL, statusUrl)); + host.link = linkHost; + } + return hosts; + }), + tap((hosts) => { + if (this.hosts.length !== hosts.length) { + this.hosts = hosts; + } + }) + ); + this.tip$ = this.stateService.chainTip$; + this.websocketService.want(['blocks', 'tomahawk']); + } + + scrollTo(host: HealthCheckHost): void { + const el = document.getElementById(host.host); + if (el) { + el.scrollIntoView(); + } + } + + trackByFn(index: number, host: HealthCheckHost): string { + return host.host; + } + + ngOnDestroy(): void { + this.hosts = []; + } +} diff --git a/frontend/src/app/interfaces/websocket.interface.ts b/frontend/src/app/interfaces/websocket.interface.ts index ff5977332..72fd8c419 100644 --- a/frontend/src/app/interfaces/websocket.interface.ts +++ b/frontend/src/app/interfaces/websocket.interface.ts @@ -1,3 +1,4 @@ +import { SafeResourceUrl } from '@angular/platform-browser'; import { ILoadingIndicators } from '../services/state.service'; import { Transaction } from './electrs.interface'; import { BlockExtended, DifficultyAdjustment, RbfTree } from './node-api.interface'; @@ -120,4 +121,17 @@ export interface Recommendedfees { hourFee: number; minimumFee: number; economyFee: number; +} + +export interface HealthCheckHost { + host: string; + active: boolean; + rtt: number; + latestHeight: number; + socket: boolean; + outOfSync: boolean; + unreachable: boolean; + checked: boolean; + link?: string; + statusPage?: SafeResourceUrl; } \ No newline at end of file diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index dc1365baa..83cd449c8 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -1,14 +1,13 @@ import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core'; import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable, merge } from 'rxjs'; import { Transaction } from '../interfaces/electrs.interface'; -import { IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, TransactionCompressed, TransactionStripped } from '../interfaces/websocket.interface'; +import { HealthCheckHost, IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, TransactionStripped } from '../interfaces/websocket.interface'; import { BlockExtended, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface'; import { Router, NavigationStart } from '@angular/router'; import { isPlatformBrowser } from '@angular/common'; import { filter, map, scan, shareReplay } from 'rxjs/operators'; import { StorageService } from './storage.service'; import { hasTouchScreen } from '../shared/pipes/bytes-pipe/utils'; -import { ApiService } from './api.service'; import { ActiveFilter } from '../shared/filters.utils'; export interface MarkBlockState { @@ -129,6 +128,7 @@ export class StateService { loadingIndicators$ = new ReplaySubject(1); recommendedFees$ = new ReplaySubject(1); chainTip$ = new ReplaySubject(-1); + serverHealth$ = new Subject(); live2Chart$ = new Subject(); diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts index 11e24ef71..f4dcc4037 100644 --- a/frontend/src/app/services/websocket.service.ts +++ b/frontend/src/app/services/websocket.service.ts @@ -415,6 +415,10 @@ export class WebsocketService { this.stateService.previousRetarget$.next(response.previousRetarget); } + if (response['tomahawk']) { + this.stateService.serverHealth$.next(response['tomahawk']); + } + if (response['git-commit']) { this.stateService.backendInfo$.next(response['git-commit']); } diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 36e7e79b8..6c43a019f 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -53,6 +53,7 @@ import { AssetComponent } from '../components/asset/asset.component'; import { AssetsComponent } from '../components/assets/assets.component'; import { AssetsNavComponent } from '../components/assets/assets-nav/assets-nav.component'; import { StatusViewComponent } from '../components/status-view/status-view.component'; +import { ServerHealthComponent } from '../components/server-health/server-health.component'; import { FeesBoxComponent } from '../components/fees-box/fees-box.component'; import { DifficultyComponent } from '../components/difficulty/difficulty.component'; import { DifficultyTooltipComponent } from '../components/difficulty/difficulty-tooltip.component'; @@ -151,6 +152,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir AssetComponent, AssetsComponent, StatusViewComponent, + ServerHealthComponent, FeesBoxComponent, DifficultyComponent, DifficultyMiningComponent, @@ -277,6 +279,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir AssetComponent, AssetsComponent, StatusViewComponent, + ServerHealthComponent, FeesBoxComponent, DifficultyComponent, DifficultyMiningComponent, From 1518b3ccfba5be7550baf46161d185f4d128111f Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 4 Mar 2024 18:29:26 +0000 Subject: [PATCH 3/5] Backend OFFICIAL config setting --- backend/mempool-config.sample.json | 1 + backend/src/__fixtures__/mempool-config.template.json | 1 + backend/src/__tests__/config.test.ts | 1 + backend/src/config.ts | 2 ++ docker/backend/mempool-config.json | 1 + docker/backend/start.sh | 2 ++ production/mempool-config.bisq.json | 1 + production/mempool-config.liquid.json | 1 + production/mempool-config.liquidtestnet.json | 1 + production/mempool-config.mainnet-lightning.json | 1 + production/mempool-config.mainnet.json | 1 + production/mempool-config.signet-lightning.json | 1 + production/mempool-config.signet.json | 1 + production/mempool-config.testnet-lightning.json | 1 + production/mempool-config.testnet.json | 1 + 15 files changed, 17 insertions(+) diff --git a/backend/mempool-config.sample.json b/backend/mempool-config.sample.json index 3c2fccfb7..5d2cf1fba 100644 --- a/backend/mempool-config.sample.json +++ b/backend/mempool-config.sample.json @@ -1,5 +1,6 @@ { "MEMPOOL": { + "OFFICIAL": false, "NETWORK": "mainnet", "BACKEND": "electrum", "ENABLED": true, diff --git a/backend/src/__fixtures__/mempool-config.template.json b/backend/src/__fixtures__/mempool-config.template.json index 9ee2bd0bc..acf628ce9 100644 --- a/backend/src/__fixtures__/mempool-config.template.json +++ b/backend/src/__fixtures__/mempool-config.template.json @@ -1,6 +1,7 @@ { "MEMPOOL": { "ENABLED": true, + "OFFICIAL": false, "NETWORK": "__MEMPOOL_NETWORK__", "BACKEND": "__MEMPOOL_BACKEND__", "BLOCKS_SUMMARIES_INDEXING": true, diff --git a/backend/src/__tests__/config.test.ts b/backend/src/__tests__/config.test.ts index 6af0ce32f..6adf0d98d 100644 --- a/backend/src/__tests__/config.test.ts +++ b/backend/src/__tests__/config.test.ts @@ -14,6 +14,7 @@ describe('Mempool Backend Config', () => { expect(config.MEMPOOL).toStrictEqual({ ENABLED: true, + OFFICIAL: false, NETWORK: 'mainnet', BACKEND: 'none', BLOCKS_SUMMARIES_INDEXING: false, diff --git a/backend/src/config.ts b/backend/src/config.ts index 32a7af3df..17699bb82 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -5,6 +5,7 @@ const configFromFile = require( interface IConfig { MEMPOOL: { ENABLED: boolean; + OFFICIAL: boolean; NETWORK: 'mainnet' | 'testnet' | 'signet' | 'liquid' | 'liquidtestnet'; BACKEND: 'esplora' | 'electrum' | 'none'; HTTP_PORT: number; @@ -161,6 +162,7 @@ interface IConfig { const defaults: IConfig = { 'MEMPOOL': { 'ENABLED': true, + 'OFFICIAL': false, 'NETWORK': 'mainnet', 'BACKEND': 'none', 'HTTP_PORT': 8999, diff --git a/docker/backend/mempool-config.json b/docker/backend/mempool-config.json index 8f69fd0c1..fb07db03d 100644 --- a/docker/backend/mempool-config.json +++ b/docker/backend/mempool-config.json @@ -3,6 +3,7 @@ "NETWORK": "__MEMPOOL_NETWORK__", "BACKEND": "__MEMPOOL_BACKEND__", "ENABLED": __MEMPOOL_ENABLED__, + "OFFICIAL": __MEMPOOL_OFFICIAL__, "HTTP_PORT": __MEMPOOL_HTTP_PORT__, "SPAWN_CLUSTER_PROCS": __MEMPOOL_SPAWN_CLUSTER_PROCS__, "API_URL_PREFIX": "__MEMPOOL_API_URL_PREFIX__", diff --git a/docker/backend/start.sh b/docker/backend/start.sh index ba9b99233..787cb0cb0 100755 --- a/docker/backend/start.sh +++ b/docker/backend/start.sh @@ -4,6 +4,7 @@ __MEMPOOL_NETWORK__=${MEMPOOL_NETWORK:=mainnet} __MEMPOOL_BACKEND__=${MEMPOOL_BACKEND:=electrum} __MEMPOOL_ENABLED__=${MEMPOOL_ENABLED:=true} +__MEMPOOL_OFFICIAL__=${MEMPOOL_OFFICIAL:=false} __MEMPOOL_HTTP_PORT__=${BACKEND_HTTP_PORT:=8999} __MEMPOOL_SPAWN_CLUSTER_PROCS__=${MEMPOOL_SPAWN_CLUSTER_PROCS:=0} __MEMPOOL_API_URL_PREFIX__=${MEMPOOL_API_URL_PREFIX:=/api/v1/} @@ -158,6 +159,7 @@ mkdir -p "${__MEMPOOL_CACHE_DIR__}" sed -i "s!__MEMPOOL_NETWORK__!${__MEMPOOL_NETWORK__}!g" mempool-config.json sed -i "s!__MEMPOOL_BACKEND__!${__MEMPOOL_BACKEND__}!g" mempool-config.json sed -i "s!__MEMPOOL_ENABLED__!${__MEMPOOL_ENABLED__}!g" mempool-config.json +sed -i "s!__MEMPOOL_OFFICIAL__!${__MEMPOOL_OFFICIAL__}!g" mempool-config.json sed -i "s!__MEMPOOL_HTTP_PORT__!${__MEMPOOL_HTTP_PORT__}!g" mempool-config.json sed -i "s!__MEMPOOL_SPAWN_CLUSTER_PROCS__!${__MEMPOOL_SPAWN_CLUSTER_PROCS__}!g" mempool-config.json sed -i "s!__MEMPOOL_API_URL_PREFIX__!${__MEMPOOL_API_URL_PREFIX__}!g" mempool-config.json diff --git a/production/mempool-config.bisq.json b/production/mempool-config.bisq.json index 26024f8a3..4913cb986 100644 --- a/production/mempool-config.bisq.json +++ b/production/mempool-config.bisq.json @@ -1,5 +1,6 @@ { "MEMPOOL": { + "OFFICIAL": true, "NETWORK": "bisq", "BACKEND": "esplora", "HTTP_PORT": 8996, diff --git a/production/mempool-config.liquid.json b/production/mempool-config.liquid.json index b852be3ae..9051bba74 100644 --- a/production/mempool-config.liquid.json +++ b/production/mempool-config.liquid.json @@ -1,5 +1,6 @@ { "MEMPOOL": { + "OFFICIAL": true, "NETWORK": "liquid", "BACKEND": "esplora", "HTTP_PORT": 8998, diff --git a/production/mempool-config.liquidtestnet.json b/production/mempool-config.liquidtestnet.json index 71c094dc6..ae6d7b1ac 100644 --- a/production/mempool-config.liquidtestnet.json +++ b/production/mempool-config.liquidtestnet.json @@ -1,5 +1,6 @@ { "MEMPOOL": { + "OFFICIAL": true, "NETWORK": "liquid", "BACKEND": "esplora", "HTTP_PORT": 8994, diff --git a/production/mempool-config.mainnet-lightning.json b/production/mempool-config.mainnet-lightning.json index bef04e7a6..8dea10b4a 100644 --- a/production/mempool-config.mainnet-lightning.json +++ b/production/mempool-config.mainnet-lightning.json @@ -1,5 +1,6 @@ { "MEMPOOL": { + "OFFICIAL": true, "ENABLED": false, "NETWORK": "mainnet", "BACKEND": "esplora", diff --git a/production/mempool-config.mainnet.json b/production/mempool-config.mainnet.json index 1bb9e35a5..47db0a322 100644 --- a/production/mempool-config.mainnet.json +++ b/production/mempool-config.mainnet.json @@ -1,5 +1,6 @@ { "MEMPOOL": { + "OFFICIAL": true, "NETWORK": "mainnet", "BACKEND": "esplora", "HTTP_PORT": 8999, diff --git a/production/mempool-config.signet-lightning.json b/production/mempool-config.signet-lightning.json index 9cffe55ca..f90b18f50 100644 --- a/production/mempool-config.signet-lightning.json +++ b/production/mempool-config.signet-lightning.json @@ -1,5 +1,6 @@ { "MEMPOOL": { + "OFFICIAL": true, "ENABLED": false, "NETWORK": "signet", "BACKEND": "esplora", diff --git a/production/mempool-config.signet.json b/production/mempool-config.signet.json index 71b52be5b..433e1e4a7 100644 --- a/production/mempool-config.signet.json +++ b/production/mempool-config.signet.json @@ -1,5 +1,6 @@ { "MEMPOOL": { + "OFFICIAL": true, "NETWORK": "signet", "BACKEND": "esplora", "HTTP_PORT": 8995, diff --git a/production/mempool-config.testnet-lightning.json b/production/mempool-config.testnet-lightning.json index f7bdebbeb..59a858cbf 100644 --- a/production/mempool-config.testnet-lightning.json +++ b/production/mempool-config.testnet-lightning.json @@ -1,5 +1,6 @@ { "MEMPOOL": { + "OFFICIAL": true, "ENABLED": false, "NETWORK": "testnet", "BACKEND": "esplora", diff --git a/production/mempool-config.testnet.json b/production/mempool-config.testnet.json index e35dfe78e..742631025 100644 --- a/production/mempool-config.testnet.json +++ b/production/mempool-config.testnet.json @@ -1,5 +1,6 @@ { "MEMPOOL": { + "OFFICIAL": true, "NETWORK": "testnet", "BACKEND": "esplora", "HTTP_PORT": 8997, From 5f3ca3a3212c8cf8c5d4a53c23ff57ea38962665 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 4 Mar 2024 18:29:50 +0000 Subject: [PATCH 4/5] Disable tomahawk status on non-official instances --- backend/src/api/bitcoin/esplora-api.ts | 24 ++++++++++++++---------- backend/src/api/websocket-handler.ts | 6 +++--- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/backend/src/api/bitcoin/esplora-api.ts b/backend/src/api/bitcoin/esplora-api.ts index 209a6ceec..c80a9e18f 100644 --- a/backend/src/api/bitcoin/esplora-api.ts +++ b/backend/src/api/bitcoin/esplora-api.ts @@ -344,16 +344,20 @@ class ElectrsApi implements AbstractBitcoinApi { } public getHealthStatus(): HealthCheckHost[] { - return this.failoverRouter.sortHosts().map(host => ({ - host: host.host, - active: host === this.failoverRouter.activeHost, - rtt: host.rtt, - latestHeight: host.latestHeight || 0, - socket: !!host.socket, - outOfSync: !!host.outOfSync, - unreachable: !!host.unreachable, - checked: !!host.checked, - })); + if (config.MEMPOOL.OFFICIAL) { + return this.failoverRouter.sortHosts().map(host => ({ + host: host.host, + active: host === this.failoverRouter.activeHost, + rtt: host.rtt, + latestHeight: host.latestHeight || 0, + socket: !!host.socket, + outOfSync: !!host.outOfSync, + unreachable: !!host.unreachable, + checked: !!host.checked, + })); + } else { + return []; + } } } diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index b2507122f..6711c88fb 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -149,7 +149,7 @@ class WebsocketHandler { response['da'] = this.socketData['da']; } - if (wantNow['want-tomahawk'] && config.MEMPOOL.BACKEND === 'esplora' && config.ESPLORA.FALLBACK?.length) { + if (wantNow['want-tomahawk']) { response['tomahawk'] = JSON.stringify(bitcoinApi.getHealthStatus()); } @@ -552,7 +552,7 @@ class WebsocketHandler { response['mempool-blocks'] = getCachedResponse('mempool-blocks', mBlocks); } - if (client['want-tomahawk'] && config.MEMPOOL.BACKEND === 'esplora' && config.ESPLORA.FALLBACK?.length) { + if (client['want-tomahawk']) { response['tomahawk'] = getCachedResponse('tomahawk', bitcoinApi.getHealthStatus()); } @@ -919,7 +919,7 @@ class WebsocketHandler { response['mempool-blocks'] = getCachedResponse('mempool-blocks', mBlocks); } - if (client['want-tomahawk'] && config.MEMPOOL.BACKEND === 'esplora' && config.ESPLORA.FALLBACK?.length) { + if (client['want-tomahawk']) { response['tomahawk'] = getCachedResponse('tomahawk', bitcoinApi.getHealthStatus()); } From 73e9c85ff1199163ecfc57f6244b32efbf2a6ede Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 4 Mar 2024 18:35:34 +0000 Subject: [PATCH 5/5] Separate tomahawk nodes & network pages, misc fixes --- frontend/src/app/app-routing.module.ts | 26 ------ .../server-health.component.html | 17 ++-- .../server-health.component.scss | 29 ++++--- .../server-health/server-health.component.ts | 32 ++------ .../server-status.component.html | 14 ++++ .../server-status.component.scss | 26 ++++++ .../server-health/server-status.component.ts | 80 +++++++++++++++++++ .../app/liquid/liquid-master-page.module.ts | 15 ++++ frontend/src/app/master-page.module.ts | 15 ++++ frontend/src/app/shared/shared.module.ts | 3 + 10 files changed, 181 insertions(+), 76 deletions(-) create mode 100644 frontend/src/app/components/server-health/server-status.component.html create mode 100644 frontend/src/app/components/server-health/server-status.component.scss create mode 100644 frontend/src/app/components/server-health/server-status.component.ts diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 18949876e..e123a1525 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -6,7 +6,6 @@ import { EightBlocksComponent } from './components/eight-blocks/eight-blocks.com import { MempoolBlockViewComponent } from './components/mempool-block-view/mempool-block-view.component'; import { ClockComponent } from './components/clock/clock.component'; import { StatusViewComponent } from './components/status-view/status-view.component'; -import { ServerHealthComponent } from './components/server-health/server-health.component'; const browserWindow = window || {}; // @ts-ignore @@ -32,11 +31,6 @@ let routes: Routes = [ data: { networks: ['bitcoin', 'liquid'] }, component: StatusViewComponent }, - { - path: 'nodes', - data: { networks: ['bitcoin', 'liquid'] }, - component: ServerHealthComponent - }, { path: '', loadChildren: () => import('./bitcoin-graphs.module').then(m => m.BitcoinGraphsModule), @@ -72,11 +66,6 @@ let routes: Routes = [ data: { networks: ['bitcoin', 'liquid'] }, component: StatusViewComponent }, - { - path: 'nodes', - data: { networks: ['bitcoin', 'liquid'] }, - component: ServerHealthComponent - }, { path: '', loadChildren: () => import('./bitcoin-graphs.module').then(m => m.BitcoinGraphsModule), @@ -145,11 +134,6 @@ let routes: Routes = [ data: { networks: ['bitcoin', 'liquid'] }, component: StatusViewComponent }, - { - path: 'nodes', - data: { networks: ['bitcoin', 'liquid'] }, - component: ServerHealthComponent - }, { path: '', loadChildren: () => import('./bitcoin-graphs.module').then(m => m.BitcoinGraphsModule), @@ -189,11 +173,6 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { data: { networks: ['bitcoin', 'liquid'] }, component: StatusViewComponent }, - { - path: 'nodes', - data: { networks: ['bitcoin', 'liquid'] }, - component: ServerHealthComponent - }, { path: '', loadChildren: () => import('./liquid/liquid-graphs.module').then(m => m.LiquidGraphsModule), @@ -234,11 +213,6 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { data: { networks: ['bitcoin', 'liquid']}, component: StatusViewComponent }, - { - path: 'nodes', - data: { networks: ['bitcoin', 'liquid'] }, - component: ServerHealthComponent - }, { path: '', loadChildren: () => import('./liquid/liquid-graphs.module').then(m => m.LiquidGraphsModule), diff --git a/frontend/src/app/components/server-health/server-health.component.html b/frontend/src/app/components/server-health/server-health.component.html index dd8d61444..f376b4314 100644 --- a/frontend/src/app/components/server-health/server-health.component.html +++ b/frontend/src/app/components/server-health/server-health.component.html @@ -1,4 +1,10 @@ -
+
+ +

Node Status

+
@@ -9,7 +15,7 @@ - + @@ -19,11 +25,4 @@
RTT Height
⭐️ {{ host.host }} {{ host.rtt | number : '1.0-0' }} {{ host.rtt == null ? '' : 'ms'}} {{ !host.checked ? '⏳' : (host.unreachable ? '🔥' : '✅') }}
- - - - -
diff --git a/frontend/src/app/components/server-health/server-health.component.scss b/frontend/src/app/components/server-health/server-health.component.scss index e403e5824..ebc6a883c 100644 --- a/frontend/src/app/components/server-health/server-health.component.scss +++ b/frontend/src/app/components/server-health/server-health.component.scss @@ -1,4 +1,18 @@ .tomahawk { + .links { + float: right; + text-align: right; + margin-top: 1em; + + a, span { + margin-left: 1em; + } + } + + .dashboard-title { + text-align: left; + } + .status-panel { max-width: 720px; margin: auto; @@ -16,20 +30,5 @@ text-align: right; } } - - td { - cursor: pointer; - } - } - - .mempoolStatus { - width: 100%; - height: 270px; - } - - .hostLink { - text-align: center; - margin: auto; - margin-top: 1em; } } \ No newline at end of file diff --git a/frontend/src/app/components/server-health/server-health.component.ts b/frontend/src/app/components/server-health/server-health.component.ts index bd7cc57e8..b1008a4a5 100644 --- a/frontend/src/app/components/server-health/server-health.component.ts +++ b/frontend/src/app/components/server-health/server-health.component.ts @@ -1,6 +1,6 @@ -import { Component, OnInit, ChangeDetectionStrategy, SecurityContext, OnDestroy } from '@angular/core'; +import { Component, OnInit, ChangeDetectionStrategy, SecurityContext } from '@angular/core'; import { WebsocketService } from '../../services/websocket.service'; -import { Observable, Subject, map, tap } from 'rxjs'; +import { Observable, Subject, map } from 'rxjs'; import { StateService } from '../../services/state.service'; import { HealthCheckHost } from '../../interfaces/websocket.interface'; import { DomSanitizer } from '@angular/platform-browser'; @@ -11,10 +11,9 @@ import { DomSanitizer } from '@angular/platform-browser'; styleUrls: ['./server-health.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ServerHealthComponent implements OnInit, OnDestroy { +export class ServerHealthComponent implements OnInit { hosts$: Observable; tip$: Subject; - hosts: HealthCheckHost[] = []; constructor( private websocketService: WebsocketService, @@ -33,36 +32,17 @@ export class ServerHealthComponent implements OnInit, OnDestroy { statusUrl = window.location.host + subpath + '/status'; linkHost = window.location.host + subpath; } else { - statusUrl = host.host.slice(0, -4) + subpath + '/status'; - linkHost = host.host.slice(0, -4) + subpath; + const hostUrl = new URL(host.host); + statusUrl = 'https://' + hostUrl.hostname + subpath + '/status'; + linkHost = hostUrl.hostname + subpath; } host.statusPage = this.sanitizer.bypassSecurityTrustResourceUrl(this.sanitizer.sanitize(SecurityContext.URL, statusUrl)); host.link = linkHost; } return hosts; - }), - tap((hosts) => { - if (this.hosts.length !== hosts.length) { - this.hosts = hosts; - } }) ); this.tip$ = this.stateService.chainTip$; this.websocketService.want(['blocks', 'tomahawk']); } - - scrollTo(host: HealthCheckHost): void { - const el = document.getElementById(host.host); - if (el) { - el.scrollIntoView(); - } - } - - trackByFn(index: number, host: HealthCheckHost): string { - return host.host; - } - - ngOnDestroy(): void { - this.hosts = []; - } } diff --git a/frontend/src/app/components/server-health/server-status.component.html b/frontend/src/app/components/server-health/server-status.component.html new file mode 100644 index 000000000..484d774a0 --- /dev/null +++ b/frontend/src/app/components/server-health/server-status.component.html @@ -0,0 +1,14 @@ +
+ +

Live Network

+ + + + + +
diff --git a/frontend/src/app/components/server-health/server-status.component.scss b/frontend/src/app/components/server-health/server-status.component.scss new file mode 100644 index 000000000..09bebe040 --- /dev/null +++ b/frontend/src/app/components/server-health/server-status.component.scss @@ -0,0 +1,26 @@ +.tomahawk { + .links { + float: right; + text-align: right; + margin-top: 1em; + + a, span { + margin-left: 1em; + } + } + + .dashboard-title { + text-align: left; + } + + .mempoolStatus { + width: 100%; + height: 270px; + } + + .hostLink { + text-align: center; + margin: auto; + margin-top: 1em; + } +} \ No newline at end of file diff --git a/frontend/src/app/components/server-health/server-status.component.ts b/frontend/src/app/components/server-health/server-status.component.ts new file mode 100644 index 000000000..8c893988d --- /dev/null +++ b/frontend/src/app/components/server-health/server-status.component.ts @@ -0,0 +1,80 @@ +import { Component, OnInit, ChangeDetectionStrategy, SecurityContext, OnDestroy, ChangeDetectorRef } from '@angular/core'; +import { WebsocketService } from '../../services/websocket.service'; +import { Observable, Subject, Subscription, map, tap } from 'rxjs'; +import { StateService } from '../../services/state.service'; +import { HealthCheckHost } from '../../interfaces/websocket.interface'; +import { DomSanitizer } from '@angular/platform-browser'; + +@Component({ + selector: 'app-server-status', + templateUrl: './server-status.component.html', + styleUrls: ['./server-status.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ServerStatusComponent implements OnInit, OnDestroy { + tip$: Subject; + hosts: HealthCheckHost[] = []; + hostSubscription: Subscription; + + constructor( + private websocketService: WebsocketService, + private stateService: StateService, + private cd: ChangeDetectorRef, + public sanitizer: DomSanitizer, + ) {} + + ngOnInit(): void { + this.hostSubscription = this.stateService.serverHealth$.pipe( + map((hosts) => { + const subpath = window.location.pathname.slice(0, -8); + for (const host of hosts) { + let statusUrl = ''; + let linkHost = ''; + if (host.socket) { + statusUrl = window.location.host + subpath + '/status'; + linkHost = window.location.host + subpath; + } else { + const hostUrl = new URL(host.host); + statusUrl = 'https://' + hostUrl.hostname + subpath + '/status'; + linkHost = hostUrl.hostname + subpath; + } + host.statusPage = this.sanitizer.bypassSecurityTrustResourceUrl(this.sanitizer.sanitize(SecurityContext.URL, statusUrl)); + host.link = linkHost; + } + return hosts; + }), + tap((hosts) => { + if (this.hosts.length !== hosts.length) { + this.hosts = hosts.sort((a,b) => { + const aParts = (a.host?.split('.') || []).reverse(); + const bParts = (b.host?.split('.') || []).reverse(); + let i = 0; + while (i < Math.max(aParts.length, bParts.length)) { + if (aParts[i] && !bParts[i]) { + return 1; + } else if (bParts[i] && !aParts[i]) { + return -1; + } else if (aParts[i] !== bParts[i]) { + return aParts[i].localeCompare(bParts[i]); + } + i++; + } + return 0; + }); + } + this.cd.markForCheck(); + }) + ).subscribe(); + this.tip$ = this.stateService.chainTip$; + this.websocketService.want(['blocks', 'tomahawk']); + } + + trackByFn(index: number, host: HealthCheckHost): string { + return host.host; + } + + ngOnDestroy(): void { + this.hosts = []; + this.hostSubscription.unsubscribe(); + } +} diff --git a/frontend/src/app/liquid/liquid-master-page.module.ts b/frontend/src/app/liquid/liquid-master-page.module.ts index 8988cb05c..4b8364ad5 100644 --- a/frontend/src/app/liquid/liquid-master-page.module.ts +++ b/frontend/src/app/liquid/liquid-master-page.module.ts @@ -19,6 +19,8 @@ import { RecentPegsListComponent } from '../components/liquid-reserves-audit/rec import { FederationWalletComponent } from '../components/liquid-reserves-audit/federation-wallet/federation-wallet.component'; import { FederationUtxosListComponent } from '../components/liquid-reserves-audit/federation-utxos-list/federation-utxos-list.component'; import { FederationAddressesListComponent } from '../components/liquid-reserves-audit/federation-addresses-list/federation-addresses-list.component'; +import { ServerHealthComponent } from '../components/server-health/server-health.component'; +import { ServerStatusComponent } from '../components/server-health/server-status.component'; const routes: Routes = [ { @@ -140,6 +142,19 @@ const routes: Routes = [ }, ]; +if (window['__env']?.OFFICIAL_MEMPOOL_SPACE) { + routes[0].children.push({ + path: 'nodes', + data: { networks: ['bitcoin', 'liquid'] }, + component: ServerHealthComponent + }); + routes[0].children.push({ + path: 'network', + data: { networks: ['bitcoin', 'liquid'] }, + component: ServerStatusComponent + }); +} + @NgModule({ imports: [ RouterModule.forChild(routes) diff --git a/frontend/src/app/master-page.module.ts b/frontend/src/app/master-page.module.ts index d7ec87030..ec3e08674 100644 --- a/frontend/src/app/master-page.module.ts +++ b/frontend/src/app/master-page.module.ts @@ -10,6 +10,8 @@ import { PushTransactionComponent } from './components/push-transaction/push-tra import { CalculatorComponent } from './components/calculator/calculator.component'; import { BlocksList } from './components/blocks-list/blocks-list.component'; import { RbfList } from './components/rbf-list/rbf-list.component'; +import { ServerHealthComponent } from './components/server-health/server-health.component'; +import { ServerStatusComponent } from './components/server-health/server-status.component'; const browserWindow = window || {}; // @ts-ignore @@ -96,6 +98,19 @@ const routes: Routes = [ } ]; +if (window['__env']?.OFFICIAL_MEMPOOL_SPACE) { + routes[0].children.push({ + path: 'nodes', + data: { networks: ['bitcoin', 'liquid'] }, + component: ServerHealthComponent + }); + routes[0].children.push({ + path: 'network', + data: { networks: ['bitcoin', 'liquid'] }, + component: ServerStatusComponent + }); +} + @NgModule({ imports: [ RouterModule.forChild(routes) diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 6c43a019f..2f6496559 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -54,6 +54,7 @@ import { AssetsComponent } from '../components/assets/assets.component'; import { AssetsNavComponent } from '../components/assets/assets-nav/assets-nav.component'; import { StatusViewComponent } from '../components/status-view/status-view.component'; import { ServerHealthComponent } from '../components/server-health/server-health.component'; +import { ServerStatusComponent } from '../components/server-health/server-status.component'; import { FeesBoxComponent } from '../components/fees-box/fees-box.component'; import { DifficultyComponent } from '../components/difficulty/difficulty.component'; import { DifficultyTooltipComponent } from '../components/difficulty/difficulty-tooltip.component'; @@ -153,6 +154,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir AssetsComponent, StatusViewComponent, ServerHealthComponent, + ServerStatusComponent, FeesBoxComponent, DifficultyComponent, DifficultyMiningComponent, @@ -280,6 +282,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir AssetsComponent, StatusViewComponent, ServerHealthComponent, + ServerStatusComponent, FeesBoxComponent, DifficultyComponent, DifficultyMiningComponent,