From d791ef7bb13e847d647b5ed19ff5a2337eebcfd3 Mon Sep 17 00:00:00 2001 From: natsoni Date: Fri, 27 Feb 2026 13:57:40 +0100 Subject: [PATCH] Add Federation reserves ratio to monitoring page --- .../bitcoin/bitcoin-api-abstract-factory.ts | 15 +++++++ backend/src/api/bitcoin/esplora-api.ts | 45 +++++++++++++++++++ .../server-health.component.html | 2 + .../server-health.component.scss | 4 ++ .../server-health/server-health.component.ts | 12 +++++ .../src/app/interfaces/websocket.interface.ts | 5 +++ 6 files changed, 83 insertions(+) diff --git a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts index eaa079eb6..36fb0d7b0 100644 --- a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts +++ b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts @@ -57,4 +57,19 @@ export interface HealthCheckHost { unreachable: boolean; checked: boolean; lastChecked: number; + hashes?: { + frontend?: string; + hybrid?: string; + backend?: string; + electrs?: string; + ssr?: string; + core?: string; + os?: string; + lastUpdated?: number; + }; + liquidAudit?: { + pegRatio: number; + bitcoinLastBlockUpdate: number; + liquidLastBlockUpdate: number; + }; } diff --git a/backend/src/api/bitcoin/esplora-api.ts b/backend/src/api/bitcoin/esplora-api.ts index f4b0b400c..96b17f7ef 100644 --- a/backend/src/api/bitcoin/esplora-api.ts +++ b/backend/src/api/bitcoin/esplora-api.ts @@ -32,6 +32,11 @@ interface FailoverHost { core?: string, os?: string, lastUpdated: number, + }, + liquidAudit?: { + pegRatio: number, + bitcoinLastBlockUpdate: number, + liquidLastBlockUpdate: number, } } @@ -145,6 +150,8 @@ class FailoverRouter { } } + await this.$updateLiquidAudit(host); + // Check front and backend git hashes less often if (Date.now() - host.hashes.lastUpdated > this.gitHashInterval) { await Promise.all([ @@ -351,6 +358,43 @@ class FailoverRouter { } } + private async $updateLiquidAudit(host: FailoverHost): Promise { + if (config.MEMPOOL.NETWORK !== 'liquid') { + return; + } + try { + const [reservesResponse, pegsResponse] = await Promise.all([ + this.pollConnection.get( + `${host.publicDomain}/api/v1/liquid/reserves`, { + timeout: config.ESPLORA.FALLBACK_TIMEOUT, + headers: { 'Host': 'liquid.network' } + } + ), + this.pollConnection.get( + `${host.publicDomain}/api/v1/liquid/pegs`, { + timeout: config.ESPLORA.FALLBACK_TIMEOUT, + headers: { 'Host': 'liquid.network' } + } + ), + ]); + + const reservesAmount = Number(reservesResponse.data?.amount); + const pegsAmount = Number(pegsResponse.data?.amount); + const bitcoinLastBlockUpdate = Number(reservesResponse.data?.lastBlockUpdate); + const liquidLastBlockUpdate = Number(pegsResponse.data?.lastBlockUpdate); + + if (Number.isFinite(reservesAmount) && Number.isFinite(pegsAmount) && Number.isFinite(bitcoinLastBlockUpdate) && Number.isFinite(liquidLastBlockUpdate) && pegsAmount > 0) { + host.liquidAudit = { + pegRatio: (reservesAmount / pegsAmount) * 100, + bitcoinLastBlockUpdate, + liquidLastBlockUpdate, + }; + } + } catch (e) { + // failed to get liquid audit values - do nothing + } + } + // returns the public mempool domain corresponding to an esplora server url // (a bit of a hack to avoid manually specifying frontend & backend URLs for each esplora server) private extractPublicDomain(url: string): string { @@ -585,6 +629,7 @@ class ElectrsApi implements AbstractBitcoinApi { checked: !!host.checked, lastChecked: host.lastChecked || 0, hashes: host.hashes, + ...(config.MEMPOOL.NETWORK === 'liquid' ? { liquidAudit: host.liquidAudit } : {}), })); } else { return []; 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 c0150f5a7..89b10ebef 100644 --- a/frontend/src/app/components/server-health/server-health.component.html +++ b/frontend/src/app/components/server-health/server-health.component.html @@ -19,6 +19,7 @@ RTT RTT Height + Peg Hybrid Front Back @@ -35,6 +36,7 @@ {{ (host.rtt / 1000) | number : '1.1-1' }} {{ host.rtt == null ? '' : 's'}} {{ !host.checked ? '⏳' : (host.unreachable ? '🔥' : '✅') }} {{ host.rtt | number : '1.0-0' }} {{ host.rtt == null ? '' : 'ms'}} {{ !host.checked ? '⏳' : (host.unreachable ? '🔥' : '✅') }} {{ host.latestHeight }} {{ !host.checked ? '⏳' : (host.outOfSync ? '🚫' : (host.latestHeight && host.latestHeight < maxHeight ? '🟧' : '✅')) }} + {{ formatPegRatio(host.liquidAudit?.pegRatio) }} @if (type !== 'core' && type !== 'os' && host.hashes?.[type]) { 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 f741cb3db..126ec8ada 100644 --- a/frontend/src/app/components/server-health/server-health.component.scss +++ b/frontend/src/app/components/server-health/server-health.component.scss @@ -35,6 +35,10 @@ &.rtt, &.height { text-align: right; } + &.peg { + text-align: right; + white-space: nowrap; + } &.only-small { display: table-cell; } 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 399e42d2e..7e4d173c5 100644 --- a/frontend/src/app/components/server-health/server-health.component.ts +++ b/frontend/src/app/components/server-health/server-health.component.ts @@ -18,6 +18,7 @@ export class ServerHealthComponent implements OnInit { interval: number; now: number = Date.now(); colors: Record> = {}; + isLiquid = false; repoMap = { frontend: 'mempool', @@ -93,6 +94,8 @@ export class ServerHealthComponent implements OnInit { this.now = Date.now(); this.cd.markForCheck(); }, 1000); + + this.isLiquid = this.stateService.network === 'liquid'; } trackByFn(index: number, host: HealthCheckHost): string { @@ -171,4 +174,13 @@ export class ServerHealthComponent implements OnInit { const hue = (index / (sortedVersions.length - 1)) * 120; return `hsl(${hue}, 70%, 35%)`; } + + public formatPegRatio(pegRatio: number | null | undefined): string { + if (pegRatio == null) { + return '?'; + } + + const ratio = Math.floor(pegRatio); + return `${ratio}%${ratio >= 100 ? ' ✅' : ' 🔥'}`; + } } diff --git a/frontend/src/app/interfaces/websocket.interface.ts b/frontend/src/app/interfaces/websocket.interface.ts index d22061259..4cc04b276 100644 --- a/frontend/src/app/interfaces/websocket.interface.ts +++ b/frontend/src/app/interfaces/websocket.interface.ts @@ -156,6 +156,11 @@ export interface HealthCheckHost { core?: string; os?: string; }; + liquidAudit?: { + pegRatio: number; + bitcoinLastBlockUpdate: number; + liquidLastBlockUpdate: number; + }; } export interface StratumJob {