From a23881e62ccca84ce0d334b9812fc32aa5594f40 Mon Sep 17 00:00:00 2001 From: hunicus <93150691+hunicus@users.noreply.github.com> Date: Fri, 11 Aug 2023 17:11:05 +0900 Subject: [PATCH 01/26] Implement enterprise cta in docs [desktop] --- .../docs/api-docs/api-docs-nav.component.html | 4 ++++ .../docs/api-docs/api-docs-nav.component.scss | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/frontend/src/app/docs/api-docs/api-docs-nav.component.html b/frontend/src/app/docs/api-docs/api-docs-nav.component.html index ec1cde38f..8d17faf4e 100644 --- a/frontend/src/app/docs/api-docs/api-docs-nav.component.html +++ b/frontend/src/app/docs/api-docs/api-docs-nav.component.html @@ -1,3 +1,7 @@ +
+

Get higher API limits with Mempool Enterprise

+ More Info +

{{ item.title }}

{{ item.title }} diff --git a/frontend/src/app/docs/api-docs/api-docs-nav.component.scss b/frontend/src/app/docs/api-docs/api-docs-nav.component.scss index 3d1b17324..cee9cba14 100644 --- a/frontend/src/app/docs/api-docs/api-docs-nav.component.scss +++ b/frontend/src/app/docs/api-docs/api-docs-nav.component.scss @@ -11,3 +11,19 @@ a { display: block; margin: 5px 0; } + +#enterprise-cta-desktop { + text-align: center; + padding: 20px; + margin: 20px 20px 20px 0; + background-color: #1d1f31; + border-radius: 12px; +} + +#enterprise-cta-desktop p { + margin: 0 auto 20px auto; +} + +#enterprise-cta-desktop a { + display: inline-block; +} From d02838fb598895efb6b8f1f4f53fb7e3c0d3a225 Mon Sep 17 00:00:00 2001 From: hunicus <93150691+hunicus@users.noreply.github.com> Date: Sun, 13 Aug 2023 06:30:42 +0900 Subject: [PATCH 02/26] Implement enterprise cta in docs [mobile] --- .../docs/api-docs/api-docs-nav.component.html | 2 +- .../docs/api-docs/api-docs-nav.component.scss | 5 ++- .../app/docs/api-docs/api-docs.component.html | 8 ++++ .../app/docs/api-docs/api-docs.component.scss | 39 +++++++++++++++++++ .../app/docs/api-docs/api-docs.component.ts | 1 + 5 files changed, 53 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/docs/api-docs/api-docs-nav.component.html b/frontend/src/app/docs/api-docs/api-docs-nav.component.html index 8d17faf4e..6f4aedb5f 100644 --- a/frontend/src/app/docs/api-docs/api-docs-nav.component.html +++ b/frontend/src/app/docs/api-docs/api-docs-nav.component.html @@ -1,5 +1,5 @@
-

Get higher API limits with Mempool Enterprise

+

Get higher API limits with Mempool Enterprise®

More Info
diff --git a/frontend/src/app/docs/api-docs/api-docs-nav.component.scss b/frontend/src/app/docs/api-docs/api-docs-nav.component.scss index cee9cba14..550d1c936 100644 --- a/frontend/src/app/docs/api-docs/api-docs-nav.component.scss +++ b/frontend/src/app/docs/api-docs/api-docs-nav.component.scss @@ -21,9 +21,12 @@ a { } #enterprise-cta-desktop p { - margin: 0 auto 20px auto; + margin: 0 auto 16px auto; + color: #fff; + font-weight: 400; } #enterprise-cta-desktop a { display: inline-block; } + diff --git a/frontend/src/app/docs/api-docs/api-docs.component.html b/frontend/src/app/docs/api-docs/api-docs.component.html index 40a7ae486..22fbec709 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.html +++ b/frontend/src/app/docs/api-docs/api-docs.component.html @@ -39,6 +39,14 @@
+
+

Get higher API limits with Mempool Enterprise®

+ +
+

Below is a reference for the {{ network.val === '' ? 'Bitcoin' : network.val.charAt(0).toUpperCase() + network.val.slice(1) }} REST API service.

Note that we enforce rate limits. If you exceed these limits, you will get an HTTP 429 error. If you repeatedly exceed the limits, you may be banned from accessing the service altogether. Consider an enterprise sponsorship if you need higher API limits.

diff --git a/frontend/src/app/docs/api-docs/api-docs.component.scss b/frontend/src/app/docs/api-docs/api-docs.component.scss index 8e4c0c7a9..e79b124e8 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.scss +++ b/frontend/src/app/docs/api-docs/api-docs.component.scss @@ -282,6 +282,41 @@ h3 { margin-bottom: 0; } +#enterprise-cta-mobile { + padding: 20px; + background-color: #1d1f31; + border-radius: 0.25rem; + text-align: center; + position: fixed; + z-index: 100; + left: 30px; + width: calc(100% - 60px); + bottom: 70px; + display: none; + border: 3px solid #533180; +} + +#enterprise-cta-mobile p { + font-size: 16px; + display: inline-block; + margin: 0 auto; +} + +#enterprise-cta-mobile a { + padding: 4px 8px; + font-size: 16px; + margin: 15px 5px 5px 5px; +} + +#enterprise-cta-mobile .btn-secondary:hover { + background-color: #2d3348; + border-color: #2d3348; +} + +#enterprise-cta-mobile .no-line-break { + white-space: nowrap; +} + @media (max-width: 992px) { h3 { @@ -340,6 +375,10 @@ h3 { #disclaimer table { display: none; } + + #enterprise-cta-mobile { + display: initial; + } } @media (min-width: 992px) { diff --git a/frontend/src/app/docs/api-docs/api-docs.component.ts b/frontend/src/app/docs/api-docs/api-docs.component.ts index 62a0fadba..398008d0a 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.ts +++ b/frontend/src/app/docs/api-docs/api-docs.component.ts @@ -30,6 +30,7 @@ export class ApiDocsComponent implements OnInit, AfterViewInit { officialMempoolInstance: boolean; auditEnabled: boolean; mobileViewport: boolean = false; + showMobileEnterpriseUpsell: boolean = true; @ViewChildren(FaqTemplateDirective) faqTemplates: QueryList; dict = {}; From 693b845c45cb1683dcfeeb3fd3961e7e64e6e871 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sat, 23 Sep 2023 11:57:20 -0700 Subject: [PATCH 03/26] Add block height workflow --- .../workflows/get_backend_block_height.yml | 19 +++++++++++++++ scripts/get_block_tip_height.sh | 24 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 .github/workflows/get_backend_block_height.yml create mode 100644 scripts/get_block_tip_height.sh diff --git a/.github/workflows/get_backend_block_height.yml b/.github/workflows/get_backend_block_height.yml new file mode 100644 index 000000000..52f3b038c --- /dev/null +++ b/.github/workflows/get_backend_block_height.yml @@ -0,0 +1,19 @@ +name: 'Check if servers are in sync' + +on: [workflow_dispatch] + +jobs: + print-backend-sha: + runs-on: 'ubuntu-latest' + name: Get block height + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + path: repo + + - name: Run script + working-directory: repo + run: | + chmod +x ./scripts/get_block_tip_height.sh + sh ./scripts/get_block_tip_height.sh diff --git a/scripts/get_block_tip_height.sh b/scripts/get_block_tip_height.sh new file mode 100644 index 000000000..861be0406 --- /dev/null +++ b/scripts/get_block_tip_height.sh @@ -0,0 +1,24 @@ +BASE_HEIGHT=$(curl -sk https://node202.tk7.mempool.space/api/v1/blocks/tip/height) +IN_SYNC=true +echo "Base height (node202.tk7): $BASE_HEIGHT" + +for LOCATION in fmt va1 fra tk7 +do + for NODE in 201 202 203 204 205 206 + do + NODE_HEIGHT=$(curl -sk https://node$NODE.$LOCATION.mempool.space/api/v1/blocks/tip/height) + echo $(echo node$NODE.$LOCATION.mempool.space) - $NODE_HEIGHT + if [ "$NODE_HEIGHT" -ne "$BASE_HEIGHT" ]; then + echo $(echo node$NODE.$LOCATION.mempool.space) is not in sync + IN_SYNC=false + fi + done +done + +if [ "$IN_SYNC" = false ]; then + echo "One or more servers are out of sync. Check the logs." + exit -1 +else + echo "All servers are in sync." +fi + From 24b3ca559d89bf9665111c28c9e5e1c27e63b2c5 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sat, 23 Sep 2023 13:30:54 -0700 Subject: [PATCH 04/26] Add block delta to the logs --- scripts/get_block_tip_height.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/get_block_tip_height.sh b/scripts/get_block_tip_height.sh index 861be0406..603193bb8 100644 --- a/scripts/get_block_tip_height.sh +++ b/scripts/get_block_tip_height.sh @@ -9,7 +9,8 @@ do NODE_HEIGHT=$(curl -sk https://node$NODE.$LOCATION.mempool.space/api/v1/blocks/tip/height) echo $(echo node$NODE.$LOCATION.mempool.space) - $NODE_HEIGHT if [ "$NODE_HEIGHT" -ne "$BASE_HEIGHT" ]; then - echo $(echo node$NODE.$LOCATION.mempool.space) is not in sync + COUNT=$((BASE_HEIGHT-NODE_HEIGHT)) + echo $(echo node$NODE.$LOCATION.mempool.space) is not in sync. delta: $COUNT IN_SYNC=false fi done From 64f6775c685f5597f7da4755a08d9933fc937288 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Wed, 3 Jan 2024 15:12:11 +0100 Subject: [PATCH 05/26] [database] increase default pool size to 100 connections --- backend/src/__fixtures__/mempool-config.template.json | 3 ++- backend/src/__tests__/config.test.ts | 3 ++- backend/src/config.ts | 2 ++ backend/src/database.ts | 2 +- docker/backend/mempool-config.json | 3 ++- docker/backend/start.sh | 2 ++ 6 files changed, 11 insertions(+), 4 deletions(-) diff --git a/backend/src/__fixtures__/mempool-config.template.json b/backend/src/__fixtures__/mempool-config.template.json index 0c30651ce..4b7966e08 100644 --- a/backend/src/__fixtures__/mempool-config.template.json +++ b/backend/src/__fixtures__/mempool-config.template.json @@ -77,7 +77,8 @@ "USERNAME": "__DATABASE_USERNAME__", "PASSWORD": "__DATABASE_PASSWORD__", "PID_DIR": "__DATABASE_PID_FILE__", - "TIMEOUT": 3000 + "TIMEOUT": 3000, + "POOL_SIZE": 100 }, "SYSLOG": { "ENABLED": false, diff --git a/backend/src/__tests__/config.test.ts b/backend/src/__tests__/config.test.ts index 2991162e9..84290744f 100644 --- a/backend/src/__tests__/config.test.ts +++ b/backend/src/__tests__/config.test.ts @@ -91,7 +91,8 @@ describe('Mempool Backend Config', () => { USERNAME: 'mempool', PASSWORD: 'mempool', TIMEOUT: 180000, - PID_DIR: '' + PID_DIR: '', + POOL_SIZE: 100, }); expect(config.SYSLOG).toStrictEqual({ diff --git a/backend/src/config.ts b/backend/src/config.ts index 4115149e6..6b2011d39 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -101,6 +101,7 @@ interface IConfig { PASSWORD: string; TIMEOUT: number; PID_DIR: string; + POOL_SIZE: number; }; SYSLOG: { ENABLED: boolean; @@ -236,6 +237,7 @@ const defaults: IConfig = { 'PASSWORD': 'mempool', 'TIMEOUT': 180000, 'PID_DIR': '', + 'POOL_SIZE': 100, }, 'SYSLOG': { 'ENABLED': true, diff --git a/backend/src/database.ts b/backend/src/database.ts index dc543bbbc..05f624ff4 100644 --- a/backend/src/database.ts +++ b/backend/src/database.ts @@ -21,7 +21,7 @@ import { execSync } from 'child_process'; database: config.DATABASE.DATABASE, user: config.DATABASE.USERNAME, password: config.DATABASE.PASSWORD, - connectionLimit: 10, + connectionLimit: config.DATABASE.POOL_SIZE, supportBigNumbers: true, timezone: '+00:00', }; diff --git a/docker/backend/mempool-config.json b/docker/backend/mempool-config.json index e73fa1929..804ca4d11 100644 --- a/docker/backend/mempool-config.json +++ b/docker/backend/mempool-config.json @@ -77,7 +77,8 @@ "USERNAME": "__DATABASE_USERNAME__", "PASSWORD": "__DATABASE_PASSWORD__", "TIMEOUT": __DATABASE_TIMEOUT__, - "PID_DIR": "__DATABASE_PID_DIR__" + "PID_DIR": "__DATABASE_PID_DIR__", + "POOL_SIZE": __DATABASE_POOL_SIZE__ }, "SYSLOG": { "ENABLED": __SYSLOG_ENABLED__, diff --git a/docker/backend/start.sh b/docker/backend/start.sh index 232cf7284..196d4dca2 100755 --- a/docker/backend/start.sh +++ b/docker/backend/start.sh @@ -79,6 +79,7 @@ __DATABASE_USERNAME__=${DATABASE_USERNAME:=mempool} __DATABASE_PASSWORD__=${DATABASE_PASSWORD:=mempool} __DATABASE_TIMEOUT__=${DATABASE_TIMEOUT:=180000} __DATABASE_PID_DIR__=${DATABASE_PID_DIR:=""} +__DATABASE_POOL_SIZE__=${DATABASE_POOL_SIZE:=100} # SYSLOG __SYSLOG_ENABLED__=${SYSLOG_ENABLED:=false} @@ -226,6 +227,7 @@ sed -i "s!__DATABASE_USERNAME__!${__DATABASE_USERNAME__}!g" mempool-config.json sed -i "s!__DATABASE_PASSWORD__!${__DATABASE_PASSWORD__}!g" mempool-config.json sed -i "s!__DATABASE_TIMEOUT__!${__DATABASE_TIMEOUT__}!g" mempool-config.json sed -i "s!__DATABASE_PID_DIR__!${__DATABASE_PID_DIR__}!g" mempool-config.json +sed -i "s!__DATABASE_POOL_SIZE__!${__DATABASE_POOL_SIZE__}!g" mempool-config.json sed -i "s!__SYSLOG_ENABLED__!${__SYSLOG_ENABLED__}!g" mempool-config.json sed -i "s!__SYSLOG_HOST__!${__SYSLOG_HOST__}!g" mempool-config.json From 08272c7f87db20755ae06a79c91be47c24479afd Mon Sep 17 00:00:00 2001 From: softsimon Date: Mon, 29 Jan 2024 01:50:46 +0700 Subject: [PATCH 06/26] Display pool tags on dashboard --- .../blockchain-blocks/blockchain-blocks.component.html | 2 +- .../blockchain-blocks/blockchain-blocks.component.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html index 680beb006..8c8ea6579 100644 --- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html +++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html @@ -56,7 +56,7 @@
-
+
{{ block.extras.pool.name}} diff --git a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.scss b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.scss index 795e1f4df..c1cc6809d 100644 --- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.scss +++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.scss @@ -166,7 +166,7 @@ opacity: 1; } .hide { - opacity: 0; + opacity: 0.4; pointer-events : none; } From df92f4d0aac4003a744d39edd2372b494ffc6de8 Mon Sep 17 00:00:00 2001 From: James Blacklock Date: Tue, 6 Feb 2024 21:36:14 -0500 Subject: [PATCH 07/26] modify docker/init.sh for macOS compatibility --- docker/init.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docker/init.sh b/docker/init.sh index ee9ac9542..3c5ec6aa3 100755 --- a/docker/init.sh +++ b/docker/init.sh @@ -1,7 +1,7 @@ #!/bin/sh #backend -cp ./docker/backend/* ./backend/ +cp -r ./docker/backend/* ./backend/ #geoip-data mkdir -p ./backend/GeoIP/ @@ -13,8 +13,8 @@ localhostIP="127.0.0.1" cp ./docker/frontend/* ./frontend cp ./nginx.conf ./frontend/ cp ./nginx-mempool.conf ./frontend/ -sed -i "s/${localhostIP}:80/0.0.0.0:__MEMPOOL_FRONTEND_HTTP_PORT__/g" ./frontend/nginx.conf -sed -i "s/${localhostIP}/0.0.0.0/g" ./frontend/nginx.conf -sed -i "s/user nobody;//g" ./frontend/nginx.conf -sed -i "s!/etc/nginx/nginx-mempool.conf!/etc/nginx/conf.d/nginx-mempool.conf!g" ./frontend/nginx.conf -sed -i "s/${localhostIP}:8999/__MEMPOOL_BACKEND_MAINNET_HTTP_HOST__:__MEMPOOL_BACKEND_MAINNET_HTTP_PORT__/g" ./frontend/nginx-mempool.conf +sed -i"" -e "s/${localhostIP}:80/0.0.0.0:__MEMPOOL_FRONTEND_HTTP_PORT__/g" ./frontend/nginx.conf +sed -i"" -e "s/${localhostIP}/0.0.0.0/g" ./frontend/nginx.conf +sed -i"" -e "s/user nobody;//g" ./frontend/nginx.conf +sed -i"" -e "s!/etc/nginx/nginx-mempool.conf!/etc/nginx/conf.d/nginx-mempool.conf!g" ./frontend/nginx.conf +sed -i"" -e "s/${localhostIP}:8999/__MEMPOOL_BACKEND_MAINNET_HTTP_HOST__:__MEMPOOL_BACKEND_MAINNET_HTTP_PORT__/g" ./frontend/nginx-mempool.conf From 96a17f09a5adf69bae96c25ea4ad38939eaa4f8e Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 16 Feb 2024 02:30:51 +0000 Subject: [PATCH 08/26] Add embeddable wallet balance page --- frontend/src/app/app-routing.module.ts | 41 ++++ .../address-group.component.html | 20 ++ .../address-group.component.scss | 82 +++++++ .../address-group/address-group.component.ts | 212 ++++++++++++++++++ frontend/src/app/master-page.module.ts | 1 + frontend/src/app/services/state.service.ts | 1 + .../src/app/services/websocket.service.ts | 18 ++ .../truncate/truncate.component.html | 2 +- .../components/truncate/truncate.component.ts | 1 + frontend/src/app/shared/shared.module.ts | 3 + 10 files changed, 380 insertions(+), 1 deletion(-) create mode 100644 frontend/src/app/components/address-group/address-group.component.html create mode 100644 frontend/src/app/components/address-group/address-group.component.scss create mode 100644 frontend/src/app/components/address-group/address-group.component.ts diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index e123a1525..1fe196090 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 { AddressGroupComponent } from './components/address-group/address-group.component'; const browserWindow = window || {}; // @ts-ignore @@ -26,6 +27,14 @@ let routes: Routes = [ loadChildren: () => import('./master-page.module').then(m => m.MasterPageModule), data: { preload: true }, }, + { + path: 'wallet', + children: [], + component: AddressGroupComponent, + data: { + networkSpecific: true, + } + }, { path: 'status', data: { networks: ['bitcoin', 'liquid'] }, @@ -61,6 +70,14 @@ let routes: Routes = [ loadChildren: () => import('./master-page.module').then(m => m.MasterPageModule), data: { preload: true }, }, + { + path: 'wallet', + children: [], + component: AddressGroupComponent, + data: { + networkSpecific: true, + } + }, { path: 'status', data: { networks: ['bitcoin', 'liquid'] }, @@ -88,6 +105,14 @@ let routes: Routes = [ loadChildren: () => import('./master-page.module').then(m => m.MasterPageModule), data: { preload: true }, }, + { + path: 'wallet', + children: [], + component: AddressGroupComponent, + data: { + networkSpecific: true, + } + }, { path: 'preview', children: [ @@ -168,6 +193,14 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { loadChildren: () => import ('./liquid/liquid-master-page.module').then(m => m.LiquidMasterPageModule), data: { preload: true }, }, + { + path: 'wallet', + children: [], + component: AddressGroupComponent, + data: { + networkSpecific: true, + } + }, { path: 'status', data: { networks: ['bitcoin', 'liquid'] }, @@ -195,6 +228,14 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { loadChildren: () => import ('./liquid/liquid-master-page.module').then(m => m.LiquidMasterPageModule), data: { preload: true }, }, + { + path: 'wallet', + children: [], + component: AddressGroupComponent, + data: { + networkSpecific: true, + } + }, { path: 'preview', children: [ diff --git a/frontend/src/app/components/address-group/address-group.component.html b/frontend/src/app/components/address-group/address-group.component.html new file mode 100644 index 000000000..52c7d3ca1 --- /dev/null +++ b/frontend/src/app/components/address-group/address-group.component.html @@ -0,0 +1,20 @@ +
+

Balances

+ + + + + + + + + + + +
Total
+ +
+ +
diff --git a/frontend/src/app/components/address-group/address-group.component.scss b/frontend/src/app/components/address-group/address-group.component.scss new file mode 100644 index 000000000..cbe35d518 --- /dev/null +++ b/frontend/src/app/components/address-group/address-group.component.scss @@ -0,0 +1,82 @@ +.frame { + position: relative; + background: #24273e; + padding: 0.5rem; + height: calc(100% + 60px); +} + +.pagination { + position: absolute; + bottom: 0.5rem; + right: 0.5rem; +} + +.table { + td, th { + padding: 0.15rem 0.5rem; + + &.address { + width: auto; + } + &.btc { + width: 140px; + text-align: right; + } + &.fiat { + width: 142px; + text-align: right; + } + } + + tr { + border-collapse: collapse; + + &:first-child { + border-bottom: solid 1px white; + td, th { + padding-bottom: 0.3rem; + } + } + &:nth-child(2) { + td, th { + padding-top: 0.3rem; + } + } + &:nth-child(even) { + background: #181b2d; + } + } + + @media (min-width: 528px) { + td, th { + &.btc { + width: 160px; + } + &.fiat { + width: 140px; + } + } + } + + @media (min-width: 576px) { + td, th { + &.btc { + width: 170px; + } + &.fiat { + width: 140px; + } + } + } + + @media (min-width: 992px) { + td, th { + &.btc { + width: 210px; + } + &.fiat { + width: 140px; + } + } + } +} \ No newline at end of file diff --git a/frontend/src/app/components/address-group/address-group.component.ts b/frontend/src/app/components/address-group/address-group.component.ts new file mode 100644 index 000000000..ec9b2ed1d --- /dev/null +++ b/frontend/src/app/components/address-group/address-group.component.ts @@ -0,0 +1,212 @@ +import { Component, OnInit, OnDestroy, ChangeDetectorRef, HostListener } from '@angular/core'; +import { ActivatedRoute, ParamMap } from '@angular/router'; +import { ElectrsApiService } from '../../services/electrs-api.service'; +import { switchMap, catchError } from 'rxjs/operators'; +import { Address, Transaction } from '../../interfaces/electrs.interface'; +import { WebsocketService } from '../../services/websocket.service'; +import { StateService } from '../../services/state.service'; +import { AudioService } from '../../services/audio.service'; +import { ApiService } from '../../services/api.service'; +import { of, Subscription, forkJoin } from 'rxjs'; +import { SeoService } from '../../services/seo.service'; +import { AddressInformation } from '../../interfaces/node-api.interface'; + +@Component({ + selector: 'app-address-group', + templateUrl: './address-group.component.html', + styleUrls: ['./address-group.component.scss'] +}) +export class AddressGroupComponent implements OnInit, OnDestroy { + network = ''; + + balance = 0; + confirmed = 0; + mempool = 0; + addresses: { [address: string]: number | null }; + addressStrings: string[] = []; + addressInfo: { [address: string]: AddressInformation | null }; + seenTxs: { [txid: string ]: boolean } = {}; + isLoadingAddress = true; + error: any; + mainSubscription: Subscription; + wsSubscription: Subscription; + + page: string[] = []; + pageIndex: number = 1; + itemsPerPage: number = 10; + + screenSize: 'lg' | 'md' | 'sm' = 'lg'; + digitsInfo: string = '1.8-8'; + + constructor( + private route: ActivatedRoute, + private electrsApiService: ElectrsApiService, + private websocketService: WebsocketService, + private stateService: StateService, + private audioService: AudioService, + private apiService: ApiService, + private seoService: SeoService, + private cd: ChangeDetectorRef, + ) { } + + ngOnInit(): void { + this.onResize(); + this.stateService.networkChanged$.subscribe((network) => this.network = network); + this.websocketService.want(['blocks']); + + this.mainSubscription = this.route.queryParamMap + .pipe( + switchMap((params: ParamMap) => { + this.error = undefined; + this.isLoadingAddress = true; + this.addresses = {}; + this.addressInfo = {}; + this.balance = 0; + + this.addressStrings = params.get('addresses').split(',').map(address => { + if (/^[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,100}|04[a-fA-F0-9]{128}|(02|03)[a-fA-F0-9]{64}$/.test(address)) { + return address.toLowerCase(); + } else { + return address; + } + }); + + return forkJoin(this.addressStrings.map(address => { + const getLiquidInfo = ((this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet') && /^([a-zA-HJ-NP-Z1-9]{26,35}|[a-z]{2,5}1[ac-hj-np-z02-9]{8,100}|[a-km-zA-HJ-NP-Z1-9]{80})$/.test(address)); + return forkJoin([ + of(address), + this.electrsApiService.getAddress$(address), + (getLiquidInfo ? this.apiService.validateAddress$(address) : of(null)), + ]); + })); + }), + catchError(e => { + this.error = e; + return of([]); + }) + ).subscribe((addresses) => { + for (const addressData of addresses) { + const address = addressData[0]; + const addressBalance = addressData[1] as Address; + if (addressBalance) { + this.addresses[address] = addressBalance.chain_stats.funded_txo_sum + + addressBalance.mempool_stats.funded_txo_sum + - addressBalance.chain_stats.spent_txo_sum + - addressBalance.mempool_stats.spent_txo_sum; + this.balance += this.addresses[address]; + this.confirmed += this.addresses[address]; + } + this.addressInfo[address] = addressData[2] ? addressData[2] as AddressInformation : null; + } + this.websocketService.startTrackAddresses(this.addressStrings); + this.isLoadingAddress = false; + this.pageChange(this.pageIndex); + }); + + this.wsSubscription = this.stateService.multiAddressTransactions$.subscribe(update => { + for (const address of Object.keys(update)) { + for (const tx of update[address].mempool) { + this.addTransaction(tx, false, false); + } + for (const tx of update[address].confirmed) { + this.addTransaction(tx, true, false); + } + for (const tx of update[address].removed) { + this.removeTransaction(tx, tx.status.confirmed); + } + } + }); + } + + pageChange(index): void { + this.page = this.addressStrings.slice((index - 1) * this.itemsPerPage, index * this.itemsPerPage); + } + + addTransaction(transaction: Transaction, confirmed = false, playSound: boolean = true): boolean { + if (this.seenTxs[transaction.txid]) { + this.removeTransaction(transaction, false); + } + this.seenTxs[transaction.txid] = true; + + let balance = 0; + transaction.vin.forEach((vin) => { + if (this.addressStrings.includes(vin?.prevout?.scriptpubkey_address)) { + this.addresses[vin?.prevout?.scriptpubkey_address] -= vin.prevout.value; + balance -= vin.prevout.value; + this.balance -= vin.prevout.value; + if (confirmed) { + this.confirmed -= vin.prevout.value; + } + } + }); + transaction.vout.forEach((vout) => { + if (this.addressStrings.includes(vout?.scriptpubkey_address)) { + this.addresses[vout?.scriptpubkey_address] += vout.value; + balance += vout.value; + this.balance += vout.value; + if (confirmed) { + this.confirmed += vout.value; + } + } + }); + + if (playSound) { + if (balance > 0) { + this.audioService.playSound('cha-ching'); + } else { + this.audioService.playSound('chime'); + } + } + + return true; + } + + removeTransaction(transaction: Transaction, confirmed = false): boolean { + transaction.vin.forEach((vin) => { + if (this.addressStrings.includes(vin?.prevout?.scriptpubkey_address)) { + this.addresses[vin?.prevout?.scriptpubkey_address] += vin.prevout.value; + this.balance += vin.prevout.value; + if (confirmed) { + this.confirmed += vin.prevout.value; + } + } + }); + transaction.vout.forEach((vout) => { + if (this.addressStrings.includes(vout?.scriptpubkey_address)) { + this.addresses[vout?.scriptpubkey_address] -= vout.value; + this.balance -= vout.value; + if (confirmed) { + this.confirmed -= vout.value; + } + } + }); + + return true; + } + + @HostListener('window:resize', ['$event']) + onResize(): void { + if (window.innerWidth >= 992) { + this.screenSize = 'lg'; + this.digitsInfo = '1.8-8'; + } else if (window.innerWidth >= 528) { + this.screenSize = 'md'; + this.digitsInfo = '1.4-4'; + } else { + this.screenSize = 'sm'; + this.digitsInfo = '1.2-2'; + } + const newItemsPerPage = Math.floor((window.innerHeight - 150) / 30); + if (newItemsPerPage !== this.itemsPerPage) { + this.itemsPerPage = newItemsPerPage; + this.pageIndex = 1; + this.pageChange(this.pageIndex); + } + } + + ngOnDestroy(): void { + this.mainSubscription.unsubscribe(); + this.wsSubscription.unsubscribe(); + this.websocketService.stopTrackingAddresses(); + } +} diff --git a/frontend/src/app/master-page.module.ts b/frontend/src/app/master-page.module.ts index d7ec87030..e19e86518 100644 --- a/frontend/src/app/master-page.module.ts +++ b/frontend/src/app/master-page.module.ts @@ -6,6 +6,7 @@ import { SharedModule } from './shared/shared.module'; import { StartComponent } from './components/start/start.component'; import { AddressComponent } from './components/address/address.component'; +import { AddressGroupComponent } from './components/address-group/address-group.component'; import { PushTransactionComponent } from './components/push-transaction/push-transaction.component'; import { CalculatorComponent } from './components/calculator/calculator.component'; import { BlocksList } from './components/blocks-list/blocks-list.component'; diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index dc1365baa..d0dec76f5 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -119,6 +119,7 @@ export class StateService { mempoolTransactions$ = new Subject(); mempoolTxPosition$ = new Subject<{ txid: string, position: MempoolPosition, cpfp: CpfpInfo | null}>(); mempoolRemovedTransactions$ = new Subject(); + multiAddressTransactions$ = new Subject<{ [address: string]: { mempool: Transaction[], confirmed: Transaction[], removed: Transaction[] }}>(); 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 11e24ef71..90ddb6599 100644 --- a/frontend/src/app/services/websocket.service.ts +++ b/frontend/src/app/services/websocket.service.ts @@ -32,6 +32,7 @@ export class WebsocketService { private isTrackingRbf: 'all' | 'fullRbf' | false = false; private isTrackingRbfSummary = false; private isTrackingAddress: string | false = false; + private isTrackingAddresses: string[] | false = false; private trackingMempoolBlock: number; private latestGitCommit = ''; private onlineCheckTimeout: number; @@ -126,6 +127,9 @@ export class WebsocketService { if (this.isTrackingAddress) { this.startTrackAddress(this.isTrackingAddress); } + if (this.isTrackingAddresses) { + this.startTrackAddresses(this.isTrackingAddresses); + } this.stateService.connectionState$.next(2); } @@ -175,6 +179,16 @@ export class WebsocketService { this.isTrackingAddress = false; } + startTrackAddresses(addresses: string[]) { + this.websocketSubject.next({ 'track-addresses': addresses }); + this.isTrackingAddresses = addresses; + } + + stopTrackingAddresses() { + this.websocketSubject.next({ 'track-addresses': [] }); + this.isTrackingAddresses = false; + } + startTrackAsset(asset: string) { this.websocketSubject.next({ 'track-asset': asset }); } @@ -374,6 +388,10 @@ export class WebsocketService { }); } + if (response['multi-address-transactions']) { + this.stateService.multiAddressTransactions$.next(response['multi-address-transactions']); + } + if (response['block-transactions']) { response['block-transactions'].forEach((addressTransaction: Transaction) => { this.stateService.blockTransactions$.next(addressTransaction); diff --git a/frontend/src/app/shared/components/truncate/truncate.component.html b/frontend/src/app/shared/components/truncate/truncate.component.html index 4d42ab91c..825639ad2 100644 --- a/frontend/src/app/shared/components/truncate/truncate.component.html +++ b/frontend/src/app/shared/components/truncate/truncate.component.html @@ -1,6 +1,6 @@ - + diff --git a/frontend/src/app/shared/components/truncate/truncate.component.ts b/frontend/src/app/shared/components/truncate/truncate.component.ts index 0b6d2d8c1..a00ddb193 100644 --- a/frontend/src/app/shared/components/truncate/truncate.component.ts +++ b/frontend/src/app/shared/components/truncate/truncate.component.ts @@ -9,6 +9,7 @@ import { Component, Input, Inject, LOCALE_ID, ChangeDetectionStrategy } from '@a export class TruncateComponent { @Input() text: string; @Input() link: any = null; + @Input() external: boolean = false; @Input() lastChars: number = 4; @Input() maxWidth: number = null; @Input() inline: boolean = false; diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 36e7e79b8..b3c942ccf 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -46,6 +46,7 @@ import { BlockOverviewGraphComponent } from '../components/block-overview-graph/ import { BlockOverviewTooltipComponent } from '../components/block-overview-tooltip/block-overview-tooltip.component'; import { BlockFiltersComponent } from '../components/block-filters/block-filters.component'; import { AddressComponent } from '../components/address/address.component'; +import { AddressGroupComponent } from '../components/address-group/address-group.component'; import { SearchFormComponent } from '../components/search-form/search-form.component'; import { AddressLabelsComponent } from '../components/address-labels/address-labels.component'; import { FooterComponent } from '../components/footer/footer.component'; @@ -145,6 +146,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir BlockFiltersComponent, TransactionsListComponent, AddressComponent, + AddressGroupComponent, SearchFormComponent, AddressLabelsComponent, FooterComponent, @@ -271,6 +273,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir BlockFiltersComponent, TransactionsListComponent, AddressComponent, + AddressGroupComponent, SearchFormComponent, AddressLabelsComponent, FooterComponent, From a7ce7121ee4fb8050006ebebea021f762d713614 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 16 Feb 2024 02:50:16 +0000 Subject: [PATCH 09/26] add mempool logo to wallet widget --- .../components/address-group/address-group.component.html | 5 ++++- .../components/address-group/address-group.component.scss | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/components/address-group/address-group.component.html b/frontend/src/app/components/address-group/address-group.component.html index 52c7d3ca1..e7f559ba1 100644 --- a/frontend/src/app/components/address-group/address-group.component.html +++ b/frontend/src/app/components/address-group/address-group.component.html @@ -1,5 +1,8 @@
-

Balances

+
+

Balances

+ +
diff --git a/frontend/src/app/components/address-group/address-group.component.scss b/frontend/src/app/components/address-group/address-group.component.scss index cbe35d518..d99e9b522 100644 --- a/frontend/src/app/components/address-group/address-group.component.scss +++ b/frontend/src/app/components/address-group/address-group.component.scss @@ -5,6 +5,13 @@ height: calc(100% + 60px); } +.heading { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: start; +} + .pagination { position: absolute; bottom: 0.5rem; From 1f14cbfcaa8da334137b8c0c6a9013b938b4a7f2 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 16 Feb 2024 05:55:36 +0000 Subject: [PATCH 10/26] Fix websocket wallet balances, reposition logo --- .../address-group/address-group.component.html | 3 ++- .../address-group/address-group.component.scss | 12 ++++++++++++ .../address-group/address-group.component.ts | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/address-group/address-group.component.html b/frontend/src/app/components/address-group/address-group.component.html index e7f559ba1..174853600 100644 --- a/frontend/src/app/components/address-group/address-group.component.html +++ b/frontend/src/app/components/address-group/address-group.component.html @@ -1,7 +1,8 @@
-

Balances

+

Balances

+
Total
diff --git a/frontend/src/app/components/address-group/address-group.component.scss b/frontend/src/app/components/address-group/address-group.component.scss index d99e9b522..fedb57d2d 100644 --- a/frontend/src/app/components/address-group/address-group.component.scss +++ b/frontend/src/app/components/address-group/address-group.component.scss @@ -10,6 +10,16 @@ flex-direction: row; justify-content: space-between; align-items: start; + + & > * { + flex-basis: 0; + flex-grow: 1; + } + + h3 { + text-align: center; + margin: 0 1em; + } } .pagination { @@ -19,6 +29,8 @@ } .table { + margin-top: 0.5em; + td, th { padding: 0.15rem 0.5rem; diff --git a/frontend/src/app/components/address-group/address-group.component.ts b/frontend/src/app/components/address-group/address-group.component.ts index ec9b2ed1d..30bee7543 100644 --- a/frontend/src/app/components/address-group/address-group.component.ts +++ b/frontend/src/app/components/address-group/address-group.component.ts @@ -94,7 +94,7 @@ export class AddressGroupComponent implements OnInit, OnDestroy { - addressBalance.chain_stats.spent_txo_sum - addressBalance.mempool_stats.spent_txo_sum; this.balance += this.addresses[address]; - this.confirmed += this.addresses[address]; + this.confirmed += (addressBalance.chain_stats.funded_txo_sum - addressBalance.chain_stats.spent_txo_sum); } this.addressInfo[address] = addressData[2] ? addressData[2] as AddressInformation : null; } From 75578ac9fa5ac4b08bd19fa332bc184c16f09a3c Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 21 Nov 2023 17:20:11 +0900 Subject: [PATCH 11/26] Improve timeout handling in esplora api health monitoring & logs --- backend/src/api/bitcoin/esplora-api.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/backend/src/api/bitcoin/esplora-api.ts b/backend/src/api/bitcoin/esplora-api.ts index 2f4bcee85..b8e3784c7 100644 --- a/backend/src/api/bitcoin/esplora-api.ts +++ b/backend/src/api/bitcoin/esplora-api.ts @@ -1,5 +1,5 @@ import config from '../../config'; -import axios, { AxiosResponse } from 'axios'; +import axios, { AxiosResponse, isAxiosError } from 'axios'; import http from 'http'; import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory'; import { IEsploraApi } from './esplora-api.interface'; @@ -10,6 +10,7 @@ interface FailoverHost { host: string, rtts: number[], rtt: number, + timedOut?: boolean, failures: number, latestHeight?: number, socket?: boolean, @@ -108,11 +109,17 @@ class FailoverRouter { host.rtts = []; host.rtt = Infinity; } + host.timedOut = false; } catch (e) { host.outOfSync = true; host.unreachable = true; host.rtts = []; host.rtt = Infinity; + if (isAxiosError(e) && (e.code === 'ECONNABORTED' || e.code === 'ETIMEDOUT')) { + host.timedOut = true; + } else { + host.timedOut = false; + } } host.checked = true; @@ -143,7 +150,7 @@ class FailoverRouter { private formatRanking(index: number, host: FailoverHost, active: FailoverHost, maxHeight: number): string { const heightStatus = !host.checked ? '⏳' : (host.outOfSync ? '🚫' : (host.latestHeight && host.latestHeight < maxHeight ? '🟧' : '✅')); - return `${host === active ? '⭐️' : ' '} ${host.rtt < Infinity ? Math.round(host.rtt).toString().padStart(5, ' ') + 'ms' : ' - '} ${!host.checked ? '⏳' : (host.unreachable ? '🔥' : '✅')} | block: ${host.latestHeight || '??????'} ${heightStatus} | ${host.host} ${host === active ? '⭐️' : ' '}`; + return `${host === active ? '⭐️' : ' '} ${host.rtt < Infinity ? Math.round(host.rtt).toString().padStart(5, ' ') + 'ms' : (host.timedOut ? ' ⌛️💥 ' : ' - ')} ${!host.checked ? '⏳' : (host.unreachable ? '🔥' : '✅')} | block: ${host.latestHeight || '??????'} ${heightStatus} | ${host.host} ${host === active ? '⭐️' : ' '}`; } private updateFallback(): FailoverHost[] { From f63f1b177354d48dcf5bbccf6a7c9052e008b0d2 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 3 Mar 2024 20:29:54 +0000 Subject: [PATCH 12/26] 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 13/26] 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 14/26] 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 15/26] 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 16/26] 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, From 9e1fe71dc88dd039d36db6afcfc757dadb7113cb Mon Sep 17 00:00:00 2001 From: wiz Date: Tue, 5 Mar 2024 20:48:39 +0900 Subject: [PATCH 17/26] ops: Add MAX_TRACKED_ADDRESSES to prod configs --- production/mempool-config.mainnet.json | 3 ++- production/mempool-config.signet.json | 3 ++- production/mempool-config.testnet.json | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/production/mempool-config.mainnet.json b/production/mempool-config.mainnet.json index 47db0a322..5038d9bfb 100644 --- a/production/mempool-config.mainnet.json +++ b/production/mempool-config.mainnet.json @@ -22,7 +22,8 @@ "DISK_CACHE_BLOCK_INTERVAL": 1, "MAX_PUSH_TX_SIZE_WEIGHT": 4000000, "ALLOW_UNREACHABLE": true, - "PRICE_UPDATES_PER_HOUR": 12 + "PRICE_UPDATES_PER_HOUR": 12, + "MAX_TRACKED_ADDRESSES": 10 }, "SYSLOG" : { "MIN_PRIORITY": "debug" diff --git a/production/mempool-config.signet.json b/production/mempool-config.signet.json index 433e1e4a7..0a711d16f 100644 --- a/production/mempool-config.signet.json +++ b/production/mempool-config.signet.json @@ -15,7 +15,8 @@ "POLL_RATE_MS": 1000, "DISK_CACHE_BLOCK_INTERVAL": 1, "MAX_PUSH_TX_SIZE_WEIGHT": 4000000, - "ALLOW_UNREACHABLE": true + "ALLOW_UNREACHABLE": true, + "MAX_TRACKED_ADDRESSES": 10 }, "SYSLOG" : { "MIN_PRIORITY": "debug" diff --git a/production/mempool-config.testnet.json b/production/mempool-config.testnet.json index 742631025..adc93c0e9 100644 --- a/production/mempool-config.testnet.json +++ b/production/mempool-config.testnet.json @@ -15,7 +15,8 @@ "POLL_RATE_MS": 1000, "DISK_CACHE_BLOCK_INTERVAL": 1, "MAX_PUSH_TX_SIZE_WEIGHT": 4000000, - "ALLOW_UNREACHABLE": true + "ALLOW_UNREACHABLE": true, + "MAX_TRACKED_ADDRESSES": 10 }, "SYSLOG" : { "MIN_PRIORITY": "debug" From acb60fa1bca01d729b2bd220845b6b333ff5382d Mon Sep 17 00:00:00 2001 From: wiz Date: Tue, 5 Mar 2024 21:44:54 +0900 Subject: [PATCH 18/26] ops: Add check script to production folder --- production/check | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100755 production/check diff --git a/production/check b/production/check new file mode 100755 index 000000000..bbae2824b --- /dev/null +++ b/production/check @@ -0,0 +1,23 @@ +#!/usr/bin/env zsh + +check_frontend_git_commit_hash() { + echo -n $(curl -s https://node$1.$2.mempool.space/resources/config.js|grep GIT_COMMIT_HASH|cut -d "'" -f2|cut -c1-8) +} + +check_html_md5_hash() { + echo -n $(curl -s https://node$1.$2.mempool.space|md5|cut -c1-8) +} + +for site in fmt va1 fra tk7;do + echo "${site}" + for node in 201 202 203 204 205 206 207 208 209 210 211 212 213 214;do + [ "${site}" = "fmt" ] && [ "${node}" -gt 206 ] && continue + [ "${site}" = "tk7" ] && [ "${node}" -gt 206 ] && continue + echo -n "node${node}.${site}: " + check_frontend_git_commit_hash $node $site + echo -n " " + check_html_md5_hash $node $site + echo + done +done + From 0f1434e32da759fe143990956c763c69d283af56 Mon Sep 17 00:00:00 2001 From: junderw Date: Tue, 5 Mar 2024 22:32:14 +0900 Subject: [PATCH 19/26] Bump rust-gbt deps --- Cargo.lock | 85 ++++++++++++++++-------------- Cargo.toml | 1 + backend/package-lock.json | 17 +++--- backend/rust-gbt/Cargo.toml | 10 ++-- backend/rust-gbt/index.js | 43 +++++++++++++++ backend/rust-gbt/package-lock.json | 8 +-- backend/rust-gbt/package.json | 2 +- 7 files changed, 109 insertions(+), 57 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 30a0d97ab..0b51ea544 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,9 +57,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1586fa608b1dab41f667475b4a41faec5ba680aee428bfa5de4ea520fdc6e901" dependencies = [ "quote", - "syn 2.0.20", + "syn", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "gbt" version = "1.0.0" @@ -71,15 +77,15 @@ dependencies = [ "napi-derive", "priority-queue", "tracing", - "tracing-log", + "tracing-log 0.2.0", "tracing-subscriber", ] [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "hermit-abi" @@ -92,11 +98,11 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.3" +version = "2.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" dependencies = [ - "autocfg", + "equivalent", "hashbrown", ] @@ -114,12 +120,12 @@ checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" [[package]] name = "libloading" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "2caa5afb8bf9f3a2652760ce7d4f62d21c4d5a423e68466fca30df82f2330164" dependencies = [ "cfg-if", - "winapi", + "windows-targets", ] [[package]] @@ -145,9 +151,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "napi" -version = "2.13.2" +version = "2.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ede2d12cd6fce44da537a4be1f5510c73be2506c2e32dfaaafd1f36968f3a0e" +checksum = "54a63d0570e4c3e0daf7a8d380563610e159f538e20448d6c911337246f40e84" dependencies = [ "bitflags", "ctor", @@ -159,29 +165,29 @@ dependencies = [ [[package]] name = "napi-build" -version = "2.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882a73d9ef23e8dc2ebbffb6a6ae2ef467c0f18ac10711e4cc59c5485d41df0e" +checksum = "2f9130fccc5f763cf2069b34a089a18f0d0883c66aceb81f2fad541a3d823c43" [[package]] name = "napi-derive" -version = "2.13.0" +version = "2.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da1c6a8fa84d549aa8708fcd062372bf8ec6e849de39016ab921067d21bde367" +checksum = "05bb7c37e3c1dda9312fdbe4a9fc7507fca72288ba154ec093e2d49114e727ce" dependencies = [ "cfg-if", "convert_case", "napi-derive-backend", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "napi-derive-backend" -version = "1.0.52" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20bbc7c69168d06a848f925ec5f0e0997f98e8c8d4f2cc30157f0da51c009e17" +checksum = "f785a8b8d7b83e925f5aa6d2ae3c159d17fe137ac368dc185bef410e7acdaeb4" dependencies = [ "convert_case", "once_cell", @@ -189,14 +195,14 @@ dependencies = [ "quote", "regex", "semver", - "syn 1.0.109", + "syn", ] [[package]] name = "napi-sys" -version = "2.2.3" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "166b5ef52a3ab5575047a9fe8d4a030cdd0f63c96f071cd6907674453b07bae3" +checksum = "2503fa6af34dc83fb74888df8b22afe933b58d37daf7d80424b1c60c68196b8b" dependencies = [ "libloading", ] @@ -223,9 +229,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "overload" @@ -241,11 +247,12 @@ checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "priority-queue" -version = "1.3.2" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff39edfcaec0d64e8d0da38564fad195d2d51b680940295fcc307366e101e61" +checksum = "509354d8a769e8d0b567d6821b84495c60213162761a732d68ce87c964bd347f" dependencies = [ "autocfg", + "equivalent", "indexmap", ] @@ -320,17 +327,6 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.20" @@ -384,7 +380,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.20", + "syn", ] [[package]] @@ -408,6 +404,17 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.17" @@ -423,7 +430,7 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log", + "tracing-log 0.1.3", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 59562297c..2f70699f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "2" members = [ "./backend/rust-gbt", ] diff --git a/backend/package-lock.json b/backend/package-lock.json index 8c18dc8c2..95a949ef5 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -9,6 +9,7 @@ "version": "3.0.0-dev", "license": "GNU Affero General Public License v3.0", "dependencies": { + "@babel/core": "^7.24.0", "@mempool/electrum-client": "1.1.9", "@types/node": "^18.15.3", "axios": "~1.6.1", @@ -1499,9 +1500,9 @@ } }, "node_modules/@napi-rs/cli": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.16.1.tgz", - "integrity": "sha512-L0Gr5iEQIDEbvWdDr1HUaBOxBSHL1VZhWSk1oryawoT8qJIY+KGfLFelU+Qma64ivCPbxYpkfPoKYVG3rcoGIA==", + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.18.0.tgz", + "integrity": "sha512-lfSRT7cs3iC4L+kv9suGYQEezn5Nii7Kpu+THsYVI0tA1Vh59LH45p4QADaD7hvIkmOz79eEGtoKQ9nAkAPkzA==", "bin": { "napi": "scripts/index.js" }, @@ -7669,7 +7670,7 @@ "version": "3.0.1", "hasInstallScript": true, "dependencies": { - "@napi-rs/cli": "2.16.1" + "@napi-rs/cli": "2.18.0" }, "engines": { "node": ">= 12" @@ -8774,9 +8775,9 @@ "integrity": "sha512-mlvPiCzUlaETpYW3i6V87A24jjMYgsebaXtUo3WQyyLnYUuxs0KiXQ2mnKh3h15j8Xg/hfxeGIi+5OC9u0nftQ==" }, "@napi-rs/cli": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.16.1.tgz", - "integrity": "sha512-L0Gr5iEQIDEbvWdDr1HUaBOxBSHL1VZhWSk1oryawoT8qJIY+KGfLFelU+Qma64ivCPbxYpkfPoKYVG3rcoGIA==" + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.18.0.tgz", + "integrity": "sha512-lfSRT7cs3iC4L+kv9suGYQEezn5Nii7Kpu+THsYVI0tA1Vh59LH45p4QADaD7hvIkmOz79eEGtoKQ9nAkAPkzA==" }, "@noble/hashes": { "version": "1.3.0", @@ -12702,7 +12703,7 @@ "rust-gbt": { "version": "file:rust-gbt", "requires": { - "@napi-rs/cli": "2.16.1" + "@napi-rs/cli": "2.18.0" } }, "safe-buffer": { diff --git a/backend/rust-gbt/Cargo.toml b/backend/rust-gbt/Cargo.toml index 4d0a5b45d..10c572bf9 100644 --- a/backend/rust-gbt/Cargo.toml +++ b/backend/rust-gbt/Cargo.toml @@ -12,14 +12,14 @@ crate-type = ["cdylib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -priority-queue = "1.3.2" +priority-queue = "2.0.2" bytes = "1.4.0" -napi = { version = "2.13.2", features = ["napi8", "tokio_rt"] } -napi-derive = "2.13.0" +napi = { version = "2.16.0", features = ["napi8", "tokio_rt"] } +napi-derive = "2.16.0" bytemuck = "1.13.1" tracing = "0.1.36" -tracing-log = "0.1.3" +tracing-log = "0.2.0" tracing-subscriber = { version = "0.3.15", features = ["env-filter"]} [build-dependencies] -napi-build = "2.0.1" +napi-build = "2.1.2" diff --git a/backend/rust-gbt/index.js b/backend/rust-gbt/index.js index 8680501d1..dd58a8b76 100644 --- a/backend/rust-gbt/index.js +++ b/backend/rust-gbt/index.js @@ -237,6 +237,49 @@ switch (platform) { loadError = e } break + case 'riscv64': + if (isMusl()) { + localFileExisted = existsSync( + join(__dirname, 'gbt.linux-riscv64-musl.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./gbt.linux-riscv64-musl.node') + } else { + nativeBinding = require('gbt-linux-riscv64-musl') + } + } catch (e) { + loadError = e + } + } else { + localFileExisted = existsSync( + join(__dirname, 'gbt.linux-riscv64-gnu.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./gbt.linux-riscv64-gnu.node') + } else { + nativeBinding = require('gbt-linux-riscv64-gnu') + } + } catch (e) { + loadError = e + } + } + break + case 's390x': + localFileExisted = existsSync( + join(__dirname, 'gbt.linux-s390x-gnu.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./gbt.linux-s390x-gnu.node') + } else { + nativeBinding = require('gbt-linux-s390x-gnu') + } + } catch (e) { + loadError = e + } + break default: throw new Error(`Unsupported architecture on Linux: ${arch}`) } diff --git a/backend/rust-gbt/package-lock.json b/backend/rust-gbt/package-lock.json index ab3d72e52..e351c82f8 100644 --- a/backend/rust-gbt/package-lock.json +++ b/backend/rust-gbt/package-lock.json @@ -9,16 +9,16 @@ "version": "3.0.1", "hasInstallScript": true, "dependencies": { - "@napi-rs/cli": "2.16.1" + "@napi-rs/cli": "2.18.0" }, "engines": { "node": ">= 12" } }, "node_modules/@napi-rs/cli": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.16.1.tgz", - "integrity": "sha512-L0Gr5iEQIDEbvWdDr1HUaBOxBSHL1VZhWSk1oryawoT8qJIY+KGfLFelU+Qma64ivCPbxYpkfPoKYVG3rcoGIA==", + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.18.0.tgz", + "integrity": "sha512-lfSRT7cs3iC4L+kv9suGYQEezn5Nii7Kpu+THsYVI0tA1Vh59LH45p4QADaD7hvIkmOz79eEGtoKQ9nAkAPkzA==", "bin": { "napi": "scripts/index.js" }, diff --git a/backend/rust-gbt/package.json b/backend/rust-gbt/package.json index aa98313ed..b0dd96698 100644 --- a/backend/rust-gbt/package.json +++ b/backend/rust-gbt/package.json @@ -25,7 +25,7 @@ } }, "dependencies": { - "@napi-rs/cli": "2.16.1" + "@napi-rs/cli": "2.18.0" }, "engines": { "node": ">= 12" From 71863e4f48b77fadda73c3f9f4be4637c43612b6 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 5 Mar 2024 23:16:20 +0000 Subject: [PATCH 20/26] Polish /nodes and /network pages --- .../server-health.component.html | 18 +++++---- .../server-health.component.scss | 40 ++++++++++++++++++- .../server-health/server-health.component.ts | 23 ++++++++++- .../server-status.component.html | 12 +++--- .../server-health/server-status.component.ts | 4 +- .../src/app/interfaces/websocket.interface.ts | 1 + 6 files changed, 81 insertions(+), 17 deletions(-) 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 f376b4314..0350997e1 100644 --- a/frontend/src/app/components/server-health/server-health.component.html +++ b/frontend/src/app/components/server-health/server-health.component.html @@ -7,18 +7,22 @@
- +
- + + - + + - - - - + + + + + + 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 ebc6a883c..572628a9d 100644 --- a/frontend/src/app/components/server-health/server-health.component.scss +++ b/frontend/src/app/components/server-health/server-health.component.scss @@ -26,9 +26,47 @@ td, th { padding: 0.25em; - &.rtt, &.height { + + &.rank, &.flag { + width: 28px; text-align: right; } + &.rtt, &.height { + width: 92px; + text-align: right; + } + &.only-small { + display: table-cell; + &.rtt { + width: 60px; + } + } + &.only-large { + display: none; + } + &.height { + padding-right: 0.5em; + } + &.host { + width: auto; + overflow: hidden; + text-overflow: ellipsis; + } + + @media (min-width: 576px) { + &.rank, &.flag { + width: 32px; + } + &.rtt, &.height { + width: 96px; + } + &.only-small { + display: none; + } + &.only-large { + display: table-cell; + } + } } } } \ 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 b1008a4a5..363b28111 100644 --- a/frontend/src/app/components/server-health/server-health.component.ts +++ b/frontend/src/app/components/server-health/server-health.component.ts @@ -29,8 +29,8 @@ export class ServerHealthComponent implements OnInit { let statusUrl = ''; let linkHost = ''; if (host.socket) { - statusUrl = window.location.host + subpath + '/status'; - linkHost = window.location.host + subpath; + statusUrl = 'https://' + window.location.hostname + subpath + '/status'; + linkHost = window.location.hostname + subpath; } else { const hostUrl = new URL(host.host); statusUrl = 'https://' + hostUrl.hostname + subpath + '/status'; @@ -38,6 +38,7 @@ export class ServerHealthComponent implements OnInit { } host.statusPage = this.sanitizer.bypassSecurityTrustResourceUrl(this.sanitizer.sanitize(SecurityContext.URL, statusUrl)); host.link = linkHost; + host.flag = this.parseFlag(host.host); } return hosts; }) @@ -45,4 +46,22 @@ export class ServerHealthComponent implements OnInit { this.tip$ = this.stateService.chainTip$; this.websocketService.want(['blocks', 'tomahawk']); } + + trackByFn(index: number, host: HealthCheckHost): string { + return host.host; + } + + private parseFlag(host: string): string { + if (host.includes('.fra.')) { + return '🇩🇪'; + } else if (host.includes('.tk7.')) { + return '🇯🇵'; + } else if (host.includes('.fmt.')) { + return '🇺🇸'; + } else if (host.includes('.va1.')) { + return '🇺🇸'; + } else { + return ''; + } + } } diff --git a/frontend/src/app/components/server-health/server-status.component.html b/frontend/src/app/components/server-health/server-status.component.html index 484d774a0..23ca16a61 100644 --- a/frontend/src/app/components/server-health/server-status.component.html +++ b/frontend/src/app/components/server-health/server-status.component.html @@ -1,9 +1,11 @@ -
-
+ @@ -21,6 +24,7 @@ + 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 572628a9d..4cba0cd76 100644 --- a/frontend/src/app/components/server-health/server-health.component.scss +++ b/frontend/src/app/components/server-health/server-health.component.scss @@ -1,22 +1,16 @@ .tomahawk { .links { - float: right; text-align: right; - margin-top: 1em; + margin-inline-end: 1em; a, span { margin-left: 1em; } } - .dashboard-title { - text-align: left; - } - .status-panel { max-width: 720px; margin: auto; - margin-top: 2em; padding: 1em; background: #24273e; } @@ -31,6 +25,12 @@ width: 28px; text-align: right; } + &.updated { + display: none; + width: 130px; + text-align: right; + white-space: pre-wrap; + } &.rtt, &.height { width: 92px; text-align: right; @@ -57,6 +57,9 @@ &.rank, &.flag { width: 32px; } + &.updated { + display: table-cell; + } &.rtt, &.height { width: 96px; } 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 363b28111..bb4d59959 100644 --- a/frontend/src/app/components/server-health/server-health.component.ts +++ b/frontend/src/app/components/server-health/server-health.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, ChangeDetectionStrategy, SecurityContext } from '@angular/core'; +import { Component, OnInit, ChangeDetectionStrategy, SecurityContext, ChangeDetectorRef } from '@angular/core'; import { WebsocketService } from '../../services/websocket.service'; import { Observable, Subject, map } from 'rxjs'; import { StateService } from '../../services/state.service'; @@ -14,17 +14,20 @@ import { DomSanitizer } from '@angular/platform-browser'; export class ServerHealthComponent implements OnInit { hosts$: Observable; tip$: Subject; + interval: number; + now: number = Date.now(); constructor( private websocketService: WebsocketService, private stateService: StateService, + private cd: ChangeDetectorRef, public sanitizer: DomSanitizer, ) {} ngOnInit(): void { this.hosts$ = this.stateService.serverHealth$.pipe( map((hosts) => { - const subpath = window.location.pathname.slice(0, -6); + const subpath = window.location.pathname.slice(0, -11); for (const host of hosts) { let statusUrl = ''; let linkHost = ''; @@ -44,13 +47,27 @@ export class ServerHealthComponent implements OnInit { }) ); this.tip$ = this.stateService.chainTip$; - this.websocketService.want(['blocks', 'tomahawk']); + this.websocketService.want(['mempool-blocks', 'stats', 'blocks', 'tomahawk']); + + this.interval = window.setInterval(() => { + this.now = Date.now(); + this.cd.markForCheck(); + }, 1000); } trackByFn(index: number, host: HealthCheckHost): string { return host.host; } + getLastUpdateSeconds(host: HealthCheckHost): string { + if (host.lastChecked) { + const seconds = Math.ceil((this.now - host.lastChecked) / 1000); + return `${seconds} second${seconds > 1 ? 's' : ' '} ago`; + } else { + return '~'; + } + } + private parseFlag(host: string): string { if (host.includes('.fra.')) { return '🇩🇪'; diff --git a/frontend/src/app/components/server-health/server-status.component.html b/frontend/src/app/components/server-health/server-status.component.html index 23ca16a61..7e8a5cc50 100644 --- a/frontend/src/app/components/server-health/server-status.component.html +++ b/frontend/src/app/components/server-health/server-status.component.html @@ -1,12 +1,12 @@
-
- -

Live Network

+ + + +
HostRTTRTTRTT Height
⭐️{{ host.host }}{{ host.rtt | number : '1.0-0' }} {{ host.rtt == null ? '' : 'ms'}} {{ !host.checked ? '⏳' : (host.unreachable ? '🔥' : '✅') }}
{{ i + 1 }}{{ host.active ? '⭐️' : host.flag }}{{ host.link }}{{ (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 < tip ? '🟧' : '✅')) }}
HostLast checked RTT RTT Height{{ i + 1 }} {{ host.active ? '⭐️' : host.flag }} {{ host.link }}{{ getLastUpdateSeconds(host) }} {{ (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 < tip ? '🟧' : '✅')) }}