From ad753b9d168682303da681e6d08b75754006f01a Mon Sep 17 00:00:00 2001 From: Wes van der Vleuten <16665772+WesVleuten@users.noreply.github.com> Date: Thu, 1 Sep 2022 05:51:23 +0200 Subject: [PATCH 001/109] Added missing backend docker config --- docker/backend/mempool-config.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docker/backend/mempool-config.json b/docker/backend/mempool-config.json index b5b1a16b7..9ac2d4f7a 100644 --- a/docker/backend/mempool-config.json +++ b/docker/backend/mempool-config.json @@ -67,6 +67,22 @@ "ENABLED": __BISQ_ENABLED__, "DATA_PATH": "__BISQ_DATA_PATH__" }, + "LIGHTNING": { + "ENABLED": __LIGHTNING_ENABLED__, + "BACKEND": "__LIGHTNING_BACKEND__", + "STATS_REFRESH_INTERVAL": __LIGHTNING_STATS_REFRESH_INTERVAL__, + "GRAPH_REFRESH_INTERVAL": __LIGHTNING_GRAPH_REFRESH_INTERVAL__, + "LOGGER_UPDATE_INTERVAL": __LIGHTNING_LOGGER_UPDATE_INTERVAL__, + "TOPOLOGY_FOLDER": "__LIGHTNING_TOPOLOGY_FOLDER__" + }, + "LND": { + "TLS_CERT_PATH": "__LND_TLS_CERT_PATH__", + "MACAROON_PATH": "__LND_MACAROON_PATH__", + "REST_API_URL": "__LND_REST_API_URL__" + }, + "CLIGHTNING": { + "SOCKET": "__CLN_SOCKET__" + }, "SOCKS5PROXY": { "ENABLED": __SOCKS5PROXY_ENABLED__, "USE_ONION": __SOCKS5PROXY_USE_ONION__, From 95cd01d1fa9ae482d47f76078ab3a2c7bc8baf67 Mon Sep 17 00:00:00 2001 From: Wes van der Vleuten <16665772+WesVleuten@users.noreply.github.com> Date: Thu, 1 Sep 2022 06:56:05 +0200 Subject: [PATCH 002/109] Accept the CLA for @WesVleuten --- contributors/WesVleuten.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 contributors/WesVleuten.txt diff --git a/contributors/WesVleuten.txt b/contributors/WesVleuten.txt new file mode 100644 index 000000000..a13bde140 --- /dev/null +++ b/contributors/WesVleuten.txt @@ -0,0 +1,3 @@ +I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of September 1, 2022. + +Signed: WesVleuten From 206edb76139a1803e6237b53fbecfc56261fb495 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Sun, 4 Sep 2022 12:02:40 +0200 Subject: [PATCH 003/109] Show tooltip on location is truncated --- .../components/geolocation/geolocation.component.html | 9 ++++++++- .../components/geolocation/geolocation.component.ts | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/shared/components/geolocation/geolocation.component.html b/frontend/src/app/shared/components/geolocation/geolocation.component.html index c63af47be..b772ddfd4 100644 --- a/frontend/src/app/shared/components/geolocation/geolocation.component.html +++ b/frontend/src/app/shared/components/geolocation/geolocation.component.html @@ -1 +1,8 @@ - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/frontend/src/app/shared/components/geolocation/geolocation.component.ts b/frontend/src/app/shared/components/geolocation/geolocation.component.ts index d1c02e53a..afcea3b1e 100644 --- a/frontend/src/app/shared/components/geolocation/geolocation.component.ts +++ b/frontend/src/app/shared/components/geolocation/geolocation.component.ts @@ -80,4 +80,8 @@ export class GeolocationComponent implements OnChanges { return; } } + + isEllipsisActive(e): boolean { + return (e.offsetWidth < e.scrollWidth); + } } From 50ae075b1f1b8f22c271b60be667b2bca2041222 Mon Sep 17 00:00:00 2001 From: Wes van der Vleuten <16665772+WesVleuten@users.noreply.github.com> Date: Mon, 5 Sep 2022 19:02:13 +0200 Subject: [PATCH 004/109] Fixed CLN to CLIGHTNING --- docker/README.md | 4 ++-- docker/backend/mempool-config.json | 2 +- docker/backend/start.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/README.md b/docker/README.md index affe1d460..92cc1df6d 100644 --- a/docker/README.md +++ b/docker/README.md @@ -403,7 +403,7 @@ Corresponding `docker-compose.yml` overrides: `mempool-config.json`: ``` - "CLN": { + "CLIGHTNING": { "SOCKET": "" } ``` @@ -412,6 +412,6 @@ Corresponding `docker-compose.yml` overrides: ``` api: environment: - CLN_SOCKET: "" + CLIGHTNING_SOCKET: "" ... ``` diff --git a/docker/backend/mempool-config.json b/docker/backend/mempool-config.json index 9ac2d4f7a..930430127 100644 --- a/docker/backend/mempool-config.json +++ b/docker/backend/mempool-config.json @@ -81,7 +81,7 @@ "REST_API_URL": "__LND_REST_API_URL__" }, "CLIGHTNING": { - "SOCKET": "__CLN_SOCKET__" + "SOCKET": "__CLIGHTNING_SOCKET__" }, "SOCKS5PROXY": { "ENABLED": __SOCKS5PROXY_ENABLED__, diff --git a/docker/backend/start.sh b/docker/backend/start.sh index 8e6a34013..47b768e7d 100644 --- a/docker/backend/start.sh +++ b/docker/backend/start.sh @@ -105,7 +105,7 @@ __LND_MACAROON_PATH__=${LND_MACAROON_PATH:=""} __LND_REST_API_URL__=${LND_REST_API_URL:="https://localhost:8080"} # CLN -__CLN_SOCKET__=${CLN_SOCKET:=""} +__CLIGHTNING_SOCKET__=${CLIGHTNING_SOCKET:=""} mkdir -p "${__MEMPOOL_CACHE_DIR__}" From 5eab47674ceae593d7a60d5628edc2833fc39f7d Mon Sep 17 00:00:00 2001 From: Wes van der Vleuten <16665772+WesVleuten@users.noreply.github.com> Date: Mon, 5 Sep 2022 19:24:34 +0200 Subject: [PATCH 005/109] Fixed forgotten CLN instance --- docker/backend/start.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/backend/start.sh b/docker/backend/start.sh index 47b768e7d..71c59cb3d 100644 --- a/docker/backend/start.sh +++ b/docker/backend/start.sh @@ -203,6 +203,6 @@ sed -i "s!__LND_MACAROON_PATH__!${__LND_MACAROON_PATH__}!g" mempool-config.json sed -i "s!__LND_REST_API_URL__!${__LND_REST_API_URL__}!g" mempool-config.json # CLN -sed -i "s!__CLN_SOCKET__!${__CLN_SOCKET__}!g" mempool-config.json +sed -i "s!__CLIGHTNING_SOCKET__!${__CLIGHTNING_SOCKET__}!g" mempool-config.json node /backend/dist/index.js From aa1519c18eb06c0917fefb84940583126a3cf37c Mon Sep 17 00:00:00 2001 From: nymkappa Date: Thu, 8 Sep 2022 18:56:25 +0200 Subject: [PATCH 006/109] Show zero base fee tag on channels --- .../channel-box/channel-box.component.html | 103 +++++++++++------- .../app/shared/pipes/amount-shortener.pipe.ts | 3 +- 2 files changed, 68 insertions(+), 38 deletions(-) diff --git a/frontend/src/app/lightning/channel/channel-box/channel-box.component.html b/frontend/src/app/lightning/channel/channel-box/channel-box.component.html index 8b486eff5..4a421480d 100644 --- a/frontend/src/app/lightning/channel/channel-box/channel-box.component.html +++ b/frontend/src/app/lightning/channel/channel-box/channel-box.component.html @@ -11,44 +11,73 @@
-
-
- - - - - - - - - - - - - - - - - - - - - - - -
Fee rate - {{ channel.fee_rate ?? '-' }} ppm ({{ channel.fee_rate / 10000 | number }}%) -
Base fee - -
Min HTLC - -
Max HTLC - -
Timelock delta - -
-
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Fee rate + + {{ channel.fee_rate !== null ? (channel.fee_rate | amountShortener : 2 : undefined : true) : '-' }} ppm ({{ channel.fee_rate !== null ? '(' + (channel.fee_rate / 10000 | amountShortener : 2 : undefined : true) + '%)' : '' }} + + + {{ channel.fee_rate !== null ? (channel.fee_rate | number) : '-' }} ppm {{ channel.fee_rate !== null ? '(' + (channel.fee_rate / 10000 | number) + '%)' : '' }} + +
Base fee +
+ + + {{ channel.base_fee_mtokens !== null ? (channel.base_fee_mtokens / 1000 | amountShortener : 2 : undefined : true) : '-' }} + sats + + + Zero base fee + + +
+
+ + + + Zero base fee + + +
+
Min HTLC + +
Max HTLC + +
Timelock delta + +
{{ i }} blocks diff --git a/frontend/src/app/shared/pipes/amount-shortener.pipe.ts b/frontend/src/app/shared/pipes/amount-shortener.pipe.ts index db3d94284..71ff76f77 100644 --- a/frontend/src/app/shared/pipes/amount-shortener.pipe.ts +++ b/frontend/src/app/shared/pipes/amount-shortener.pipe.ts @@ -7,6 +7,7 @@ export class AmountShortenerPipe implements PipeTransform { transform(num: number, ...args: any[]): unknown { const digits = args[0] ?? 1; const unit = args[1] || undefined; + const isMoney = args[2] || false; if (num < 1000) { return num.toFixed(digits); @@ -16,7 +17,7 @@ export class AmountShortenerPipe implements PipeTransform { { value: 1, symbol: '' }, { value: 1e3, symbol: 'k' }, { value: 1e6, symbol: 'M' }, - { value: 1e9, symbol: 'G' }, + { value: 1e9, symbol: isMoney ? 'B' : 'G' }, { value: 1e12, symbol: 'T' }, { value: 1e15, symbol: 'P' }, { value: 1e18, symbol: 'E' } From faec398cf02e15bb628da434d665249e9c313078 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Wed, 24 Aug 2022 15:39:52 +0200 Subject: [PATCH 007/109] Log correct maxmind mysql updates - fix stats import processed files counter --- .../lightning/sync-tasks/node-locations.ts | 45 +++++++++++++++---- .../lightning/sync-tasks/stats-importer.ts | 3 ++ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/backend/src/tasks/lightning/sync-tasks/node-locations.ts b/backend/src/tasks/lightning/sync-tasks/node-locations.ts index 9069e0fff..ba59e9e48 100644 --- a/backend/src/tasks/lightning/sync-tasks/node-locations.ts +++ b/backend/src/tasks/lightning/sync-tasks/node-locations.ts @@ -4,11 +4,14 @@ import nodesApi from '../../../api/explorer/nodes.api'; import config from '../../../config'; import DB from '../../../database'; import logger from '../../../logger'; +import { ResultSetHeader } from 'mysql2'; import * as IPCheck from '../../../utils/ipcheck.js'; export async function $lookupNodeLocation(): Promise { let loggerTimer = new Date().getTime() / 1000; let progress = 0; + let nodesUpdated = 0; + let geoNamesInserted = 0; logger.info(`Running node location updater using Maxmind`); try { @@ -71,51 +74,72 @@ export async function $lookupNodeLocation(): Promise { city.location?.accuracy_radius, node.public_key ]; - await DB.query(query, params); + let result = await DB.query(query, params); + if (result[0].changedRows ?? 0 > 0) { + ++nodesUpdated; + } // Store Continent if (city.continent?.geoname_id) { - await DB.query( + result = await DB.query( `INSERT IGNORE INTO geo_names (id, type, names) VALUES (?, 'continent', ?)`, [city.continent?.geoname_id, JSON.stringify(city.continent?.names)]); + if (result[0].changedRows ?? 0 > 0) { + ++geoNamesInserted; + } } // Store Country if (city.country?.geoname_id) { - await DB.query( + result = await DB.query( `INSERT IGNORE INTO geo_names (id, type, names) VALUES (?, 'country', ?)`, [city.country?.geoname_id, JSON.stringify(city.country?.names)]); + if (result[0].changedRows ?? 0 > 0) { + ++geoNamesInserted; + } } // Store Country ISO code if (city.country?.iso_code) { - await DB.query( + result = await DB.query( `INSERT IGNORE INTO geo_names (id, type, names) VALUES (?, 'country_iso_code', ?)`, [city.country?.geoname_id, city.country?.iso_code]); + if (result[0].changedRows ?? 0 > 0) { + ++geoNamesInserted; + } } // Store Division if (city.subdivisions && city.subdivisions[0]) { - await DB.query( + result = await DB.query( `INSERT IGNORE INTO geo_names (id, type, names) VALUES (?, 'division', ?)`, [city.subdivisions[0].geoname_id, JSON.stringify(city.subdivisions[0]?.names)]); + if (result[0].changedRows ?? 0 > 0) { + ++geoNamesInserted; + } } // Store City if (city.city?.geoname_id) { - await DB.query( + result = await DB.query( `INSERT IGNORE INTO geo_names (id, type, names) VALUES (?, 'city', ?)`, [city.city?.geoname_id, JSON.stringify(city.city?.names)]); + if (result[0].changedRows ?? 0 > 0) { + ++geoNamesInserted; + } } // Store AS name if (isp?.autonomous_system_organization ?? asn?.autonomous_system_organization) { - await DB.query( + result = await DB.query( `INSERT IGNORE INTO geo_names (id, type, names) VALUES (?, 'as_organization', ?)`, [ asOverwrite?.asn ?? isp?.autonomous_system_number ?? asn?.autonomous_system_number, JSON.stringify(asOverwrite?.name ?? isp?.isp ?? asn?.autonomous_system_organization) ]); + if (result[0].changedRows ?? 0 > 0) { + ++geoNamesInserted; + } } } @@ -128,7 +152,12 @@ export async function $lookupNodeLocation(): Promise { } } } - logger.info(`${progress} nodes location data updated`); + + if (nodesUpdated > 0) { + logger.info(`${nodesUpdated} nodes maxmind data updated, ${geoNamesInserted} geo names inserted`); + } else { + logger.debug(`${nodesUpdated} nodes maxmind data updated, ${geoNamesInserted} geo names inserted`); + } } catch (e) { logger.err('$lookupNodeLocation() error: ' + (e instanceof Error ? e.message : e)); } diff --git a/backend/src/tasks/lightning/sync-tasks/stats-importer.ts b/backend/src/tasks/lightning/sync-tasks/stats-importer.ts index e3dfe6652..6d6c9e4d3 100644 --- a/backend/src/tasks/lightning/sync-tasks/stats-importer.ts +++ b/backend/src/tasks/lightning/sync-tasks/stats-importer.ts @@ -360,9 +360,11 @@ class LightningStatsImporter { fileContent = await fsPromises.readFile(`${this.topologiesFolder}/${filename}`, 'utf8'); } catch (e: any) { if (e.errno == -1) { // EISDIR - Ignore directorie + totalProcessed++; continue; } logger.err(`Unable to open ${this.topologiesFolder}/${filename}`); + totalProcessed++; continue; } @@ -372,6 +374,7 @@ class LightningStatsImporter { graph = await this.cleanupTopology(graph); } catch (e) { logger.debug(`Invalid topology file ${this.topologiesFolder}/${filename}, cannot parse the content. Reason: ${e instanceof Error ? e.message : e}`); + totalProcessed++; continue; } From 67675e1f79ff2bad1760854b272c766697fcd8d9 Mon Sep 17 00:00:00 2001 From: softsimon Date: Fri, 9 Sep 2022 22:39:10 +0200 Subject: [PATCH 008/109] Dashboard node statistics skeleton loader fix --- .../node-statistics/node-statistics.component.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/lightning/node-statistics/node-statistics.component.html b/frontend/src/app/lightning/node-statistics/node-statistics.component.html index ae4ea3dd7..74c14c8b0 100644 --- a/frontend/src/app/lightning/node-statistics/node-statistics.component.html +++ b/frontend/src/app/lightning/node-statistics/node-statistics.component.html @@ -43,6 +43,13 @@
+
+
Capacity
+
+
+
+
+
Nodes
@@ -57,12 +64,5 @@
-
-
Average Channel
-
-
-
-
-
\ No newline at end of file From 9a5844bbdc3077bb53f80a05fc9f38a372a40cc2 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sat, 10 Sep 2022 01:00:45 +0000 Subject: [PATCH 009/109] Refactor preview routes into separate module --- frontend/src/app/app-routing.module.ts | 88 +++---------------- .../lightning/node/node-preview.component.ts | 2 +- frontend/src/app/previews.module.ts | 26 ++++++ frontend/src/app/previews.routing.module.ts | 39 ++++++++ frontend/src/app/shared/shared.module.ts | 11 --- 5 files changed, 76 insertions(+), 90 deletions(-) create mode 100644 frontend/src/app/previews.module.ts create mode 100644 frontend/src/app/previews.routing.module.ts diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 2d12bc2e7..260efcafe 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -3,14 +3,10 @@ import { Routes, RouterModule } from '@angular/router'; import { AppPreloadingStrategy } from './app.preloading-strategy' import { StartComponent } from './components/start/start.component'; import { TransactionComponent } from './components/transaction/transaction.component'; -import { TransactionPreviewComponent } from './components/transaction/transaction-preview.component'; import { BlockComponent } from './components/block/block.component'; import { BlockAuditComponent } from './components/block-audit/block-audit.component'; -import { BlockPreviewComponent } from './components/block/block-preview.component'; import { AddressComponent } from './components/address/address.component'; -import { AddressPreviewComponent } from './components/address/address-preview.component'; import { MasterPageComponent } from './components/master-page/master-page.component'; -import { MasterPagePreviewComponent } from './components/master-page-preview/master-page-preview.component'; import { AboutComponent } from './components/about/about.component'; import { StatusViewComponent } from './components/status-view/status-view.component'; import { TermsOfServiceComponent } from './components/terms-of-service/terms-of-service.component'; @@ -346,61 +342,18 @@ let routes: Routes = [ }, { path: 'preview', - component: MasterPagePreviewComponent, children: [ { - path: 'block/:id', - component: BlockPreviewComponent + path: '', + loadChildren: () => import('./previews.module').then(m => m.PreviewsModule) }, { - path: 'testnet/block/:id', - component: BlockPreviewComponent + path: 'testnet', + loadChildren: () => import('./previews.module').then(m => m.PreviewsModule) }, { - path: 'signet/block/:id', - component: BlockPreviewComponent - }, - { - path: 'address/:id', - children: [], - component: AddressPreviewComponent - }, - { - path: 'testnet/address/:id', - children: [], - component: AddressPreviewComponent - }, - { - path: 'signet/address/:id', - children: [], - component: AddressPreviewComponent - }, - { - path: 'tx/:id', - children: [], - component: TransactionPreviewComponent - }, - { - path: 'testnet/tx/:id', - children: [], - component: TransactionPreviewComponent - }, - { - path: 'signet/tx/:id', - children: [], - component: TransactionPreviewComponent - }, - { - path: 'lightning', - loadChildren: () => import('./lightning/lightning-previews.module').then(m => m.LightningPreviewsModule) - }, - { - path: 'testnet/lightning', - loadChildren: () => import('./lightning/lightning-previews.module').then(m => m.LightningPreviewsModule) - }, - { - path: 'signet/lightning', - loadChildren: () => import('./lightning/lightning-previews.module').then(m => m.LightningPreviewsModule) + path: 'signet', + loadChildren: () => import('./previews.module').then(m => m.PreviewsModule) }, ], }, @@ -643,35 +596,14 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { }, { path: 'preview', - component: MasterPagePreviewComponent, children: [ { - path: 'block/:id', - component: BlockPreviewComponent + path: '', + loadChildren: () => import('./previews.module').then(m => m.PreviewsModule) }, { - path: 'testnet/block/:id', - component: BlockPreviewComponent - }, - { - path: 'address/:id', - children: [], - component: AddressPreviewComponent - }, - { - path: 'testnet/address/:id', - children: [], - component: AddressPreviewComponent - }, - { - path: 'tx/:id', - children: [], - component: TransactionPreviewComponent - }, - { - path: 'testnet/tx/:id', - children: [], - component: TransactionPreviewComponent + path: 'testnet', + loadChildren: () => import('./previews.module').then(m => m.PreviewsModule) }, ], }, diff --git a/frontend/src/app/lightning/node/node-preview.component.ts b/frontend/src/app/lightning/node/node-preview.component.ts index c2c3ab72c..574ee51de 100644 --- a/frontend/src/app/lightning/node/node-preview.component.ts +++ b/frontend/src/app/lightning/node/node-preview.component.ts @@ -42,7 +42,7 @@ export class NodePreviewComponent implements OnInit { this.node$ = this.activatedRoute.paramMap .pipe( switchMap((params: ParamMap) => { - this.publicKey = params.get('public_key'); + this.publicKey = params.get('public_key'); this.openGraphService.waitFor('node-map-' + this.publicKey); this.openGraphService.waitFor('node-data-' + this.publicKey); return this.lightningApiService.getNode$(params.get('public_key')); diff --git a/frontend/src/app/previews.module.ts b/frontend/src/app/previews.module.ts new file mode 100644 index 000000000..166670ced --- /dev/null +++ b/frontend/src/app/previews.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from './shared/shared.module'; +import { RouterModule } from '@angular/router'; +import { GraphsModule } from './graphs/graphs.module'; +import { PreviewsRoutingModule } from './previews.routing.module'; +import { TransactionPreviewComponent } from './components/transaction/transaction-preview.component'; +import { BlockPreviewComponent } from './components/block/block-preview.component'; +import { AddressPreviewComponent } from './components/address/address-preview.component'; +import { MasterPagePreviewComponent } from './components/master-page-preview/master-page-preview.component'; +@NgModule({ + declarations: [ + TransactionPreviewComponent, + BlockPreviewComponent, + AddressPreviewComponent, + MasterPagePreviewComponent, + ], + imports: [ + CommonModule, + SharedModule, + RouterModule, + GraphsModule, + PreviewsRoutingModule, + ], +}) +export class PreviewsModule { } diff --git a/frontend/src/app/previews.routing.module.ts b/frontend/src/app/previews.routing.module.ts new file mode 100644 index 000000000..5ac13c36d --- /dev/null +++ b/frontend/src/app/previews.routing.module.ts @@ -0,0 +1,39 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { TransactionPreviewComponent } from './components/transaction/transaction-preview.component'; +import { BlockPreviewComponent } from './components/block/block-preview.component'; +import { AddressPreviewComponent } from './components/address/address-preview.component'; +import { MasterPagePreviewComponent } from './components/master-page-preview/master-page-preview.component'; + +const routes: Routes = [ + { + path: '', + component: MasterPagePreviewComponent, + children: [ + { + path: 'block/:id', + component: BlockPreviewComponent + }, + { + path: 'address/:id', + children: [], + component: AddressPreviewComponent + }, + { + path: 'tx/:id', + children: [], + component: TransactionPreviewComponent + }, + { + path: 'lightning', + loadChildren: () => import('./lightning/lightning-previews.module').then(m => m.LightningPreviewsModule) + }, + ], + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class PreviewsRoutingModule { } diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 8c2c9e803..be4ba2fe0 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -7,7 +7,6 @@ import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, fa faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft } from '@fortawesome/free-solid-svg-icons'; import { InfiniteScrollModule } from 'ngx-infinite-scroll'; import { MasterPageComponent } from '../components/master-page/master-page.component'; -import { MasterPagePreviewComponent } from '../components/master-page-preview/master-page-preview.component'; import { PreviewTitleComponent } from '../components/master-page-preview/preview-title.component'; import { BisqMasterPageComponent } from '../components/bisq-master-page/bisq-master-page.component'; import { LiquidMasterPageComponent } from '../components/liquid-master-page/liquid-master-page.component'; @@ -44,15 +43,12 @@ import { RouterModule } from '@angular/router'; import { CapAddressPipe } from './pipes/cap-address-pipe/cap-address-pipe'; import { StartComponent } from '../components/start/start.component'; import { TransactionComponent } from '../components/transaction/transaction.component'; -import { TransactionPreviewComponent } from '../components/transaction/transaction-preview.component'; import { TransactionsListComponent } from '../components/transactions-list/transactions-list.component'; import { BlockComponent } from '../components/block/block.component'; -import { BlockPreviewComponent } from '../components/block/block-preview.component'; import { BlockAuditComponent } from '../components/block-audit/block-audit.component'; import { BlockOverviewGraphComponent } from '../components/block-overview-graph/block-overview-graph.component'; import { BlockOverviewTooltipComponent } from '../components/block-overview-tooltip/block-overview-tooltip.component'; import { AddressComponent } from '../components/address/address.component'; -import { AddressPreviewComponent } from '../components/address/address-preview.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'; @@ -117,21 +113,17 @@ import { GeolocationComponent } from '../shared/components/geolocation/geolocati AmountComponent, AboutComponent, MasterPageComponent, - MasterPagePreviewComponent, PreviewTitleComponent, BisqMasterPageComponent, LiquidMasterPageComponent, StartComponent, TransactionComponent, - TransactionPreviewComponent, BlockComponent, - BlockPreviewComponent, BlockAuditComponent, BlockOverviewGraphComponent, BlockOverviewTooltipComponent, TransactionsListComponent, AddressComponent, - AddressPreviewComponent, SearchFormComponent, TimeSpanComponent, AddressLabelsComponent, @@ -228,15 +220,12 @@ import { GeolocationComponent } from '../shared/components/geolocation/geolocati AmountComponent, StartComponent, TransactionComponent, - TransactionPreviewComponent, BlockComponent, - BlockPreviewComponent, BlockAuditComponent, BlockOverviewGraphComponent, BlockOverviewTooltipComponent, TransactionsListComponent, AddressComponent, - AddressPreviewComponent, SearchFormComponent, TimeSpanComponent, AddressLabelsComponent, From 7d367572dc817cb2cb875f47c5334ff7c9d4c747 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sat, 10 Sep 2022 14:53:52 +0000 Subject: [PATCH 010/109] Add lighting ISP preview --- .../lightning/lightning-previews.module.ts | 2 + .../lightning-previews.routing.module.ts | 5 + .../nodes-map/nodes-map.component.html | 4 +- .../nodes-map/nodes-map.component.scss | 11 ++ .../nodes-map/nodes-map.component.ts | 37 ++++++- .../nodes-per-isp-preview.component.html | 63 +++++++++++ .../nodes-per-isp-preview.component.scss | 31 ++++++ .../nodes-per-isp-preview.component.ts | 103 ++++++++++++++++++ unfurler/src/routes.ts | 11 ++ 9 files changed, 259 insertions(+), 8 deletions(-) create mode 100644 frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.html create mode 100644 frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.scss create mode 100644 frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.ts diff --git a/frontend/src/app/lightning/lightning-previews.module.ts b/frontend/src/app/lightning/lightning-previews.module.ts index 4d5d6cee9..0400acc55 100644 --- a/frontend/src/app/lightning/lightning-previews.module.ts +++ b/frontend/src/app/lightning/lightning-previews.module.ts @@ -8,10 +8,12 @@ import { LightningApiService } from './lightning-api.service'; import { NodePreviewComponent } from './node/node-preview.component'; import { LightningPreviewsRoutingModule } from './lightning-previews.routing.module'; import { ChannelPreviewComponent } from './channel/channel-preview.component'; +import { NodesPerISPPreview } from './nodes-per-isp/nodes-per-isp-preview.component'; @NgModule({ declarations: [ NodePreviewComponent, ChannelPreviewComponent, + NodesPerISPPreview, ], imports: [ CommonModule, diff --git a/frontend/src/app/lightning/lightning-previews.routing.module.ts b/frontend/src/app/lightning/lightning-previews.routing.module.ts index 69de2aadf..11250214d 100644 --- a/frontend/src/app/lightning/lightning-previews.routing.module.ts +++ b/frontend/src/app/lightning/lightning-previews.routing.module.ts @@ -2,6 +2,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { NodePreviewComponent } from './node/node-preview.component'; import { ChannelPreviewComponent } from './channel/channel-preview.component'; +import { NodesPerISPPreview } from './nodes-per-isp/nodes-per-isp-preview.component'; const routes: Routes = [ { @@ -12,6 +13,10 @@ const routes: Routes = [ path: 'channel/:short_id', component: ChannelPreviewComponent, }, + { + path: 'nodes/isp/:isp', + component: NodesPerISPPreview, + }, { path: '**', redirectTo: '' diff --git a/frontend/src/app/lightning/nodes-map/nodes-map.component.html b/frontend/src/app/lightning/nodes-map/nodes-map.component.html index d739dd2c9..f6a6f6009 100644 --- a/frontend/src/app/lightning/nodes-map/nodes-map.component.html +++ b/frontend/src/app/lightning/nodes-map/nodes-map.component.html @@ -1,4 +1,4 @@ -
+
@@ -8,7 +8,7 @@
+ (chartInit)="onChartInit($event)" (chartFinished)="onChartFinished($event)">
diff --git a/frontend/src/app/lightning/nodes-map/nodes-map.component.scss b/frontend/src/app/lightning/nodes-map/nodes-map.component.scss index d7ad42b46..7a67e4eb4 100644 --- a/frontend/src/app/lightning/nodes-map/nodes-map.component.scss +++ b/frontend/src/app/lightning/nodes-map/nodes-map.component.scss @@ -21,6 +21,17 @@ height: 240px; padding: 0px; } +.full-container.fit-container { + margin: 0; + padding: 0; + height: 100%; + min-height: 100px; + + .chart { + padding: 0; + min-height: 100px; + } +} .chart { width: 100%; diff --git a/frontend/src/app/lightning/nodes-map/nodes-map.component.ts b/frontend/src/app/lightning/nodes-map/nodes-map.component.ts index b783e225a..8ec853aaa 100644 --- a/frontend/src/app/lightning/nodes-map/nodes-map.component.ts +++ b/frontend/src/app/lightning/nodes-map/nodes-map.component.ts @@ -1,7 +1,7 @@ -import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, NgZone, OnDestroy, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject, Input, Output, EventEmitter, LOCALE_ID, NgZone, OnDestroy, OnInit, OnChanges } from '@angular/core'; import { SeoService } from 'src/app/services/seo.service'; import { ApiService } from 'src/app/services/api.service'; -import { Observable, tap, zip } from 'rxjs'; +import { Observable, BehaviorSubject, switchMap, tap, combineLatest } from 'rxjs'; import { AssetsService } from 'src/app/services/assets.service'; import { EChartsOption, registerMap } from 'echarts'; import { lerpColor } from 'src/app/shared/graphs.utils'; @@ -17,11 +17,14 @@ import { getFlagEmoji } from 'src/app/shared/common.utils'; styleUrls: ['./nodes-map.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class NodesMap implements OnInit { +export class NodesMap implements OnInit, OnChanges { @Input() widget: boolean = false; @Input() nodes: any[] | undefined = undefined; @Input() type: 'none' | 'isp' | 'country' = 'none'; - + @Input() fitContainer = false; + @Output() readyEvent = new EventEmitter(); + inputNodes$: BehaviorSubject; + nodes$: Observable; observable$: Observable; chartInstance = undefined; @@ -45,9 +48,17 @@ export class NodesMap implements OnInit { ngOnInit(): void { this.seoService.setTitle($localize`Lightning nodes world map`); - this.observable$ = zip( + if (!this.inputNodes$) { + this.inputNodes$ = new BehaviorSubject(this.nodes); + } + + this.nodes$ = this.inputNodes$.pipe( + switchMap((nodes) => nodes ? [nodes] : this.apiService.getWorldNodes$()) + ); + + this.observable$ = combineLatest( this.assetsService.getWorldMapJson$, - this.nodes ? [this.nodes] : this.apiService.getWorldNodes$() + this.nodes$ ).pipe(tap((data) => { registerMap('world', data[0]); @@ -110,6 +121,16 @@ export class NodesMap implements OnInit { })); } + ngOnChanges(changes): void { + if (changes.nodes) { + if (!this.inputNodes$) { + this.inputNodes$ = new BehaviorSubject(changes.nodes.currentValue); + } else { + this.inputNodes$.next(changes.nodes.currentValue); + } + } + } + prepareChartOptions(nodes, maxLiquidity, mapCenter, mapZoom) { let title: object; if (nodes.length === 0) { @@ -224,4 +245,8 @@ export class NodesMap implements OnInit { this.chartInstance.resize(); }); } + + onChartFinished(e) { + this.readyEvent.emit(); + } } diff --git a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.html b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.html new file mode 100644 index 000000000..4db69156f --- /dev/null +++ b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.html @@ -0,0 +1,63 @@ +
+ + lightning ISP + +
+
+

{{ isp?.name }}

+ + ASN {{ isp?.id }} + +
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
Nodes{{ ispNodes.nodes.length }}
Liquidity + + + + +
Channels{{ ispNodes.sumChannels }}
Top country + {{ ispNodes.topCountry.country }} {{ ispNodes.topCountry.flag }} +
Top node + {{ ispNodes.nodes[0].alias }} +
+
+
+ +
+
+
+ + +
+ Error loading data. +
+
diff --git a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.scss b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.scss new file mode 100644 index 000000000..2fe34ef5e --- /dev/null +++ b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.scss @@ -0,0 +1,31 @@ +.table { + font-size: 32px; + margin-top: 0px; +} + +.map-col { + flex-grow: 0; + flex-shrink: 0; + width: 470px; + height: 360px; + min-width: 470px; + min-height: 360px; + max-height: 360px; + padding: 0; + background: #181b2d; + overflow: hidden; + margin-top: 0; +} + +.row { + margin-right: 0; +} + +.full-width-row { + padding-left: 15px; + flex-wrap: nowrap; +} + +::ng-deep .symbol { + font-size: 24px; +} \ No newline at end of file diff --git a/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.ts b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.ts new file mode 100644 index 000000000..18e2f2d6c --- /dev/null +++ b/frontend/src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.ts @@ -0,0 +1,103 @@ +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { ActivatedRoute, ParamMap } from '@angular/router'; +import { catchError, map, switchMap, Observable, share, of } from 'rxjs'; +import { ApiService } from 'src/app/services/api.service'; +import { SeoService } from 'src/app/services/seo.service'; +import { OpenGraphService } from 'src/app/services/opengraph.service'; +import { getFlagEmoji } from 'src/app/shared/common.utils'; +import { GeolocationData } from 'src/app/shared/components/geolocation/geolocation.component'; + +@Component({ + selector: 'app-nodes-per-isp-preview', + templateUrl: './nodes-per-isp-preview.component.html', + styleUrls: ['./nodes-per-isp-preview.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class NodesPerISPPreview implements OnInit { + nodes$: Observable; + isp: {name: string, id: number}; + id: string; + error: Error; + + constructor( + private apiService: ApiService, + private seoService: SeoService, + private openGraphService: OpenGraphService, + private route: ActivatedRoute, + ) { } + + ngOnInit(): void { + this.nodes$ = this.route.paramMap + .pipe( + switchMap((params: ParamMap) => { + this.id = params.get('isp'); + this.isp = null; + this.openGraphService.waitFor('isp-map-' + this.id); + this.openGraphService.waitFor('isp-data-' + this.id); + return this.apiService.getNodeForISP$(params.get('isp')); + }), + map(response => { + this.isp = { + name: response.isp, + id: this.route.snapshot.params.isp.split(',').join(', ') + }; + this.seoService.setTitle($localize`Lightning nodes on ISP: ${response.isp} [AS${this.route.snapshot.params.isp}]`); + + for (const i in response.nodes) { + response.nodes[i].geolocation = { + country: response.nodes[i].country?.en, + city: response.nodes[i].city?.en, + subdivision: response.nodes[i].subdivision?.en, + iso: response.nodes[i].iso_code, + }; + } + + const sumLiquidity = response.nodes.reduce((partialSum, a) => partialSum + a.capacity, 0); + const sumChannels = response.nodes.reduce((partialSum, a) => partialSum + a.channels, 0); + const countries = {}; + const topCountry = { + count: 0, + country: '', + iso: '', + flag: '', + }; + for (const node of response.nodes) { + if (!node.geolocation.iso) { + continue; + } + countries[node.geolocation.iso] = countries[node.geolocation.iso] ?? 0 + 1; + if (countries[node.geolocation.iso] > topCountry.count) { + topCountry.count = countries[node.geolocation.iso]; + topCountry.country = node.geolocation.country; + topCountry.iso = node.geolocation.iso; + } + } + topCountry.flag = getFlagEmoji(topCountry.iso); + + this.openGraphService.waitOver('isp-data-' + this.id); + + return { + nodes: response.nodes, + sumLiquidity: sumLiquidity, + sumChannels: sumChannels, + topCountry: topCountry, + }; + }), + catchError(err => { + this.error = err; + this.openGraphService.fail('isp-map-' + this.id); + this.openGraphService.fail('isp-data-' + this.id); + return of({ + nodes: [], + sumLiquidity: 0, + sumChannels: 0, + topCountry: {}, + }); + }) + ); + } + + onMapReady() { + this.openGraphService.waitOver('isp-map-' + this.id); + } +} diff --git a/unfurler/src/routes.ts b/unfurler/src/routes.ts index c6e77e79a..4c25bf93b 100644 --- a/unfurler/src/routes.ts +++ b/unfurler/src/routes.ts @@ -46,6 +46,17 @@ const routes = { return `Lightning Channel: ${path[0]}`; } }, + nodes: { + routes: { + isp: { + render: true, + params: 1, + getTitle(path) { + return `Lightning ISP: ${path[0]}`; + } + } + } + } } }, mining: { From 0ac33528352cf1936bf2e997a2d20889841873c5 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sat, 10 Sep 2022 15:57:06 +0000 Subject: [PATCH 011/109] tweak block preview (height & hash side by side) --- .../block/block-preview.component.html | 10 +++++-- .../block/block-preview.component.scss | 28 +++++++++++++++++-- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/components/block/block-preview.component.html b/frontend/src/app/components/block/block-preview.component.html index 666714b24..4e75abdc1 100644 --- a/frontend/src/app/components/block/block-preview.component.html +++ b/frontend/src/app/components/block/block-preview.component.html @@ -4,15 +4,19 @@
-
-
+
+

Genesis {{ blockHeight }}

+

+ {{ blockHash.slice(0,20) }} +
+ …{{ blockHash.slice(-19) }} +

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

-
Unknown capacity
+
Unknown Capacity

-
Tor capacity
+
Tor Capacity

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

-
Clearnet capacity
+
Clearnet Capacity

-
Unknown capacity
+
Unknown Capacity

-
Tor capacity
+
Tor Capacity

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

Flow

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

Flow

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

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

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

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

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

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

- Top 100 oldest lightning nodes +

+ Top 100 oldest lightning nodes

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

Fee distribution

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

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

+
+
+
+ +

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

+
+ +

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

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

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

    +
    +

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

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

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

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

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

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

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

    -
    - -

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

    -
    +

    Hostname

    +

    {{plainHostname}}

    +

    Port

    +

    {{electrsPort}}

    +

    SSL

    +

    Enabled

    +

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

    diff --git a/frontend/src/app/docs/api-docs/api-docs.component.ts b/frontend/src/app/docs/api-docs/api-docs.component.ts index ed0ecb0a2..7b78d187b 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.ts +++ b/frontend/src/app/docs/api-docs/api-docs.component.ts @@ -12,6 +12,8 @@ import { FaqTemplateDirective } from '../faq-template/faq-template.component'; styleUrls: ['./api-docs.component.scss'] }) export class ApiDocsComponent implements OnInit, AfterViewInit { + plainHostname = document.location.hostname; + electrsPort = 0; hostname = document.location.hostname; network$: Observable; active = 0; @@ -82,6 +84,20 @@ export class ApiDocsComponent implements OnInit, AfterViewInit { this.network$.subscribe((network) => { this.active = (network === 'liquid' || network === 'liquidtestnet') ? 2 : 0; + switch( network ) { + case "": + this.electrsPort = 50002; break; + case "mainnet": + this.electrsPort = 50002; break; + case "testnet": + this.electrsPort = 60002; break; + case "signet": + this.electrsPort = 60602; break; + case "liquid": + this.electrsPort = 51002; break; + case "liquidtestnet": + this.electrsPort = 51302; break; + } }); } diff --git a/frontend/src/app/docs/docs/docs.component.ts b/frontend/src/app/docs/docs/docs.component.ts index c129cd21e..3e74ba959 100644 --- a/frontend/src/app/docs/docs/docs.component.ts +++ b/frontend/src/app/docs/docs/docs.component.ts @@ -46,7 +46,7 @@ export class DocsComponent implements OnInit { this.env = this.stateService.env; this.showWebSocketTab = ( ! ( ( this.stateService.network === "bisq" ) || ( this.stateService.network === "liquidtestnet" ) ) ); this.showFaqTab = ( this.env.BASE_MODULE === 'mempool' ) ? true : false; - this.showElectrsTab = this.stateService.env.OFFICIAL_MEMPOOL_SPACE && ( this.stateService.network === "" || this.stateService.network === "mainnet" || this.stateService.network === "testnet" || this.stateService.network === "signet" ); + this.showElectrsTab = this.stateService.env.OFFICIAL_MEMPOOL_SPACE && ( this.stateService.network !== "bisq" ); document.querySelector( "html" ).style.scrollBehavior = "smooth"; } From 38ec5ef957dfc795db3d44efa0550b3b42aa61f0 Mon Sep 17 00:00:00 2001 From: hunicus <93150691+hunicus@users.noreply.github.com> Date: Sun, 30 Oct 2022 13:08:26 -0400 Subject: [PATCH 106/109] Position docs footer on bottom For short docs pages (like electrum rpc). --- frontend/src/app/docs/api-docs/api-docs.component.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/src/app/docs/api-docs/api-docs.component.scss b/frontend/src/app/docs/api-docs/api-docs.component.scss index aebaafe6f..acfc209e5 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.scss +++ b/frontend/src/app/docs/api-docs/api-docs.component.scss @@ -10,6 +10,12 @@ font-size: 12px; } +.container-xl { + display: flex; + min-height: 75vh; + flex-direction: column; +} + code { background-color: #1d1f31; font-family: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New; From 387a51d87eec7a0b2a846a2f15c9294853ce3d39 Mon Sep 17 00:00:00 2001 From: softsimon Date: Mon, 7 Nov 2022 04:28:23 +0400 Subject: [PATCH 107/109] Use relative import paths in the frontend --- .../components/block-audit/block-audit.component.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/components/block-audit/block-audit.component.ts b/frontend/src/app/components/block-audit/block-audit.component.ts index a7eb879b4..f8ce8d9bb 100644 --- a/frontend/src/app/components/block-audit/block-audit.component.ts +++ b/frontend/src/app/components/block-audit/block-audit.component.ts @@ -1,12 +1,12 @@ import { Component, OnDestroy, OnInit, AfterViewInit, ViewChildren, QueryList } from '@angular/core'; import { ActivatedRoute, ParamMap, Router } from '@angular/router'; -import { Observable, Subscription, combineLatest } from 'rxjs'; +import { Subscription, combineLatest } from 'rxjs'; import { map, switchMap, startWith, catchError } from 'rxjs/operators'; -import { BlockAudit, TransactionStripped } from 'src/app/interfaces/node-api.interface'; -import { ApiService } from 'src/app/services/api.service'; -import { StateService } from 'src/app/services/state.service'; -import { detectWebGL } from 'src/app/shared/graphs.utils'; -import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe'; +import { BlockAudit, TransactionStripped } from '../../interfaces/node-api.interface'; +import { ApiService } from '../../services/api.service'; +import { StateService } from '../../services/state.service'; +import { detectWebGL } from '../../shared/graphs.utils'; +import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe'; import { BlockOverviewGraphComponent } from '../block-overview-graph/block-overview-graph.component'; @Component({ From 69a36e17a87fa0dcd81c873e72addab8a1860ded Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn <100320+knorrium@users.noreply.github.com> Date: Sun, 6 Nov 2022 20:24:12 -0800 Subject: [PATCH 108/109] Update staging hosts for testing --- frontend/proxy.conf.staging.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/proxy.conf.staging.js b/frontend/proxy.conf.staging.js index 098edb619..0cf366ca7 100644 --- a/frontend/proxy.conf.staging.js +++ b/frontend/proxy.conf.staging.js @@ -3,9 +3,9 @@ const fs = require('fs'); let PROXY_CONFIG = require('./proxy.conf'); PROXY_CONFIG.forEach(entry => { - entry.target = entry.target.replace("mempool.space", "mempool.ninja"); - entry.target = entry.target.replace("liquid.network", "liquid.place"); - entry.target = entry.target.replace("bisq.markets", "bisq.ninja"); + entry.target = entry.target.replace("mempool.space", "mempool-staging.fra.mempool.space"); + entry.target = entry.target.replace("liquid.network", "liquid-staging.fra.mempool.space"); + entry.target = entry.target.replace("bisq.markets", "bisq-staging.fra.mempool.space"); }); module.exports = PROXY_CONFIG; From b6d4e6b9939dd62773a4dfad6bb2308532a22dfa Mon Sep 17 00:00:00 2001 From: wiz Date: Mon, 7 Nov 2022 15:44:25 +0900 Subject: [PATCH 109/109] [ops] Fix nvidia-driver package name --- production/install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/production/install b/production/install index 96bb4b623..9bab3a418 100755 --- a/production/install +++ b/production/install @@ -401,7 +401,7 @@ FREEBSD_PKG+=(nginx rsync py39-certbot-nginx mariadb105-server keybase) FREEBSD_PKG+=(geoipupdate) FREEBSD_UNFURL_PKG=() -FREEBSD_UNFURL_PKG+=(nvidia-driver-470-470.129.06 chromium xinit xterm twm ja-sourcehansans-otf) +FREEBSD_UNFURL_PKG+=(nvidia-driver-470 chromium xinit xterm twm ja-sourcehansans-otf) FREEBSD_UNFURL_PKG+=(zh-sourcehansans-sc-otf ko-aleefonts-ttf lohit tlwg-ttf) #############################