From 25c0eb62b24439283096c4e04f59ff6dcf37c9f7 Mon Sep 17 00:00:00 2001 From: natsoni Date: Tue, 4 Jun 2024 10:58:04 +0200 Subject: [PATCH 01/35] More robust price service --- .../tasks/price-feeds/free-currency-api.ts | 2 +- backend/src/tasks/price-updater.ts | 33 +++++++++++++------ backend/src/utils/axios-query.ts | 9 ++++- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/backend/src/tasks/price-feeds/free-currency-api.ts b/backend/src/tasks/price-feeds/free-currency-api.ts index 2a21a16d2..48e511aa8 100644 --- a/backend/src/tasks/price-feeds/free-currency-api.ts +++ b/backend/src/tasks/price-feeds/free-currency-api.ts @@ -76,7 +76,7 @@ class FreeCurrencyApi implements ConversionFeed { } public async $fetchConversionRates(date: string): Promise { - const response = await query(`${this.API_URL_PREFIX}historical?date=${date}&apikey=${this.API_KEY}`); + const response = await query(`${this.API_URL_PREFIX}historical?date=${date}&apikey=${this.API_KEY}`, true); if (response && response['data'] && (response['data'][date] || this.PAID)) { if (this.PAID) { response['data'] = this.convertData(response['data']); diff --git a/backend/src/tasks/price-updater.ts b/backend/src/tasks/price-updater.ts index 16fe713b7..467669a6f 100644 --- a/backend/src/tasks/price-updater.ts +++ b/backend/src/tasks/price-updater.ts @@ -59,7 +59,7 @@ class PriceUpdater { private currencyConversionFeed: ConversionFeed | undefined; private newCurrencies: string[] = ['BGN', 'BRL', 'CNY', 'CZK', 'DKK', 'HKD', 'HRK', 'HUF', 'IDR', 'ILS', 'INR', 'ISK', 'KRW', 'MXN', 'MYR', 'NOK', 'NZD', 'PHP', 'PLN', 'RON', 'RUB', 'SEK', 'SGD', 'THB', 'TRY', 'ZAR']; private lastTimeConversionsRatesFetched: number = 0; - private latestConversionsRatesFromFeed: ConversionRates = {}; + private latestConversionsRatesFromFeed: ConversionRates = { USD: -1 }; private ratesChangedCallback: ((rates: ApiPrice) => void) | undefined; constructor() { @@ -157,9 +157,9 @@ class PriceUpdater { try { this.latestConversionsRatesFromFeed = await this.currencyConversionFeed.$fetchLatestConversionRates(); this.lastTimeConversionsRatesFetched = Math.round(new Date().getTime() / 1000); - logger.debug(`Fetched currencies conversion rates from external API: ${JSON.stringify(this.latestConversionsRatesFromFeed)}`); + logger.debug(`Fetched currencies conversion rates from conversions API: ${JSON.stringify(this.latestConversionsRatesFromFeed)}`); } catch (e) { - logger.err(`Cannot fetch conversion rates from the API. Reason: ${(e instanceof Error ? e.message : e)}`); + logger.err(`Cannot fetch conversion rates from conversions API. Reason: ${(e instanceof Error ? e.message : e)}`); } } @@ -408,17 +408,17 @@ class PriceUpdater { try { const remainingQuota = await this.currencyConversionFeed?.$getQuota(); if (remainingQuota['month']['remaining'] < 500) { // We need some calls left for the daily updates - logger.debug(`Not enough currency API credit to insert missing prices in ${priceTimesToFill.length} rows (${remainingQuota['month']['remaining']} calls left).`, logger.tags.mining); + logger.debug(`Not enough conversions API credit to insert missing prices in ${priceTimesToFill.length} rows (${remainingQuota['month']['remaining']} calls left).`, logger.tags.mining); this.additionalCurrenciesHistoryInserted = true; // Do not try again until next day return; } } catch (e) { - logger.err(`Cannot fetch currency API credit, insertion of missing prices aborted. Reason: ${(e instanceof Error ? e.message : e)}`); + logger.err(`Cannot fetch conversions API credit, insertion of missing prices aborted. Reason: ${(e instanceof Error ? e.message : e)}`); return; } this.additionalCurrenciesHistoryRunning = true; - logger.debug(`Fetching missing conversion rates from external API to fill ${priceTimesToFill.length} rows`, logger.tags.mining); + logger.debug(`Inserting missing historical conversion rates using conversions API to fill ${priceTimesToFill.length} rows`, logger.tags.mining); let conversionRates: { [timestamp: number]: ConversionRates } = {}; let totalInserted = 0; @@ -430,10 +430,23 @@ class PriceUpdater { const month = new Date(priceTime.time * 1000).getMonth(); const yearMonthTimestamp = new Date(year, month, 1).getTime() / 1000; if (conversionRates[yearMonthTimestamp] === undefined) { - conversionRates[yearMonthTimestamp] = await this.currencyConversionFeed?.$fetchConversionRates(`${year}-${month + 1 < 10 ? `0${month + 1}` : `${month + 1}`}-01`) || { USD: -1 }; - if (conversionRates[yearMonthTimestamp]['USD'] < 0) { - logger.err(`Cannot fetch conversion rates from the API for ${year}-${month + 1 < 10 ? `0${month + 1}` : `${month + 1}`}-01. Aborting insertion of missing prices.`, logger.tags.mining); - this.lastFailedHistoricalRun = Math.round(new Date().getTime() / 1000); + try { + if (year === new Date().getFullYear() && month === new Date().getMonth()) { // For rows in the current month, we use the latest conversion rates + conversionRates[yearMonthTimestamp] = this.latestConversionsRatesFromFeed; + } else { + conversionRates[yearMonthTimestamp] = await this.currencyConversionFeed?.$fetchConversionRates(`${year}-${month + 1 < 10 ? `0${month + 1}` : `${month + 1}`}-15`) || { USD: -1 }; + } + + if (conversionRates[yearMonthTimestamp]['USD'] < 0) { + throw new Error('Incorrect USD conversion rate'); + } + } catch (e) { + if ((e instanceof Error ? e.message : '').includes('429')) { // Continue 60 seconds later if and only if error is 429 + this.lastFailedHistoricalRun = Math.round(new Date().getTime() / 1000); + logger.info(`Got a 429 error from conversions API. This is expected to happen a few times during the initial historical price insertion, process will resume in 60 seconds.`, logger.tags.mining); + } else { + logger.err(`Cannot fetch conversion rates from conversions API for ${year}-${month + 1 < 10 ? `0${month + 1}` : `${month + 1}`}-01, trying again next day. Error: ${(e instanceof Error ? e.message : e)}`, logger.tags.mining); + } break; } } diff --git a/backend/src/utils/axios-query.ts b/backend/src/utils/axios-query.ts index 0a155fd55..e641d3ce9 100644 --- a/backend/src/utils/axios-query.ts +++ b/backend/src/utils/axios-query.ts @@ -5,7 +5,7 @@ import config from '../config'; import logger from '../logger'; import * as https from 'https'; -export async function query(path): Promise { +export async function query(path, throwOnFail: boolean = false): Promise { type axiosOptions = { headers: { 'User-Agent': string @@ -21,6 +21,7 @@ export async function query(path): Promise { timeout: config.SOCKS5PROXY.ENABLED ? 30000 : 10000 }; let retry = 0; + let lastError: any = null; while (retry < config.MEMPOOL.EXTERNAL_MAX_RETRY) { try { @@ -50,6 +51,7 @@ export async function query(path): Promise { } return data.data; } catch (e) { + lastError = e; logger.warn(`Could not connect to ${path} (Attempt ${retry + 1}/${config.MEMPOOL.EXTERNAL_MAX_RETRY}). Reason: ` + (e instanceof Error ? e.message : e)); retry++; } @@ -59,5 +61,10 @@ export async function query(path): Promise { } logger.err(`Could not connect to ${path}. All ${config.MEMPOOL.EXTERNAL_MAX_RETRY} attempts failed`); + + if (throwOnFail && lastError) { + throw lastError; + } + return undefined; } From 746a045c4863e362729cc13c07c203e348bb711b Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 10 Jun 2024 22:02:14 +0000 Subject: [PATCH 02/35] Refactor address page component with AddressStats class --- .../components/address/address.component.html | 10 +- .../components/address/address.component.ts | 123 +++++++++++++----- 2 files changed, 92 insertions(+), 41 deletions(-) diff --git a/frontend/src/app/components/address/address.component.html b/frontend/src/app/components/address/address.component.html index 26e5d8203..98b432521 100644 --- a/frontend/src/app/components/address/address.component.html +++ b/frontend/src/app/components/address/address.component.html @@ -28,16 +28,16 @@ Total received - + Total sent - + Balance - + @@ -76,8 +76,8 @@

  - {{ (transactions?.length | number) || '?' }} of {{ txCount | number }} transaction - {{ (transactions?.length | number) || '?' }} of {{ txCount | number }} transactions + {{ (transactions?.length | number) || '?' }} of {{ mempoolStats.tx_count + chainStats.tx_count | number }} transaction + {{ (transactions?.length | number) || '?' }} of {{ mempoolStats.tx_count + chainStats.tx_count | number }} transactions

diff --git a/frontend/src/app/components/address/address.component.ts b/frontend/src/app/components/address/address.component.ts index e79ad45e2..4ef67ed5f 100644 --- a/frontend/src/app/components/address/address.component.ts +++ b/frontend/src/app/components/address/address.component.ts @@ -2,7 +2,7 @@ import { Component, OnInit, OnDestroy } from '@angular/core'; import { ActivatedRoute, ParamMap } from '@angular/router'; import { ElectrsApiService } from '../../services/electrs-api.service'; import { switchMap, filter, catchError, map, tap } from 'rxjs/operators'; -import { Address, ScriptHash, Transaction } from '../../interfaces/electrs.interface'; +import { Address, ChainStats, Transaction } from '../../interfaces/electrs.interface'; import { WebsocketService } from '../../services/websocket.service'; import { StateService } from '../../services/state.service'; import { AudioService } from '../../services/audio.service'; @@ -12,6 +12,78 @@ import { SeoService } from '../../services/seo.service'; import { seoDescriptionNetwork } from '../../shared/common.utils'; import { AddressInformation } from '../../interfaces/node-api.interface'; +class AddressStats implements ChainStats { + address: string; + scriptpubkey?: string; + funded_txo_count: number; + funded_txo_sum: number; + spent_txo_count: number; + spent_txo_sum: number; + tx_count: number; + + constructor (stats: ChainStats, address: string, scriptpubkey?: string) { + Object.assign(this, stats); + this.address = address; + this.scriptpubkey = scriptpubkey; + } + + public addTx(tx: Transaction): void { + for (const vin of tx.vin) { + if (vin.prevout?.scriptpubkey_address === this.address || (this.scriptpubkey === vin.prevout?.scriptpubkey)) { + this.spendTxo(vin.prevout.value); + } + } + for (const vout of tx.vout) { + if (vout.scriptpubkey_address === this.address || (this.scriptpubkey === vout.scriptpubkey)) { + this.fundTxo(vout.value); + } + } + this.tx_count++; + } + + public removeTx(tx: Transaction): void { + for (const vin of tx.vin) { + if (vin.prevout?.scriptpubkey_address === this.address || (this.scriptpubkey === vin.prevout?.scriptpubkey)) { + this.unspendTxo(vin.prevout.value); + } + } + for (const vout of tx.vout) { + if (vout.scriptpubkey_address === this.address || (this.scriptpubkey === vout.scriptpubkey)) { + this.unfundTxo(vout.value); + } + } + this.tx_count--; + } + + private fundTxo(value: number): void { + this.funded_txo_sum += value; + this.funded_txo_count++; + } + + private unfundTxo(value: number): void { + this.funded_txo_sum -= value; + this.funded_txo_count--; + } + + private spendTxo(value: number): void { + this.spent_txo_sum += value; + this.spent_txo_count++; + } + + private unspendTxo(value: number): void { + this.spent_txo_sum -= value; + this.spent_txo_count--; + } + + get balance(): number { + return this.funded_txo_sum - this.spent_txo_sum; + } + + get utxos(): number { + return this.funded_txo_count - this.spent_txo_count; + } +} + @Component({ selector: 'app-address', templateUrl: './address.component.html', @@ -35,9 +107,9 @@ export class AddressComponent implements OnInit, OnDestroy { addressInfo: null | AddressInformation = null; fullyLoaded = false; - txCount = 0; - received = 0; - sent = 0; + chainStats: AddressStats; + mempoolStats: AddressStats; + now = Date.now() / 1000; balancePeriod: 'all' | '1m' = 'all'; @@ -55,7 +127,7 @@ export class AddressComponent implements OnInit, OnDestroy { private seoService: SeoService, ) { } - ngOnInit() { + ngOnInit(): void { this.stateService.networkChanged$.subscribe((network) => this.network = network); this.websocketService.want(['blocks']); @@ -175,7 +247,7 @@ export class AddressComponent implements OnInit, OnDestroy { }); this.transactions = this.tempTransactions; - if (this.transactions.length === this.txCount) { + if (this.transactions.length === (this.mempoolStats.tx_count + this.chainStats.tx_count)) { this.fullyLoaded = true; } this.isLoadingTransactions = false; @@ -196,11 +268,13 @@ export class AddressComponent implements OnInit, OnDestroy { this.mempoolTxSubscription = this.stateService.mempoolTransactions$ .subscribe(tx => { this.addTransaction(tx); + this.mempoolStats.addTx(tx); }); this.mempoolRemovedTxSubscription = this.stateService.mempoolRemovedTransactions$ .subscribe(tx => { this.removeTransaction(tx); + this.mempoolStats.removeTx(tx); }); this.blockTxSubscription = this.stateService.blockTransactions$ @@ -209,12 +283,14 @@ export class AddressComponent implements OnInit, OnDestroy { if (tx) { tx.status = transaction.status; this.transactions = this.transactions.slice(); + this.mempoolStats.removeTx(transaction); this.audioService.playSound('magic'); } else { if (this.addTransaction(transaction, false)) { this.audioService.playSound('magic'); } } + this.chainStats.addTx(transaction); }); } @@ -225,7 +301,6 @@ export class AddressComponent implements OnInit, OnDestroy { this.transactions.unshift(transaction); this.transactions = this.transactions.slice(); - this.txCount++; if (playSound) { if (transaction.vout.some((vout) => vout?.scriptpubkey_address === this.address.address)) { @@ -235,17 +310,6 @@ export class AddressComponent implements OnInit, OnDestroy { } } - transaction.vin.forEach((vin) => { - if (vin?.prevout?.scriptpubkey_address === this.address.address) { - this.sent += vin.prevout.value; - } - }); - transaction.vout.forEach((vout) => { - if (vout?.scriptpubkey_address === this.address.address) { - this.received += vout.value; - } - }); - return true; } @@ -257,23 +321,11 @@ export class AddressComponent implements OnInit, OnDestroy { this.transactions.splice(index, 1); this.transactions = this.transactions.slice(); - this.txCount--; - - transaction.vin.forEach((vin) => { - if (vin?.prevout?.scriptpubkey_address === this.address.address) { - this.sent -= vin.prevout.value; - } - }); - transaction.vout.forEach((vout) => { - if (vout?.scriptpubkey_address === this.address.address) { - this.received -= vout.value; - } - }); return true; } - loadMore() { + loadMore(): void { if (this.isLoadingTransactions || this.fullyLoaded) { return; } @@ -301,10 +353,9 @@ export class AddressComponent implements OnInit, OnDestroy { }); } - updateChainStats() { - this.received = this.address.chain_stats.funded_txo_sum + this.address.mempool_stats.funded_txo_sum; - this.sent = this.address.chain_stats.spent_txo_sum + this.address.mempool_stats.spent_txo_sum; - this.txCount = this.address.chain_stats.tx_count + this.address.mempool_stats.tx_count; + updateChainStats(): void { + this.chainStats = new AddressStats(this.address.chain_stats, this.address.address); + this.mempoolStats = new AddressStats(this.address.mempool_stats, this.address.address); } setBalancePeriod(period: 'all' | '1m'): boolean { @@ -319,7 +370,7 @@ export class AddressComponent implements OnInit, OnDestroy { ); } - ngOnDestroy() { + ngOnDestroy(): void { this.mainSubscription.unsubscribe(); this.mempoolTxSubscription.unsubscribe(); this.mempoolRemovedTxSubscription.unsubscribe(); From 9514bb703b888d5c252eccd321dc7537d59afa16 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 10 Jun 2024 23:04:37 +0000 Subject: [PATCH 03/35] Redesign top of address page --- .../address-labels.component.html | 4 +- .../address-labels.component.ts | 1 + .../components/address/address.component.html | 119 +++++++++++++----- .../components/address/address.component.scss | 2 +- .../components/address/address.component.ts | 34 ++++- .../address-type/address-type.component.html | 29 +++++ .../address-type/address-type.component.ts | 11 ++ frontend/src/app/shared/shared.module.ts | 3 + 8 files changed, 165 insertions(+), 38 deletions(-) create mode 100644 frontend/src/app/shared/components/address-type/address-type.component.html create mode 100644 frontend/src/app/shared/components/address-type/address-type.component.ts diff --git a/frontend/src/app/components/address-labels/address-labels.component.html b/frontend/src/app/components/address-labels/address-labels.component.html index dfc6647f4..b055cf606 100644 --- a/frontend/src/app/components/address-labels/address-labels.component.html +++ b/frontend/src/app/components/address-labels/address-labels.component.html @@ -4,7 +4,7 @@ {{ label }} @@ -15,6 +15,6 @@ {{ label }} \ No newline at end of file diff --git a/frontend/src/app/components/address-labels/address-labels.component.ts b/frontend/src/app/components/address-labels/address-labels.component.ts index 2365c167f..867fc9970 100644 --- a/frontend/src/app/components/address-labels/address-labels.component.ts +++ b/frontend/src/app/components/address-labels/address-labels.component.ts @@ -15,6 +15,7 @@ export class AddressLabelsComponent implements OnChanges { @Input() vin: Vin; @Input() vout: Vout; @Input() channel: any; + @Input() class: string = ''; label?: string; diff --git a/frontend/src/app/components/address/address.component.html b/frontend/src/app/components/address/address.component.html index 98b432521..ba4884219 100644 --- a/frontend/src/app/components/address/address.component.html +++ b/frontend/src/app/components/address/address.component.html @@ -14,40 +14,39 @@
-
- - - - - - - - - - - - - - - - - - - - - -
Unconfidential - - - -
Total received
Total sent
Balance
-
-
-
-
- + @if (isMobile) { +
+ + + + + + + + + +
-
+ } @else { +
+ + + + + + +
+
+
+ + + + + + +
+
+ }
@@ -182,3 +181,57 @@
+ + + + + Balance + + + + + + + pending + + + + + + + Unspent TXOs + {{ chainStats.utxos }} + + + + + + pending + {{ mempoolStats.utxos > 0 ? '+' : ''}}{{ mempoolStats.utxos }} + + + + + + Volume + + + + + + + Type + + + + + + + Unconfidential + + + + + + + \ No newline at end of file diff --git a/frontend/src/app/components/address/address.component.scss b/frontend/src/app/components/address/address.component.scss index da615376c..45b684a49 100644 --- a/frontend/src/app/components/address/address.component.scss +++ b/frontend/src/app/components/address/address.component.scss @@ -25,7 +25,7 @@ tr td { &:last-child { text-align: right; - @media (min-width: 576px) { + @media (min-width: 768px) { text-align: left; } } diff --git a/frontend/src/app/components/address/address.component.ts b/frontend/src/app/components/address/address.component.ts index 4ef67ed5f..b485167cf 100644 --- a/frontend/src/app/components/address/address.component.ts +++ b/frontend/src/app/components/address/address.component.ts @@ -1,8 +1,8 @@ -import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Component, OnInit, OnDestroy, HostListener } from '@angular/core'; import { ActivatedRoute, ParamMap } from '@angular/router'; import { ElectrsApiService } from '../../services/electrs-api.service'; import { switchMap, filter, catchError, map, tap } from 'rxjs/operators'; -import { Address, ChainStats, Transaction } from '../../interfaces/electrs.interface'; +import { Address, ChainStats, Transaction, Vin, Vout } from '../../interfaces/electrs.interface'; import { WebsocketService } from '../../services/websocket.service'; import { StateService } from '../../services/state.service'; import { AudioService } from '../../services/audio.service'; @@ -79,6 +79,10 @@ class AddressStats implements ChainStats { return this.funded_txo_sum - this.spent_txo_sum; } + get volume(): number { + return this.funded_txo_sum + this.spent_txo_sum; + } + get utxos(): number { return this.funded_txo_count - this.spent_txo_count; } @@ -92,6 +96,8 @@ class AddressStats implements ChainStats { export class AddressComponent implements OnInit, OnDestroy { network = ''; + isMobile: boolean; + address: Address; addressString: string; isLoadingAddress = true; @@ -110,6 +116,10 @@ export class AddressComponent implements OnInit, OnDestroy { chainStats: AddressStats; mempoolStats: AddressStats; + exampleChannel?: any; + exampleVin?: Vin; + exampleVout?: Vout; + now = Date.now() / 1000; balancePeriod: 'all' | '1m' = 'all'; @@ -147,6 +157,9 @@ export class AddressComponent implements OnInit, OnDestroy { this.isLoadingTransactions = true; this.transactions = null; this.addressInfo = null; + this.exampleChannel = null; + this.exampleVin = null; + this.exampleVout = null; document.body.scrollTo(0, 0); this.addressString = params.get('id') || ''; if (/^[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,100}|04[a-fA-F0-9]{128}|(02|03)[a-fA-F0-9]{64}$/.test(this.addressString)) { @@ -252,6 +265,18 @@ export class AddressComponent implements OnInit, OnDestroy { } this.isLoadingTransactions = false; + for (const tx of this.transactions) { + if (!this.exampleVin) { + this.exampleVin = tx.vin.find(v => v.prevout?.scriptpubkey_address === this.address.address); + } + if (!this.exampleVout) { + this.exampleVout = tx.vout.find(v => v.scriptpubkey_address === this.address.address); + } + if (this.exampleVin && this.exampleVout) { + break; + } + } + if (!this.showBalancePeriod()) { this.setBalancePeriod('all'); } else { @@ -370,6 +395,11 @@ export class AddressComponent implements OnInit, OnDestroy { ); } + @HostListener('window:resize', ['$event']) + onResize(): void { + this.isMobile = window.innerWidth < 768; + } + ngOnDestroy(): void { this.mainSubscription.unsubscribe(); this.mempoolTxSubscription.unsubscribe(); diff --git a/frontend/src/app/shared/components/address-type/address-type.component.html b/frontend/src/app/shared/components/address-type/address-type.component.html new file mode 100644 index 000000000..fbe041cb5 --- /dev/null +++ b/frontend/src/app/shared/components/address-type/address-type.component.html @@ -0,0 +1,29 @@ +@switch (vout?.scriptpubkey_type || null) { + @case ('fee') { + fee + } + @case ('empty') { + empty + } + @case ('v0_p2wpkh') { + P2WPKH + } + @case ('v0_p2wsh') { + P2WSH + } + @case ('v1_p2tr') { + P2TR + } + @case ('provably_unspendable') { + provably unspendable + } + @case ('multisig') { + bare multisig + } + @case (null) { + unknown + } + @default { + {{ vout.scriptpubkey_type.toUpperCase() }} + } +} \ No newline at end of file diff --git a/frontend/src/app/shared/components/address-type/address-type.component.ts b/frontend/src/app/shared/components/address-type/address-type.component.ts new file mode 100644 index 000000000..34077330a --- /dev/null +++ b/frontend/src/app/shared/components/address-type/address-type.component.ts @@ -0,0 +1,11 @@ +import { Component, Input } from '@angular/core'; +import { Vout } from '../../../interfaces/electrs.interface'; + +@Component({ + selector: 'app-address-type', + templateUrl: './address-type.component.html', + styleUrls: [] +}) +export class AddressTypeComponent { + @Input() vout: Vout; +} diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 3b56d3510..ead9060ae 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -87,6 +87,7 @@ import { ChangeComponent } from '../components/change/change.component'; import { SatsComponent } from './components/sats/sats.component'; import { BtcComponent } from './components/btc/btc.component'; import { FeeRateComponent } from './components/fee-rate/fee-rate.component'; +import { AddressTypeComponent } from './components/address-type/address-type.component'; import { TruncateComponent } from './components/truncate/truncate.component'; import { SearchResultsComponent } from '../components/search-form/search-results/search-results.component'; import { TimestampComponent } from './components/timestamp/timestamp.component'; @@ -202,6 +203,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir SatsComponent, BtcComponent, FeeRateComponent, + AddressTypeComponent, TruncateComponent, SearchResultsComponent, TimestampComponent, @@ -343,6 +345,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir SatsComponent, BtcComponent, FeeRateComponent, + AddressTypeComponent, TruncateComponent, SearchResultsComponent, TimestampComponent, From 331b54fe89acb535c0e6c39f5cfab5c04a29ff43 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 10 Jun 2024 23:12:41 +0000 Subject: [PATCH 04/35] Address mouseover QR code --- .../components/address/address.component.html | 6 ++++++ .../components/address/address.component.scss | 20 +++++++------------ .../components/address/address.component.ts | 3 +++ 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/frontend/src/app/components/address/address.component.html b/frontend/src/app/components/address/address.component.html index ba4884219..6ab0766ee 100644 --- a/frontend/src/app/components/address/address.component.html +++ b/frontend/src/app/components/address/address.component.html @@ -4,6 +4,12 @@ diff --git a/frontend/src/app/components/address/address.component.scss b/frontend/src/app/components/address/address.component.scss index 45b684a49..fb1789701 100644 --- a/frontend/src/app/components/address/address.component.scss +++ b/frontend/src/app/components/address/address.component.scss @@ -1,16 +1,14 @@ .qr-wrapper { + position: absolute; + top: 30px; + right: 0px; + border: solid 10px var(--active-bg); + border-radius: 5px; background-color: #fff; padding: 10px; padding-bottom: 5px; - display: inline-block; -} - -.qrcode-col { - margin: 20px auto 10px; - text-align: center; - @media (min-width: 992px){ - margin: 0px auto 0px; - } + display: block; + z-index: 99; } .fiat { @@ -104,10 +102,6 @@ h1 { width: 80%; } } - - .qrcode-col { - flex-grow: 0.5; - } } .widget-toggler { diff --git a/frontend/src/app/components/address/address.component.ts b/frontend/src/app/components/address/address.component.ts index b485167cf..10054727a 100644 --- a/frontend/src/app/components/address/address.component.ts +++ b/frontend/src/app/components/address/address.component.ts @@ -97,6 +97,7 @@ export class AddressComponent implements OnInit, OnDestroy { network = ''; isMobile: boolean; + showQR: boolean = false; address: Address; addressString: string; @@ -141,6 +142,8 @@ export class AddressComponent implements OnInit, OnDestroy { this.stateService.networkChanged$.subscribe((network) => this.network = network); this.websocketService.want(['blocks']); + this.onResize(); + this.addressLoadingStatus$ = this.route.paramMap .pipe( switchMap(() => this.stateService.loadingIndicators$), From 3b419be341eca1dbda4766d56dfa763dc1ced3bd Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 11 Jun 2024 20:51:17 +0000 Subject: [PATCH 05/35] Address details pending -> unconfirmed --- frontend/src/app/components/address/address.component.html | 6 +++--- frontend/src/app/components/address/address.component.scss | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/components/address/address.component.html b/frontend/src/app/components/address/address.component.html index 6ab0766ee..6652047d0 100644 --- a/frontend/src/app/components/address/address.component.html +++ b/frontend/src/app/components/address/address.component.html @@ -198,21 +198,21 @@ - pending + unconfirmed balance - Unspent TXOs + UTXOs {{ chainStats.utxos }} - pending + unconfirmed UTXOs {{ mempoolStats.utxos > 0 ? '+' : ''}}{{ mempoolStats.utxos }} diff --git a/frontend/src/app/components/address/address.component.scss b/frontend/src/app/components/address/address.component.scss index fb1789701..b4ab5ac8a 100644 --- a/frontend/src/app/components/address/address.component.scss +++ b/frontend/src/app/components/address/address.component.scss @@ -76,10 +76,10 @@ h1 { top: 9px; position: relative; @media (min-width: 576px) { + max-width: calc(100% - 180px); top: 11px; } @media (min-width: 768px) { - max-width: calc(100% - 180px); top: 17px; } } From 7dfdb5553e5b06bf9c65add5cd8cada6c0d61790 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 12 Jun 2024 04:16:58 +0000 Subject: [PATCH 06/35] Address & script parsing refactor --- .../address-labels.component.ts | 87 ++------ .../components/address/address.component.html | 4 +- .../components/address/address.component.ts | 24 +-- .../transactions-list.component.html | 2 +- frontend/src/app/shared/address-utils.ts | 193 ++++++++++++++++++ .../address-type/address-type.component.html | 4 +- .../address-type/address-type.component.ts | 4 +- frontend/src/app/shared/regex.utils.ts | 6 +- frontend/src/app/shared/script.utils.ts | 110 +++++++++- 9 files changed, 340 insertions(+), 94 deletions(-) create mode 100644 frontend/src/app/shared/address-utils.ts diff --git a/frontend/src/app/components/address-labels/address-labels.component.ts b/frontend/src/app/components/address-labels/address-labels.component.ts index 867fc9970..72a58bfca 100644 --- a/frontend/src/app/components/address-labels/address-labels.component.ts +++ b/frontend/src/app/components/address-labels/address-labels.component.ts @@ -1,7 +1,7 @@ import { Component, ChangeDetectionStrategy, Input, OnChanges } from '@angular/core'; import { Vin, Vout } from '../../interfaces/electrs.interface'; import { StateService } from '../../services/state.service'; -import { parseMultisigScript } from '../../bitcoin.utils'; +import { AddressType, AddressTypeInfo } from '../../shared/address-utils'; @Component({ selector: 'app-address-labels', @@ -12,6 +12,7 @@ import { parseMultisigScript } from '../../bitcoin.utils'; export class AddressLabelsComponent implements OnChanges { network = ''; + @Input() address: AddressTypeInfo; @Input() vin: Vin; @Input() vout: Vout; @Input() channel: any; @@ -28,10 +29,10 @@ export class AddressLabelsComponent implements OnChanges { ngOnChanges() { if (this.channel) { this.handleChannel(); + } else if (this.address) { + this.handleAddress(); } else if (this.vin) { this.handleVin(); - } else if (this.vout) { - this.handleVout(); } } @@ -42,74 +43,22 @@ export class AddressLabelsComponent implements OnChanges { this.label = `Channel ${type}: ${leftNodeName} <> ${rightNodeName}`; } + handleAddress() { + if (this.address?.scripts.size) { + const script = this.address?.scripts.values().next().value; + if (script.template?.label) { + this.label = script.template.label; + } + } + } + handleVin() { - if (this.vin.inner_witnessscript_asm) { - if (this.vin.inner_witnessscript_asm.indexOf('OP_DEPTH OP_PUSHNUM_12 OP_EQUAL OP_IF OP_PUSHNUM_11') === 0 || this.vin.inner_witnessscript_asm.indexOf('OP_PUSHNUM_15 OP_CHECKMULTISIG OP_IFDUP OP_NOTIF OP_PUSHBYTES_2') === 1259) { - if (this.vin.witness.length > 11) { - this.label = 'Liquid Peg Out'; - } else { - this.label = 'Emergency Liquid Peg Out'; - } - return; + const address = new AddressTypeInfo(this.network || 'mainnet', this.vin.prevout?.scriptpubkey_address, this.vin.prevout?.scriptpubkey_type as AddressType, [this.vin]) + if (address?.scripts.size) { + const script = address?.scripts.values().next().value; + if (script.template?.label) { + this.label = script.template.label; } - - const topElement = this.vin.witness[this.vin.witness.length - 2]; - if (/^OP_IF OP_PUSHBYTES_33 \w{66} OP_ELSE OP_PUSH(NUM_\d+|BYTES_(1 \w{2}|2 \w{4})) OP_CSV OP_DROP OP_PUSHBYTES_33 \w{66} OP_ENDIF OP_CHECKSIG$/.test(this.vin.inner_witnessscript_asm)) { - // https://github.com/lightning/bolts/blob/master/03-transactions.md#commitment-transaction-outputs - if (topElement === '01') { - // top element is '01' to get in the revocation path - this.label = 'Revoked Lightning Force Close'; - } else { - // top element is '', this is a delayed to_local output - this.label = 'Lightning Force Close'; - } - return; - } else if ( - /^OP_DUP OP_HASH160 OP_PUSHBYTES_20 \w{40} OP_EQUAL OP_IF OP_CHECKSIG OP_ELSE OP_PUSHBYTES_33 \w{66} OP_SWAP OP_SIZE OP_PUSHBYTES_1 20 OP_EQUAL OP_NOTIF OP_DROP OP_PUSHNUM_2 OP_SWAP OP_PUSHBYTES_33 \w{66} OP_PUSHNUM_2 OP_CHECKMULTISIG OP_ELSE OP_HASH160 OP_PUSHBYTES_20 \w{40} OP_EQUALVERIFY OP_CHECKSIG OP_ENDIF (OP_PUSHNUM_1 OP_CSV OP_DROP |)OP_ENDIF$/.test(this.vin.inner_witnessscript_asm) || - /^OP_DUP OP_HASH160 OP_PUSHBYTES_20 \w{40} OP_EQUAL OP_IF OP_CHECKSIG OP_ELSE OP_PUSHBYTES_33 \w{66} OP_SWAP OP_SIZE OP_PUSHBYTES_1 20 OP_EQUAL OP_IF OP_HASH160 OP_PUSHBYTES_20 \w{40} OP_EQUALVERIFY OP_PUSHNUM_2 OP_SWAP OP_PUSHBYTES_33 \w{66} OP_PUSHNUM_2 OP_CHECKMULTISIG OP_ELSE OP_DROP OP_PUSHBYTES_3 \w{6} OP_CLTV OP_DROP OP_CHECKSIG OP_ENDIF (OP_PUSHNUM_1 OP_CSV OP_DROP |)OP_ENDIF$/.test(this.vin.inner_witnessscript_asm) - ) { - // https://github.com/lightning/bolts/blob/master/03-transactions.md#offered-htlc-outputs - // https://github.com/lightning/bolts/blob/master/03-transactions.md#received-htlc-outputs - if (topElement.length === 66) { - // top element is a public key - this.label = 'Revoked Lightning HTLC'; - } else if (topElement) { - // top element is a preimage - this.label = 'Lightning HTLC'; - } else { - // top element is '' to get in the expiry of the script - this.label = 'Expired Lightning HTLC'; - } - return; - } else if (/^OP_PUSHBYTES_33 \w{66} OP_CHECKSIG OP_IFDUP OP_NOTIF OP_PUSHNUM_16 OP_CSV OP_ENDIF$/.test(this.vin.inner_witnessscript_asm)) { - // https://github.com/lightning/bolts/blob/master/03-transactions.md#to_local_anchor-and-to_remote_anchor-output-option_anchors - if (topElement) { - // top element is a signature - this.label = 'Lightning Anchor'; - } else { - // top element is '', it has been swept after 16 blocks - this.label = 'Swept Lightning Anchor'; - } - return; - } - - this.detectMultisig(this.vin.inner_witnessscript_asm); } - - this.detectMultisig(this.vin.inner_redeemscript_asm); - - this.detectMultisig(this.vin.prevout.scriptpubkey_asm); - } - - detectMultisig(script: string) { - const ms = parseMultisigScript(script); - - if (ms) { - this.label = $localize`:@@address-label.multisig:Multisig ${ms.m}:multisigM: of ${ms.n}:multisigN:`; - } - } - - handleVout() { - this.detectMultisig(this.vout.scriptpubkey_asm); } } diff --git a/frontend/src/app/components/address/address.component.html b/frontend/src/app/components/address/address.component.html index 6652047d0..de10948f7 100644 --- a/frontend/src/app/components/address/address.component.html +++ b/frontend/src/app/components/address/address.component.html @@ -220,14 +220,14 @@ Volume - + Type - + diff --git a/frontend/src/app/components/address/address.component.ts b/frontend/src/app/components/address/address.component.ts index 10054727a..477954805 100644 --- a/frontend/src/app/components/address/address.component.ts +++ b/frontend/src/app/components/address/address.component.ts @@ -2,7 +2,7 @@ import { Component, OnInit, OnDestroy, HostListener } from '@angular/core'; import { ActivatedRoute, ParamMap } from '@angular/router'; import { ElectrsApiService } from '../../services/electrs-api.service'; import { switchMap, filter, catchError, map, tap } from 'rxjs/operators'; -import { Address, ChainStats, Transaction, Vin, Vout } from '../../interfaces/electrs.interface'; +import { Address, ChainStats, Transaction, Vin } from '../../interfaces/electrs.interface'; import { WebsocketService } from '../../services/websocket.service'; import { StateService } from '../../services/state.service'; import { AudioService } from '../../services/audio.service'; @@ -11,6 +11,7 @@ import { of, merge, Subscription, Observable } from 'rxjs'; import { SeoService } from '../../services/seo.service'; import { seoDescriptionNetwork } from '../../shared/common.utils'; import { AddressInformation } from '../../interfaces/node-api.interface'; +import { AddressTypeInfo } from '../../shared/address-utils'; class AddressStats implements ChainStats { address: string; @@ -112,14 +113,13 @@ export class AddressComponent implements OnInit, OnDestroy { blockTxSubscription: Subscription; addressLoadingStatus$: Observable; addressInfo: null | AddressInformation = null; + addressTypeInfo: null | AddressTypeInfo; fullyLoaded = false; chainStats: AddressStats; mempoolStats: AddressStats; exampleChannel?: any; - exampleVin?: Vin; - exampleVout?: Vout; now = Date.now() / 1000; balancePeriod: 'all' | '1m' = 'all'; @@ -161,8 +161,6 @@ export class AddressComponent implements OnInit, OnDestroy { this.transactions = null; this.addressInfo = null; this.exampleChannel = null; - this.exampleVin = null; - this.exampleVout = null; document.body.scrollTo(0, 0); this.addressString = params.get('id') || ''; if (/^[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,100}|04[a-fA-F0-9]{128}|(02|03)[a-fA-F0-9]{64}$/.test(this.addressString)) { @@ -171,6 +169,8 @@ export class AddressComponent implements OnInit, OnDestroy { this.seoService.setTitle($localize`:@@address.component.browser-title:Address: ${this.addressString}:INTERPOLATION:`); this.seoService.setDescription($localize`:@@meta.description.bitcoin.address:See mempool transactions, confirmed transactions, balance, and more for ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'}${seoDescriptionNetwork(this.stateService.network)} address ${this.addressString}:INTERPOLATION:.`); + this.addressTypeInfo = new AddressTypeInfo(this.stateService.network || 'mainnet', this.addressString); + return merge( of(true), this.stateService.connectionState$ @@ -268,17 +268,13 @@ export class AddressComponent implements OnInit, OnDestroy { } this.isLoadingTransactions = false; + let addressVin: Vin[] = []; for (const tx of this.transactions) { - if (!this.exampleVin) { - this.exampleVin = tx.vin.find(v => v.prevout?.scriptpubkey_address === this.address.address); - } - if (!this.exampleVout) { - this.exampleVout = tx.vout.find(v => v.scriptpubkey_address === this.address.address); - } - if (this.exampleVin && this.exampleVout) { - break; - } + addressVin = addressVin.concat(tx.vin.filter(v => v.prevout?.scriptpubkey_address === this.address.address)); } + this.addressTypeInfo.processInputs(addressVin); + // hack to trigger change detection + this.addressTypeInfo = this.addressTypeInfo.clone(); if (!this.showBalancePeriod()) { this.setBalancePeriod('all'); diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.html b/frontend/src/app/components/transactions-list/transactions-list.component.html index ee7ac52f5..88a984942 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.html +++ b/frontend/src/app/components/transactions-list/transactions-list.component.html @@ -75,7 +75,7 @@ {{ vin.prevout.scriptpubkey_type?.toUpperCase() }}
- +
diff --git a/frontend/src/app/shared/address-utils.ts b/frontend/src/app/shared/address-utils.ts new file mode 100644 index 000000000..c5e1fcf3d --- /dev/null +++ b/frontend/src/app/shared/address-utils.ts @@ -0,0 +1,193 @@ +import '@angular/localize/init'; +import { ScriptInfo } from './script.utils'; +import { Vin } from '../interfaces/electrs.interface'; +import { BECH32_CHARS_LW, BASE58_CHARS, HEX_CHARS } from './regex.utils'; + +export type AddressType = 'fee' + | 'empty' + | 'provably_unspendable' + | 'op_return' + | 'multisig' + | 'p2pk' + | 'p2pkh' + | 'p2sh' + | 'p2sh-p2wpkh' + | 'p2sh-p2wsh' + | 'v0_p2wpkh' + | 'v0_p2wsh' + | 'v1_p2tr' + | 'confidential' + | 'unknown' + +const ADDRESS_PREFIXES = { + mainnet: { + base58: { + pubkey: ['1'], + script: ['3'], + }, + bech32: 'bc1', + }, + testnet: { + base58: { + pubkey: ['m', 'n'], + script: '2', + }, + bech32: 'tb1', + }, + testnet4: { + base58: { + pubkey: ['m', 'n'], + script: '2', + }, + bech32: 'tb1', + }, + signet: { + base58: { + pubkey: ['m', 'n'], + script: '2', + }, + bech32: 'tb1', + }, + liquid: { + base58: { + pubkey: ['P','Q'], + script: ['G','H'], + confidential: ['V'], + }, + bech32: 'ex1', + confidential: 'lq1', + }, + liquidtestnet: { + base58: { + pubkey: ['F'], + script: ['8','9'], + confidential: ['V'], // TODO: check if this is actually correct + }, + bech32: 'tex1', + confidential: 'tlq1', + }, +}; + +// precompiled regexes for common address types (excluding prefixes) +const base58Regex = RegExp('^' + BASE58_CHARS + '{26,34}$'); +const confidentialb58Regex = RegExp('^[TJ]' + BASE58_CHARS + '{78}$'); +const p2wpkhRegex = RegExp('^q' + BECH32_CHARS_LW + '{38}$'); +const p2wshRegex = RegExp('^q' + BECH32_CHARS_LW + '{58}$'); +const p2trRegex = RegExp('^p' + BECH32_CHARS_LW + '{58}$'); +const pubkeyRegex = RegExp('^' + `(04${HEX_CHARS}{128})|(0[23]${HEX_CHARS}{64})$`); + +export function detectAddressType(address: string, network: string): AddressType { + // normal address types + const firstChar = address.substring(0, 1); + if (ADDRESS_PREFIXES[network].base58.pubkey.includes(firstChar) && base58Regex.test(address.slice(1))) { + return 'p2pkh'; + } else if (ADDRESS_PREFIXES[network].base58.script.includes(firstChar) && base58Regex.test(address.slice(1))) { + return 'p2sh'; + } else if (address.startsWith(ADDRESS_PREFIXES[network].bech32)) { + const suffix = address.slice(ADDRESS_PREFIXES[network].bech32.length); + if (p2wpkhRegex.test(suffix)) { + return 'v0_p2wpkh'; + } else if (p2wshRegex.test(suffix)) { + return 'v0_p2wsh'; + } else if (p2trRegex.test(suffix)) { + return 'v1_p2tr'; + } + } + + // p2pk + if (pubkeyRegex.test(address)) { + return 'p2pk'; + } + + // liquid-specific types + if (network.startsWith('liquid')) { + if (ADDRESS_PREFIXES[network].base58.confidential.includes(firstChar) && confidentialb58Regex.test(address.slice(1))) { + return 'confidential'; + } else if (address.startsWith(ADDRESS_PREFIXES[network].confidential)) { + return 'confidential'; + } + } + + return 'unknown'; +} + +/** + * Parses & classifies address types + properties from address strings + * + * can optionally augment this data with examples of spends from the address, + * e.g. to classify revealed scripts for scripthash-type addresses. + */ +export class AddressTypeInfo { + network: string; + address: string; + type: AddressType; + // script data + scripts: Map; // raw script + // flags + isMultisig?: { m: number, n: number }; + tapscript?: boolean; + + constructor (network: string, address: string, type?: AddressType, vin?: Vin[]) { + this.network = network; + this.address = address; + this.scripts = new Map(); + if (type) { + this.type = type; + } else { + this.type = detectAddressType(address, network); + } + this.processInputs(vin); + } + + public clone(): AddressTypeInfo { + const cloned = new AddressTypeInfo(this.network, this.address, this.type); + cloned.scripts = new Map(Array.from(this.scripts, ([key, value]) => [key, value?.clone()])); + cloned.isMultisig = this.isMultisig; + cloned.tapscript = this.tapscript; + return cloned; + } + + public processInputs(vin: Vin[] = []): void { + // taproot can have multiple script paths + if (this.type === 'v1_p2tr') { + for (const v of vin) { + if (v.inner_witnessscript_asm) { + this.tapscript = true; + const controlBlock = v.witness[v.witness.length - 1].startsWith('50') ? v.witness[v.witness.length - 2] : v.witness[v.witness.length - 1]; + this.processScript(new ScriptInfo('inner_witnessscript', undefined, v.inner_witnessscript_asm, v.witness, controlBlock)); + } + } + // for single-script types, if we've seen one input we've seen them all + } else if (['p2sh', 'v0_p2wsh'].includes(this.type)) { + if (!this.scripts.size && vin.length) { + const v = vin[0]; + // wrapped segwit + if (this.type === 'p2sh' && v.witness?.length) { + if (v.scriptsig.startsWith('160014')) { + this.type = 'p2sh-p2wpkh'; + } else if (v.scriptsig.startsWith('220020')) { + this.type = 'p2sh-p2wsh'; + } + } + // real script + if (this.type !== 'p2sh-p2wpkh') { + if (v.inner_witnessscript_asm) { + this.processScript(new ScriptInfo('inner_witnessscript', undefined, v.inner_witnessscript_asm, v.witness)); + } else if (v.inner_redeemscript_asm) { + this.processScript(new ScriptInfo('inner_redeemscript', undefined, v.inner_redeemscript_asm, v.witness)); + } else if (v.scriptsig || v.scriptsig_asm) { + this.processScript(new ScriptInfo('scriptsig', v.scriptsig, v.scriptsig_asm, v.witness)); + } + } + } + } + // and there's nothing more to learn from processing inputs for non-scripthash types + } + + private processScript(script: ScriptInfo): void { + this.scripts.set(script.key, script); + if (script.template?.type === 'multisig') { + this.isMultisig = { m: script.template['m'], n: script.template['n'] }; + } + } +} \ No newline at end of file diff --git a/frontend/src/app/shared/components/address-type/address-type.component.html b/frontend/src/app/shared/components/address-type/address-type.component.html index fbe041cb5..fe4286689 100644 --- a/frontend/src/app/shared/components/address-type/address-type.component.html +++ b/frontend/src/app/shared/components/address-type/address-type.component.html @@ -1,4 +1,4 @@ -@switch (vout?.scriptpubkey_type || null) { +@switch (address.type || null) { @case ('fee') { fee } @@ -24,6 +24,6 @@ unknown } @default { - {{ vout.scriptpubkey_type.toUpperCase() }} + {{ address.type.toUpperCase() }} } } \ No newline at end of file diff --git a/frontend/src/app/shared/components/address-type/address-type.component.ts b/frontend/src/app/shared/components/address-type/address-type.component.ts index 34077330a..1a2456c07 100644 --- a/frontend/src/app/shared/components/address-type/address-type.component.ts +++ b/frontend/src/app/shared/components/address-type/address-type.component.ts @@ -1,5 +1,5 @@ import { Component, Input } from '@angular/core'; -import { Vout } from '../../../interfaces/electrs.interface'; +import { AddressTypeInfo } from '../../address-utils'; @Component({ selector: 'app-address-type', @@ -7,5 +7,5 @@ import { Vout } from '../../../interfaces/electrs.interface'; styleUrls: [] }) export class AddressTypeComponent { - @Input() vout: Vout; + @Input() address: AddressTypeInfo; } diff --git a/frontend/src/app/shared/regex.utils.ts b/frontend/src/app/shared/regex.utils.ts index 187111a59..cdc2963e8 100644 --- a/frontend/src/app/shared/regex.utils.ts +++ b/frontend/src/app/shared/regex.utils.ts @@ -1,14 +1,14 @@ import { Env } from '../services/state.service'; // all base58 characters -const BASE58_CHARS = `[a-km-zA-HJ-NP-Z1-9]`; +export const BASE58_CHARS = `[a-km-zA-HJ-NP-Z1-9]`; // all bech32 characters (after the separator) -const BECH32_CHARS_LW = `[ac-hj-np-z02-9]`; +export const BECH32_CHARS_LW = `[ac-hj-np-z02-9]`; const BECH32_CHARS_UP = `[AC-HJ-NP-Z02-9]`; // Hex characters -const HEX_CHARS = `[a-fA-F0-9]`; +export const HEX_CHARS = `[a-fA-F0-9]`; // A regex to say "A single 0 OR any number with no leading zeroes" // Capped at 9 digits so as to not be confused with lightning channel IDs (which are around 17 digits) diff --git a/frontend/src/app/shared/script.utils.ts b/frontend/src/app/shared/script.utils.ts index 556cd40f2..171112dcc 100644 --- a/frontend/src/app/shared/script.utils.ts +++ b/frontend/src/app/shared/script.utils.ts @@ -145,8 +145,116 @@ for (let i = 187; i <= 255; i++) { export { opcodes }; +export type ScriptType = 'scriptpubkey' + | 'scriptsig' + | 'inner_witnessscript' + | 'inner_redeemscript' + +export interface ScriptTemplate { + type: string; + label: string; +} + +export const ScriptTemplates: { [type: string]: (...args: any) => ScriptTemplate } = { + liquid_peg_out: () => ({ type: 'liquid_peg_out', label: 'Liquid Peg Out' }), + liquid_peg_out_emergency: () => ({ type: 'liquid_peg_out_emergency', label: 'Emergency Liquid Peg Out' }), + ln_force_close: () => ({ type: 'ln_force_close', label: 'Lightning Force Close' }), + ln_force_close_revoked: () => ({ type: 'ln_force_close_revoked', label: 'Revoked Lightning Force Close' }), + ln_htlc: () => ({ type: 'ln_htlc', label: 'Lightning HTLC' }), + ln_htlc_revoked: () => ({ type: 'ln_htlc_revoked', label: 'Revoked Lightning HTLC' }), + ln_htlc_expired: () => ({ type: 'ln_htlc_expired', label: 'Expired Lightning HTLC' }), + ln_anchor: () => ({ type: 'ln_anchor', label: 'Lightning Anchor' }), + ln_anchor_swept: () => ({ type: 'ln_anchor_swept', label: 'Swept Lightning Anchor' }), + multisig: (m: number, n: number) => ({ type: 'multisig', m, n, label: $localize`:@@address-label.multisig:Multisig ${m}:multisigM: of ${n}:multisigN:` }), +}; + +export class ScriptInfo { + type: ScriptType; + scriptPath?: string; + hex?: string; + asm?: string; + template: ScriptTemplate; + + constructor(type: ScriptType, hex?: string, asm?: string, witness?: string[], scriptPath?: string) { + this.type = type; + this.hex = hex; + this.asm = asm; + if (scriptPath) { + this.scriptPath = scriptPath; + } + if (this.asm) { + this.template = detectScriptTemplate(this.type, this.asm, witness); + } + } + + public clone(): ScriptInfo { + return { ...this }; + } + + get key(): string { + return this.type + (this.scriptPath || ''); + } +} + +/** parses an inner_witnessscript + witness stack, and detects named script types */ +export function detectScriptTemplate(type: ScriptType, script_asm: string, witness?: string[]): ScriptTemplate | undefined { + if (type === 'inner_witnessscript' && witness?.length) { + if (script_asm.indexOf('OP_DEPTH OP_PUSHNUM_12 OP_EQUAL OP_IF OP_PUSHNUM_11') === 0 || script_asm.indexOf('OP_PUSHNUM_15 OP_CHECKMULTISIG OP_IFDUP OP_NOTIF OP_PUSHBYTES_2') === 1259) { + if (witness.length > 11) { + return ScriptTemplates.liquid_peg_out(); + } else { + return ScriptTemplates.liquid_peg_out_emergency(); + } + } + + const topElement = witness[witness.length - 2]; + if (/^OP_IF OP_PUSHBYTES_33 \w{66} OP_ELSE OP_PUSH(NUM_\d+|BYTES_(1 \w{2}|2 \w{4})) OP_CSV OP_DROP OP_PUSHBYTES_33 \w{66} OP_ENDIF OP_CHECKSIG$/.test(script_asm)) { + // https://github.com/lightning/bolts/blob/master/03-transactions.md#commitment-transaction-outputs + if (topElement === '01') { + // top element is '01' to get in the revocation path + return ScriptTemplates.ln_force_close_revoked(); + } else { + // top element is '', this is a delayed to_local output + return ScriptTemplates.ln_force_close(); + } + } else if ( + /^OP_DUP OP_HASH160 OP_PUSHBYTES_20 \w{40} OP_EQUAL OP_IF OP_CHECKSIG OP_ELSE OP_PUSHBYTES_33 \w{66} OP_SWAP OP_SIZE OP_PUSHBYTES_1 20 OP_EQUAL OP_NOTIF OP_DROP OP_PUSHNUM_2 OP_SWAP OP_PUSHBYTES_33 \w{66} OP_PUSHNUM_2 OP_CHECKMULTISIG OP_ELSE OP_HASH160 OP_PUSHBYTES_20 \w{40} OP_EQUALVERIFY OP_CHECKSIG OP_ENDIF (OP_PUSHNUM_1 OP_CSV OP_DROP |)OP_ENDIF$/.test(script_asm) || + /^OP_DUP OP_HASH160 OP_PUSHBYTES_20 \w{40} OP_EQUAL OP_IF OP_CHECKSIG OP_ELSE OP_PUSHBYTES_33 \w{66} OP_SWAP OP_SIZE OP_PUSHBYTES_1 20 OP_EQUAL OP_IF OP_HASH160 OP_PUSHBYTES_20 \w{40} OP_EQUALVERIFY OP_PUSHNUM_2 OP_SWAP OP_PUSHBYTES_33 \w{66} OP_PUSHNUM_2 OP_CHECKMULTISIG OP_ELSE OP_DROP OP_PUSHBYTES_3 \w{6} OP_CLTV OP_DROP OP_CHECKSIG OP_ENDIF (OP_PUSHNUM_1 OP_CSV OP_DROP |)OP_ENDIF$/.test(script_asm) + ) { + // https://github.com/lightning/bolts/blob/master/03-transactions.md#offered-htlc-outputs + // https://github.com/lightning/bolts/blob/master/03-transactions.md#received-htlc-outputs + if (topElement.length === 66) { + // top element is a public key + return ScriptTemplates.ln_htlc_revoked(); + } else if (topElement) { + // top element is a preimage + return ScriptTemplates.ln_htlc(); + } else { + // top element is '' to get in the expiry of the script + return ScriptTemplates.ln_htlc_expired(); + } + } else if (/^OP_PUSHBYTES_33 \w{66} OP_CHECKSIG OP_IFDUP OP_NOTIF OP_PUSHNUM_16 OP_CSV OP_ENDIF$/.test(script_asm)) { + // https://github.com/lightning/bolts/blob/master/03-transactions.md#to_local_anchor-and-to_remote_anchor-output-option_anchors + if (topElement) { + // top element is a signature + return ScriptTemplates.ln_anchor(); + } else { + // top element is '', it has been swept after 16 blocks + return ScriptTemplates.ln_anchor_swept(); + } + } + } + + const multisig = parseMultisigScript(script_asm); + if (multisig) { + return ScriptTemplates.multisig(multisig.m, multisig.n); + } + + return; +} + /** extracts m and n from a multisig script (asm), returns nothing if it is not a multisig script */ -export function parseMultisigScript(script: string): void | { m: number, n: number } { +export function parseMultisigScript(script: string): undefined | { m: number, n: number } { if (!script) { return; } From fb621f98123778923a4787c2e213b3f55fcf23c4 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 14 Jun 2024 15:51:00 +0000 Subject: [PATCH 07/35] Address redesign liquid & layout fixes --- .../components/address/address.component.html | 20 +++++++++++++------ .../components/address/address.component.scss | 4 ++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/components/address/address.component.html b/frontend/src/app/components/address/address.component.html index de10948f7..3d6f1b86d 100644 --- a/frontend/src/app/components/address/address.component.html +++ b/frontend/src/app/components/address/address.component.html @@ -28,23 +28,31 @@ - + @if (network === 'liquid' || network === 'liquidtestnet') { + + } @else { + + } } @else {
- +
- + @if (network === 'liquid' || network === 'liquidtestnet') { + + } @else { + + }
- +
@@ -227,7 +235,7 @@ - + @@ -235,7 +243,7 @@ diff --git a/frontend/src/app/components/address/address.component.scss b/frontend/src/app/components/address/address.component.scss index b4ab5ac8a..c7d37425e 100644 --- a/frontend/src/app/components/address/address.component.scss +++ b/frontend/src/app/components/address/address.component.scss @@ -27,6 +27,10 @@ text-align: left; } } + + &.wrap-cell { + white-space: normal; + } } } From ce46aae8ccfe1d56d50b4b8df165ba825299bd62 Mon Sep 17 00:00:00 2001 From: softsimon Date: Sat, 15 Jun 2024 00:22:33 +0200 Subject: [PATCH 08/35] Default frontend network setting --- frontend/mempool-frontend-config.sample.json | 2 + .../master-page/master-page.component.html | 2 +- frontend/src/app/services/api.service.ts | 2 +- .../src/app/services/navigation.service.ts | 45 +++++++++---------- frontend/src/app/services/state.service.ts | 10 ++++- .../src/app/services/websocket.service.ts | 4 +- .../pipes/relative-url/relative-url.pipe.ts | 4 +- 7 files changed, 39 insertions(+), 30 deletions(-) diff --git a/frontend/mempool-frontend-config.sample.json b/frontend/mempool-frontend-config.sample.json index c111f35af..43c24b7e5 100644 --- a/frontend/mempool-frontend-config.sample.json +++ b/frontend/mempool-frontend-config.sample.json @@ -4,6 +4,7 @@ "SIGNET_ENABLED": false, "LIQUID_ENABLED": false, "LIQUID_TESTNET_ENABLED": false, + "MAINNET_ENABLED": true, "ITEMS_PER_PAGE": 10, "KEEP_BLOCKS_AMOUNT": 8, "NGINX_PROTOCOL": "http", @@ -12,6 +13,7 @@ "BLOCK_WEIGHT_UNITS": 4000000, "MEMPOOL_BLOCKS_AMOUNT": 8, "BASE_MODULE": "mempool", + "DEFAULT_NETWORK": "", "MEMPOOL_WEBSITE_URL": "https://mempool.space", "LIQUID_WEBSITE_URL": "https://liquid.network", "MINING_DASHBOARD": true, 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 8a67d2b4c..551bbe279 100644 --- a/frontend/src/app/components/master-page/master-page.component.html +++ b/frontend/src/app/components/master-page/master-page.component.html @@ -67,7 +67,7 @@
- Mainnet + Mainnet Signet Testnet3 Testnet4 beta diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index e9cb7e2e8..e763ae246 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -29,7 +29,7 @@ export class ApiService { } this.apiBasePath = ''; // assume mainnet by default this.stateService.networkChanged$.subscribe((network) => { - this.apiBasePath = network ? '/' + network : ''; + this.apiBasePath = network && network !== this.stateService.env.DEFAULT_NETWORK ? '/' + network : ''; }); } diff --git a/frontend/src/app/services/navigation.service.ts b/frontend/src/app/services/navigation.service.ts index 1a22c1371..5dd5dfded 100644 --- a/frontend/src/app/services/navigation.service.ts +++ b/frontend/src/app/services/navigation.service.ts @@ -1,32 +1,31 @@ import { Injectable } from '@angular/core'; -import { Router, ActivatedRoute, NavigationEnd, ActivatedRouteSnapshot } from '@angular/router'; +import { Router, 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: 'testnet4', path: '/testnet4' }, - { name: 'signet', path: '/signet' }, - ], - }, - liquid: { - subnets: [ - { name: 'liquid', path: '' }, - { name: 'liquidtestnet', path: '/testnet' }, - ], - } -}; -const networks = Object.keys(networkModules); - @Injectable({ providedIn: 'root' }) export class NavigationService { subnetPaths = new BehaviorSubject>({}); + networkModules = { + bitcoin: { + subnets: [ + { name: 'mainnet', path: '' }, + { name: 'testnet', path: this.stateService.env.DEFAULT_NETWORK === 'testnet' ? '/' : '/testnet' }, + { name: 'testnet4', path: this.stateService.env.DEFAULT_NETWORK === 'testnet4' ? '/' : '/testnet4' }, + { name: 'signet', path: this.stateService.env.DEFAULT_NETWORK === 'signet' ? '/' : '/signet' }, + ], + }, + liquid: { + subnets: [ + { name: 'liquid', path: '' }, + { name: 'liquidtestnet', path: '/testnet' }, + ], + } + }; + networks = Object.keys(this.networkModules); constructor( private stateService: StateService, @@ -46,11 +45,11 @@ export class NavigationService { 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) { + while (!this.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 => { + this.networks.forEach(network => { if (networkPaths[network] == null) { networkPaths[network] = path; } @@ -59,7 +58,7 @@ export class NavigationService { // 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 => { + this.networks.forEach(network => { if (!route.data.networks.includes(network)) { if (networkPaths[network] == null) { networkPaths[network] = path; @@ -76,7 +75,7 @@ export class NavigationService { } const subnetPaths = {}; - Object.entries(networkModules).forEach(([key, network]) => { + Object.entries(this.networkModules).forEach(([key, network]) => { network.subnets.forEach(subnet => { subnetPaths[subnet.name] = subnet.path + (networkPaths[key] != null ? networkPaths[key] : path); }); diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index 8c90921ca..1e60030f4 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -43,6 +43,7 @@ export interface Customization { } export interface Env { + MAINNET_ENABLED: boolean; TESTNET_ENABLED: boolean; TESTNET4_ENABLED: boolean; SIGNET_ENABLED: boolean; @@ -52,6 +53,7 @@ export interface Env { KEEP_BLOCKS_AMOUNT: number; OFFICIAL_MEMPOOL_SPACE: boolean; BASE_MODULE: string; + DEFAULT_NETWORK: string; NGINX_PROTOCOL?: string; NGINX_HOSTNAME?: string; NGINX_PORT?: string; @@ -77,12 +79,14 @@ export interface Env { } const defaultEnv: Env = { + 'MAINNET_ENABLED': true, 'TESTNET_ENABLED': false, 'TESTNET4_ENABLED': false, 'SIGNET_ENABLED': false, 'LIQUID_ENABLED': false, 'LIQUID_TESTNET_ENABLED': false, 'BASE_MODULE': 'mempool', + 'DEFAULT_NETWORK': '', 'ITEMS_PER_PAGE': 10, 'KEEP_BLOCKS_AMOUNT': 8, 'OFFICIAL_MEMPOOL_SPACE': false, @@ -202,6 +206,8 @@ export class StateService { this.env.MINING_DASHBOARD = false; } + this.network = this.env.DEFAULT_NETWORK; + if (this.isBrowser) { this.setNetworkBasedonUrl(window.location.pathname); this.setLightningBasedonUrl(window.location.pathname); @@ -357,8 +363,8 @@ export class StateService { this.networkChanged$.next(this.env.BASE_MODULE); } } else if (this.network !== '') { - this.network = ''; - this.networkChanged$.next(''); + this.network = this.env.DEFAULT_NETWORK; + this.networkChanged$.next(this.env.DEFAULT_NETWORK); } } } diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts index fbadf0de3..6d5507632 100644 --- a/frontend/src/app/services/websocket.service.ts +++ b/frontend/src/app/services/websocket.service.ts @@ -55,7 +55,7 @@ export class WebsocketService { .pipe(take(1)) .subscribe((response) => this.handleResponse(response)); } else { - this.network = this.stateService.network; + this.network = this.stateService.network === this.stateService.env.DEFAULT_NETWORK ? '' : this.stateService.network; this.websocketSubject = webSocket(this.webSocketUrl.replace('{network}', this.network ? '/' + this.network : '')); const { response: theInitData } = this.transferState.get(initData, null) || {}; @@ -75,7 +75,7 @@ export class WebsocketService { if (network === this.network) { return; } - this.network = network; + this.network = network === this.stateService.env.DEFAULT_NETWORK ? '' : network; clearTimeout(this.onlineCheckTimeout); clearTimeout(this.onlineCheckTimeoutTwo); diff --git a/frontend/src/app/shared/pipes/relative-url/relative-url.pipe.ts b/frontend/src/app/shared/pipes/relative-url/relative-url.pipe.ts index 4211765df..52723b0a0 100644 --- a/frontend/src/app/shared/pipes/relative-url/relative-url.pipe.ts +++ b/frontend/src/app/shared/pipes/relative-url/relative-url.pipe.ts @@ -12,7 +12,9 @@ export class RelativeUrlPipe implements PipeTransform { transform(value: string, swapNetwork?: string): string { let network = swapNetwork || this.stateService.network; - if (network === 'mainnet') network = ''; + if (network === 'mainnet' || network === this.stateService.env.DEFAULT_NETWORK) { + network = ''; + } if (this.stateService.env.BASE_MODULE === 'liquid' && network === 'liquidtestnet') { network = 'testnet'; } else if (this.stateService.env.BASE_MODULE !== 'mempool') { From 9dbf3b54fbd381e31618ca5000563eccc46712af Mon Sep 17 00:00:00 2001 From: softsimon Date: Sat, 15 Jun 2024 05:18:35 +0200 Subject: [PATCH 09/35] Electrs network routing fix --- frontend/src/app/lightning/lightning-api.service.ts | 2 +- frontend/src/app/services/electrs-api.service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/lightning/lightning-api.service.ts b/frontend/src/app/lightning/lightning-api.service.ts index 736c12c13..200a24c58 100644 --- a/frontend/src/app/lightning/lightning-api.service.ts +++ b/frontend/src/app/lightning/lightning-api.service.ts @@ -23,7 +23,7 @@ export class LightningApiService { } this.apiBasePath = ''; // assume mainnet by default this.stateService.networkChanged$.subscribe((network) => { - this.apiBasePath = network ? '/' + network : ''; + this.apiBasePath = network && network !== this.stateService.env.DEFAULT_NETWORK ? '/' + network : ''; }); } diff --git a/frontend/src/app/services/electrs-api.service.ts b/frontend/src/app/services/electrs-api.service.ts index 57dcbb762..974efea52 100644 --- a/frontend/src/app/services/electrs-api.service.ts +++ b/frontend/src/app/services/electrs-api.service.ts @@ -25,7 +25,7 @@ export class ElectrsApiService { } this.apiBasePath = ''; // assume mainnet by default this.stateService.networkChanged$.subscribe((network) => { - this.apiBasePath = network ? '/' + network : ''; + this.apiBasePath = network && network !== this.stateService.env.DEFAULT_NETWORK ? '/' + network : ''; }); } From 84dae82e9036642b8e34119aedc1daecf47530e7 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Sat, 15 Jun 2024 18:50:27 +0900 Subject: [PATCH 10/35] [about page] use grumpy guy instead of boring placeholder --- .../app/components/about/about.component.html | 12 +++++----- .../app/components/about/about.component.scss | 3 --- frontend/src/resources/profile/grumpy.svg | 22 +++++++++++++++++++ 3 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 frontend/src/resources/profile/grumpy.svg diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html index c0d39bbc8..7d1396145 100644 --- a/frontend/src/app/components/about/about.component.html +++ b/frontend/src/app/components/about/about.component.html @@ -189,7 +189,7 @@ - + @@ -201,7 +201,7 @@ @@ -214,7 +214,7 @@ @@ -355,7 +355,7 @@ @@ -369,7 +369,7 @@
- + {{ contributor.name }} @@ -381,7 +381,7 @@
- + {{ contributor.name }} diff --git a/frontend/src/app/components/about/about.component.scss b/frontend/src/app/components/about/about.component.scss index d1c15f838..aecff02fc 100644 --- a/frontend/src/app/components/about/about.component.scss +++ b/frontend/src/app/components/about/about.component.scss @@ -10,9 +10,6 @@ margin: 25px; line-height: 32px; } - .unknown { - border: 1px solid #b4b4b4; - } .image.not-rounded { border-radius: 0; diff --git a/frontend/src/resources/profile/grumpy.svg b/frontend/src/resources/profile/grumpy.svg new file mode 100644 index 000000000..5de128db1 --- /dev/null +++ b/frontend/src/resources/profile/grumpy.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + From c0ec9f70c37730862366d0e6c42f8ad397aa659c Mon Sep 17 00:00:00 2001 From: softsimon Date: Sat, 15 Jun 2024 13:00:23 +0200 Subject: [PATCH 11/35] Fix dropdown visibility when using only 1 random network --- .../master-page/master-page.component.html | 2 +- .../master-page/master-page.component.ts | 26 +++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) 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 551bbe279..591a8730e 100644 --- a/frontend/src/app/components/master-page/master-page.component.html +++ b/frontend/src/app/components/master-page/master-page.component.html @@ -62,7 +62,7 @@ } -
Type
Unconfidential - +
+
- + diff --git a/frontend/src/app/components/server-health/server-health.component.scss b/frontend/src/app/components/server-health/server-health.component.scss index bbf94288f..ff4ec1384 100644 --- a/frontend/src/app/components/server-health/server-health.component.scss +++ b/frontend/src/app/components/server-health/server-health.component.scss @@ -20,26 +20,21 @@ td, th { padding: 0.25em; + width: 0%; &.rank, &.flag { - width: 28px; text-align: right; } &.updated { display: none; - width: 130px; text-align: right; - white-space: pre-wrap; + white-space: nowrap; } &.rtt, &.height { - width: 92px; text-align: right; } &.only-small { display: table-cell; - &.rtt { - width: 60px; - } } &.only-large { display: none; @@ -48,21 +43,17 @@ padding-right: 0.5em; } &.host { - width: auto; + width: 100%; + max-width: 0; overflow: hidden; text-overflow: ellipsis; + white-space: nowrap; } @media (min-width: 576px) { - &.rank, &.flag { - width: 32px; - } &.updated { display: table-cell; } - &.rtt, &.height { - width: 96px; - } &.only-small { display: none; } diff --git a/frontend/src/app/components/server-health/server-health.component.ts b/frontend/src/app/components/server-health/server-health.component.ts index bb4d59959..37e23f12a 100644 --- a/frontend/src/app/components/server-health/server-health.component.ts +++ b/frontend/src/app/components/server-health/server-health.component.ts @@ -62,7 +62,7 @@ export class ServerHealthComponent implements OnInit { getLastUpdateSeconds(host: HealthCheckHost): string { if (host.lastChecked) { const seconds = Math.ceil((this.now - host.lastChecked) / 1000); - return `${seconds} second${seconds > 1 ? 's' : ' '} ago`; + return `${seconds} s`; } else { return '~'; } From 0719b2011069f824fa661c979cc92c9f7c57a035 Mon Sep 17 00:00:00 2001 From: softsimon Date: Thu, 20 Jun 2024 12:22:54 +0900 Subject: [PATCH 22/35] Deprecate pool_unique_id --- backend/src/api/services/acceleration.ts | 5 +---- backend/src/repositories/AccelerationRepository.ts | 4 ++-- frontend/src/app/components/block/block.component.ts | 2 +- frontend/src/app/components/tracker/tracker.component.ts | 2 +- .../src/app/components/transaction/transaction.component.ts | 2 +- frontend/src/app/docs/api-docs/api-docs-data.ts | 6 +----- 6 files changed, 7 insertions(+), 14 deletions(-) diff --git a/backend/src/api/services/acceleration.ts b/backend/src/api/services/acceleration.ts index eba5ab7e6..7debe7119 100644 --- a/backend/src/api/services/acceleration.ts +++ b/backend/src/api/services/acceleration.ts @@ -31,10 +31,7 @@ export interface AccelerationHistory { feeDelta: number, blockHash: string, blockHeight: number, - pools: { - pool_unique_id: number, - username: string, - }[], + pools: number[]; }; class AccelerationApi { diff --git a/backend/src/repositories/AccelerationRepository.ts b/backend/src/repositories/AccelerationRepository.ts index eea60fcff..c08e256b6 100644 --- a/backend/src/repositories/AccelerationRepository.ts +++ b/backend/src/repositories/AccelerationRepository.ts @@ -308,10 +308,10 @@ class AccelerationRepository { } const accelerationSummaries = accelerations.map(acc => ({ ...acc, - pools: acc.pools.map(pool => pool.pool_unique_id), + pools: acc.pools, })) for (const acc of accelerations) { - if (blockTxs[acc.txid] && acc.pools.some(pool => pool.pool_unique_id === block.extras.pool.id)) { + if (blockTxs[acc.txid] && acc.pools.some(pool => pool === block.extras.pool.id)) { const tx = blockTxs[acc.txid]; const accelerationInfo = accelerationCosts.getAccelerationInfo(tx, boostRate, transactions); accelerationInfo.cost = Math.max(0, Math.min(acc.feeDelta, accelerationInfo.cost)); diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts index d762a879e..471c1dfca 100644 --- a/frontend/src/app/components/block/block.component.ts +++ b/frontend/src/app/components/block/block.component.ts @@ -313,7 +313,7 @@ export class BlockComponent implements OnInit, OnDestroy { const acceleratedInBlock = {}; for (const acc of accelerations) { - if (acc.pools?.some(pool => pool === this.block?.extras?.pool.id || pool?.['pool_unique_id'] === this.block?.extras?.pool.id)) { + if (acc.pools?.some(pool => pool === this.block?.extras?.pool.id)) { acceleratedInBlock[acc.txid] = acc; } } diff --git a/frontend/src/app/components/tracker/tracker.component.ts b/frontend/src/app/components/tracker/tracker.component.ts index 1685ee530..7de851c6e 100644 --- a/frontend/src/app/components/tracker/tracker.component.ts +++ b/frontend/src/app/components/tracker/tracker.component.ts @@ -665,7 +665,7 @@ export class TrackerComponent implements OnInit, OnDestroy { } setIsAccelerated(initialState: boolean = false) { - this.isAcceleration = (this.tx.acceleration || (this.accelerationInfo && this.pool && this.accelerationInfo.pools.some(pool => (pool === this.pool.id || pool?.['pool_unique_id'] === this.pool.id)))); + this.isAcceleration = (this.tx.acceleration || (this.accelerationInfo && this.pool && this.accelerationInfo.pools.some(pool => (pool === this.pool.id)))); } dismissAccelAlert(): void { diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index 9f7e2aa40..d03a8a9cc 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -726,7 +726,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { } setIsAccelerated(initialState: boolean = false) { - this.isAcceleration = (this.tx.acceleration || (this.accelerationInfo && this.pool && this.accelerationInfo.pools.some(pool => (pool === this.pool.id || pool?.['pool_unique_id'] === this.pool.id)))); + this.isAcceleration = (this.tx.acceleration || (this.accelerationInfo && this.pool && this.accelerationInfo.pools.some(pool => (pool === this.pool.id)))); if (this.isAcceleration && initialState) { this.showAccelerationSummary = false; } diff --git a/frontend/src/app/docs/api-docs/api-docs-data.ts b/frontend/src/app/docs/api-docs/api-docs-data.ts index c337b7520..b799d79a2 100644 --- a/frontend/src/app/docs/api-docs/api-docs-data.ts +++ b/frontend/src/app/docs/api-docs/api-docs-data.ts @@ -9126,11 +9126,7 @@ export const restApiDocsData = [ "blockHash": "00000000000000000000482f0746d62141694b9210a813b97eb8445780a32003", "blockHeight": 829559, "bidBoost": 6102, - "pools": [ - { - "pool_unique_id": 111 - } - ] + "pools": [111] } ]`, }, From 85006a5bec9ec32faf3598323f9a9c676bf3ce42 Mon Sep 17 00:00:00 2001 From: softsimon Date: Thu, 20 Jun 2024 14:36:58 +0900 Subject: [PATCH 23/35] some -> includes --- backend/src/repositories/AccelerationRepository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/repositories/AccelerationRepository.ts b/backend/src/repositories/AccelerationRepository.ts index c08e256b6..0da66228c 100644 --- a/backend/src/repositories/AccelerationRepository.ts +++ b/backend/src/repositories/AccelerationRepository.ts @@ -311,7 +311,7 @@ class AccelerationRepository { pools: acc.pools, })) for (const acc of accelerations) { - if (blockTxs[acc.txid] && acc.pools.some(pool => pool === block.extras.pool.id)) { + if (blockTxs[acc.txid] && acc.pools.includes(block.extras.pool.id)) { const tx = blockTxs[acc.txid]; const accelerationInfo = accelerationCosts.getAccelerationInfo(tx, boostRate, transactions); accelerationInfo.cost = Math.max(0, Math.min(acc.feeDelta, accelerationInfo.cost)); From 1219526e2dba6410a0ca65133af5a8861ef7aeb4 Mon Sep 17 00:00:00 2001 From: softsimon Date: Thu, 20 Jun 2024 17:43:54 +0900 Subject: [PATCH 24/35] Disabling liquid test and fixing liquid overflow --- frontend/cypress/e2e/liquid/liquid.spec.ts | 14 -------------- .../app/components/address/address.component.html | 6 +++--- .../app/components/address/address.component.scss | 7 ------- 3 files changed, 3 insertions(+), 24 deletions(-) diff --git a/frontend/cypress/e2e/liquid/liquid.spec.ts b/frontend/cypress/e2e/liquid/liquid.spec.ts index b355af0d2..c7d2a92ee 100644 --- a/frontend/cypress/e2e/liquid/liquid.spec.ts +++ b/frontend/cypress/e2e/liquid/liquid.spec.ts @@ -72,20 +72,6 @@ describe('Liquid', () => { }); }); - it('renders unconfidential addresses correctly on mobile', () => { - cy.viewport('iphone-6'); - cy.visit(`${basePath}/address/ex1qqmmjdwrlg59c8q4l75sj6wedjx57tj5grt8pat`); - cy.waitForSkeletonGone(); - //TODO: Add proper IDs for these selectors - const firstRowSelector = '.container-xl > :nth-child(3) > div > :nth-child(1) > .table > tbody'; - const thirdRowSelector = '.container-xl > :nth-child(3) > div > :nth-child(3)'; - cy.get(firstRowSelector).invoke('css', 'width').then(firstRowWidth => { - cy.get(thirdRowSelector).invoke('css', 'width').then(thirdRowWidth => { - expect(parseInt(firstRowWidth)).to.be.lessThan(parseInt(thirdRowWidth)); - }); - }); - }); - describe('peg in/peg out', () => { it('loads peg in addresses', () => { cy.visit(`${basePath}/tx/fe764f7bedfc2a37b29d9c8aef67d64a57d253a6b11c5a55555cfd5826483a58`); diff --git a/frontend/src/app/components/address/address.component.html b/frontend/src/app/components/address/address.component.html index dcff7f6b3..f0e9012c2 100644 --- a/frontend/src/app/components/address/address.component.html +++ b/frontend/src/app/components/address/address.component.html @@ -22,7 +22,7 @@
@if (isMobile) {
-
HostLast checkedUpdated RTT RTT Height
+
@@ -39,7 +39,7 @@ } @else {
-
+
@@ -52,7 +52,7 @@
- +
diff --git a/frontend/src/app/components/address/address.component.scss b/frontend/src/app/components/address/address.component.scss index c7d37425e..8e04ffe8b 100644 --- a/frontend/src/app/components/address/address.component.scss +++ b/frontend/src/app/components/address/address.component.scss @@ -98,13 +98,6 @@ h1 { .liquid-address { .address-table { table-layout: fixed; - - tr td:first-child { - width: 170px; - } - tr td:last-child { - width: 80%; - } } } From c5b12e3bc3517c7058c4770e2070eba7398e8186 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 16 Jun 2024 14:36:24 +0000 Subject: [PATCH 25/35] split overview subscriptions in block component --- .../app/components/block/block.component.ts | 310 ++++++++++-------- 1 file changed, 167 insertions(+), 143 deletions(-) diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts index 471c1dfca..01702487f 100644 --- a/frontend/src/app/components/block/block.component.ts +++ b/frontend/src/app/components/block/block.component.ts @@ -8,7 +8,7 @@ import { StateService } from '../../services/state.service'; import { SeoService } from '../../services/seo.service'; import { WebsocketService } from '../../services/websocket.service'; import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe'; -import { BlockAudit, BlockExtended, TransactionStripped } from '../../interfaces/node-api.interface'; +import { Acceleration, BlockAudit, BlockExtended, TransactionStripped } from '../../interfaces/node-api.interface'; import { ApiService } from '../../services/api.service'; import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component'; import { detectWebGL } from '../../shared/graphs.utils'; @@ -44,6 +44,7 @@ export class BlockComponent implements OnInit, OnDestroy { latestBlocks: BlockExtended[] = []; oobFees: number = 0; strippedTransactions: TransactionStripped[]; + accelerations: Acceleration[]; overviewTransitionDirection: string; isLoadingOverview = true; error: any; @@ -68,6 +69,7 @@ export class BlockComponent implements OnInit, OnDestroy { mode: 'projected' | 'actual' = 'projected'; overviewSubscription: Subscription; + accelerationsSubscription: Subscription; keyNavigationSubscription: Subscription; blocksSubscription: Subscription; cacheBlocksSubscription: Subscription; @@ -183,6 +185,9 @@ export class BlockComponent implements OnInit, OnDestroy { } else { this.isLoadingBlock = true; this.isLoadingOverview = true; + this.strippedTransactions = undefined; + this.blockAudit = undefined; + this.accelerations = undefined; let blockInCache: BlockExtended; if (isBlockHeight) { @@ -294,158 +299,36 @@ export class BlockComponent implements OnInit, OnDestroy { this.overviewError = err; return of(null); }) - ), - this.stateService.env.ACCELERATOR === true && block.height > 819500 - ? this.servicesApiService.getAccelerationHistory$({ blockHeight: block.height }) - .pipe(catchError(() => { - return of([]); - })) - : of([]) + ) ]); }) ) - .subscribe(([transactions, blockAudit, accelerations]) => { + .subscribe(([transactions, blockAudit]) => { if (transactions) { this.strippedTransactions = transactions; } else { this.strippedTransactions = []; } + this.blockAudit = blockAudit; - const acceleratedInBlock = {}; - for (const acc of accelerations) { - if (acc.pools?.some(pool => pool === this.block?.extras?.pool.id)) { - acceleratedInBlock[acc.txid] = acc; - } - } - for (const tx of transactions) { - if (acceleratedInBlock[tx.txid]) { - tx.acc = true; - const acceleration = acceleratedInBlock[tx.txid]; - const boostCost = acceleration.boostCost || acceleration.bidBoost; - const acceleratedFeeRate = Math.max(acceleration.effectiveFee, acceleration.effectiveFee + boostCost) / acceleration.effectiveVsize; - if (acceleratedFeeRate > tx.rate) { - tx.rate = acceleratedFeeRate; - } - } else { - tx.acc = false; - } - } - - this.blockAudit = null; - if (transactions && blockAudit) { - const inTemplate = {}; - const inBlock = {}; - const isAdded = {}; - const isPrioritized = {}; - const isCensored = {}; - const isMissing = {}; - const isSelected = {}; - const isFresh = {}; - const isSigop = {}; - const isRbf = {}; - const isAccelerated = {}; - this.numMissing = 0; - this.numUnexpected = 0; - - if (blockAudit?.template) { - for (const tx of blockAudit.template) { - inTemplate[tx.txid] = true; - if (tx.acc) { - isAccelerated[tx.txid] = true; - } - } - for (const tx of transactions) { - inBlock[tx.txid] = true; - } - for (const txid of blockAudit.addedTxs) { - isAdded[txid] = true; - } - for (const txid of blockAudit.prioritizedTxs || []) { - isPrioritized[txid] = true; - } - for (const txid of blockAudit.missingTxs) { - isCensored[txid] = true; - } - for (const txid of blockAudit.freshTxs || []) { - isFresh[txid] = true; - } - for (const txid of blockAudit.sigopTxs || []) { - isSigop[txid] = true; - } - for (const txid of blockAudit.fullrbfTxs || []) { - isRbf[txid] = true; - } - for (const txid of blockAudit.acceleratedTxs || []) { - isAccelerated[txid] = true; - } - // set transaction statuses - for (const tx of blockAudit.template) { - tx.context = 'projected'; - if (isCensored[tx.txid]) { - tx.status = 'censored'; - } else if (inBlock[tx.txid]) { - tx.status = 'found'; - } else { - if (isFresh[tx.txid]) { - if (tx.rate - (tx.fee / tx.vsize) >= 0.1) { - tx.status = 'freshcpfp'; - } else { - tx.status = 'fresh'; - } - } else if (isSigop[tx.txid]) { - tx.status = 'sigop'; - } else if (isRbf[tx.txid]) { - tx.status = 'rbf'; - } else { - tx.status = 'missing'; - } - isMissing[tx.txid] = true; - this.numMissing++; - } - if (isAccelerated[tx.txid]) { - tx.status = 'accelerated'; - } - } - for (const [index, tx] of transactions.entries()) { - tx.context = 'actual'; - if (index === 0) { - tx.status = null; - } else if (isAdded[tx.txid]) { - tx.status = 'added'; - } else if (isPrioritized[tx.txid]) { - tx.status = 'prioritized'; - } else if (inTemplate[tx.txid]) { - tx.status = 'found'; - } else if (isRbf[tx.txid]) { - tx.status = 'rbf'; - } else { - tx.status = 'selected'; - isSelected[tx.txid] = true; - this.numUnexpected++; - } - if (isAccelerated[tx.txid]) { - tx.status = 'accelerated'; - } - } - for (const tx of transactions) { - inBlock[tx.txid] = true; - } - - blockAudit.feeDelta = blockAudit.expectedFees > 0 ? (blockAudit.expectedFees - (this.block?.extras.totalFees + this.oobFees)) / blockAudit.expectedFees : 0; - blockAudit.weightDelta = blockAudit.expectedWeight > 0 ? (blockAudit.expectedWeight - this.block?.weight) / blockAudit.expectedWeight : 0; - blockAudit.txDelta = blockAudit.template.length > 0 ? (blockAudit.template.length - this.block?.tx_count) / blockAudit.template.length : 0; - this.blockAudit = blockAudit; - this.setAuditAvailable(true); - } else { - this.setAuditAvailable(false); - } - } else { - this.setAuditAvailable(false); - } - + this.setupBlockAudit(); this.isLoadingOverview = false; - this.setupBlockGraphs(); - this.cd.markForCheck(); + }); + + this.accelerationsSubscription = this.block$.pipe( + switchMap((block) => { + return this.stateService.env.ACCELERATOR === true && block.height > 819500 + ? this.servicesApiService.getAccelerationHistory$({ blockHeight: block.height }) + .pipe(catchError(() => { + return of([]); + })) + : of([]); + }) + ).subscribe((accelerations) => { + this.accelerations = accelerations; + if (accelerations.length) { + this.setupBlockAudit(); + } }); this.oobSubscription = this.block$.pipe( @@ -609,6 +492,147 @@ export class BlockComponent implements OnInit, OnDestroy { } } + setupBlockAudit(): void { + const transactions = this.strippedTransactions || []; + const blockAudit = this.blockAudit; + const accelerations = this.accelerations || []; + + const acceleratedInBlock = {}; + for (const acc of accelerations) { + if (acc.pools?.some(pool => pool === this.block?.extras?.pool.id)) { + acceleratedInBlock[acc.txid] = acc; + } + } + + for (const tx of transactions) { + if (acceleratedInBlock[tx.txid]) { + tx.acc = true; + const acceleration = acceleratedInBlock[tx.txid]; + const boostCost = acceleration.boostCost || acceleration.bidBoost; + const acceleratedFeeRate = Math.max(acceleration.effectiveFee, acceleration.effectiveFee + boostCost) / acceleration.effectiveVsize; + if (acceleratedFeeRate > tx.rate) { + tx.rate = acceleratedFeeRate; + } + } else { + tx.acc = false; + } + } + + if (transactions && blockAudit) { + const inTemplate = {}; + const inBlock = {}; + const isAdded = {}; + const isPrioritized = {}; + const isCensored = {}; + const isMissing = {}; + const isSelected = {}; + const isFresh = {}; + const isSigop = {}; + const isRbf = {}; + const isAccelerated = {}; + this.numMissing = 0; + this.numUnexpected = 0; + + if (blockAudit?.template) { + for (const tx of blockAudit.template) { + inTemplate[tx.txid] = true; + if (tx.acc) { + isAccelerated[tx.txid] = true; + } + } + for (const tx of transactions) { + inBlock[tx.txid] = true; + } + for (const txid of blockAudit.addedTxs) { + isAdded[txid] = true; + } + for (const txid of blockAudit.prioritizedTxs || []) { + isPrioritized[txid] = true; + } + for (const txid of blockAudit.missingTxs) { + isCensored[txid] = true; + } + for (const txid of blockAudit.freshTxs || []) { + isFresh[txid] = true; + } + for (const txid of blockAudit.sigopTxs || []) { + isSigop[txid] = true; + } + for (const txid of blockAudit.fullrbfTxs || []) { + isRbf[txid] = true; + } + for (const txid of blockAudit.acceleratedTxs || []) { + isAccelerated[txid] = true; + } + // set transaction statuses + for (const tx of blockAudit.template) { + tx.context = 'projected'; + if (isCensored[tx.txid]) { + tx.status = 'censored'; + } else if (inBlock[tx.txid]) { + tx.status = 'found'; + } else { + if (isFresh[tx.txid]) { + if (tx.rate - (tx.fee / tx.vsize) >= 0.1) { + tx.status = 'freshcpfp'; + } else { + tx.status = 'fresh'; + } + } else if (isSigop[tx.txid]) { + tx.status = 'sigop'; + } else if (isRbf[tx.txid]) { + tx.status = 'rbf'; + } else { + tx.status = 'missing'; + } + isMissing[tx.txid] = true; + this.numMissing++; + } + if (isAccelerated[tx.txid]) { + tx.status = 'accelerated'; + } + } + for (const [index, tx] of transactions.entries()) { + tx.context = 'actual'; + if (index === 0) { + tx.status = null; + } else if (isAdded[tx.txid]) { + tx.status = 'added'; + } else if (isPrioritized[tx.txid]) { + tx.status = 'prioritized'; + } else if (inTemplate[tx.txid]) { + tx.status = 'found'; + } else if (isRbf[tx.txid]) { + tx.status = 'rbf'; + } else { + tx.status = 'selected'; + isSelected[tx.txid] = true; + this.numUnexpected++; + } + if (isAccelerated[tx.txid]) { + tx.status = 'accelerated'; + } + } + for (const tx of transactions) { + inBlock[tx.txid] = true; + } + + blockAudit.feeDelta = blockAudit.expectedFees > 0 ? (blockAudit.expectedFees - (this.block?.extras.totalFees + this.oobFees)) / blockAudit.expectedFees : 0; + blockAudit.weightDelta = blockAudit.expectedWeight > 0 ? (blockAudit.expectedWeight - this.block?.weight) / blockAudit.expectedWeight : 0; + blockAudit.txDelta = blockAudit.template.length > 0 ? (blockAudit.template.length - this.block?.tx_count) / blockAudit.template.length : 0; + this.blockAudit = blockAudit; + this.setAuditAvailable(true); + } else { + this.setAuditAvailable(false); + } + } else { + this.setAuditAvailable(false); + } + + this.setupBlockGraphs(); + this.cd.markForCheck(); + } + setupBlockGraphs(): void { if (this.blockAudit || this.strippedTransactions) { this.blockGraphProjected.forEach(graph => { From 55598e7974c905cacdb5b30a8e277ce5baa14d18 Mon Sep 17 00:00:00 2001 From: softsimon Date: Fri, 21 Jun 2024 18:38:37 +0900 Subject: [PATCH 26/35] Remove space between plus and amount --- frontend/src/app/components/block/block.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html index c83459375..6c8693d22 100644 --- a/frontend/src/app/components/block/block.component.html +++ b/frontend/src/app/components/block/block.component.html @@ -411,7 +411,7 @@
- + + {{ blockAudit.feeDelta < 0 ? '+' : '' }}{{ (-blockAudit.feeDelta * 100) | amountShortener: 2 }}% From e7cba13704aaad31171adc6ae8dcd5e462ca8028 Mon Sep 17 00:00:00 2001 From: softsimon Date: Fri, 21 Jun 2024 19:09:35 +0900 Subject: [PATCH 27/35] Add new frontend configs to docker --- docker/frontend/entrypoint.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docker/frontend/entrypoint.sh b/docker/frontend/entrypoint.sh index a57c599b4..5e79b262f 100644 --- a/docker/frontend/entrypoint.sh +++ b/docker/frontend/entrypoint.sh @@ -16,6 +16,7 @@ fi # Runtime overrides - read env vars defined in docker compose +__MAINNET_ENABLED__=${MAINNET_ENABLED:=true} __TESTNET_ENABLED__=${TESTNET_ENABLED:=false} __SIGNET_ENABLED__=${SIGNET_ENABLED:=false} __LIQUID_ENABLED__=${LIQUID_ENABLED:=false} @@ -28,6 +29,7 @@ __NGINX_PORT__=${NGINX_PORT:=8999} __BLOCK_WEIGHT_UNITS__=${BLOCK_WEIGHT_UNITS:=4000000} __MEMPOOL_BLOCKS_AMOUNT__=${MEMPOOL_BLOCKS_AMOUNT:=8} __BASE_MODULE__=${BASE_MODULE:=mempool} +__ROOT_NETWORK__=${ROOT_NETWORK:=} __MEMPOOL_WEBSITE_URL__=${MEMPOOL_WEBSITE_URL:=https://mempool.space} __LIQUID_WEBSITE_URL__=${LIQUID_WEBSITE_URL:=https://liquid.network} __MINING_DASHBOARD__=${MINING_DASHBOARD:=true} @@ -42,6 +44,7 @@ __HISTORICAL_PRICE__=${HISTORICAL_PRICE:=true} __ADDITIONAL_CURRENCIES__=${ADDITIONAL_CURRENCIES:=false} # Export as environment variables to be used by envsubst +export __MAINNET_ENABLED__ export __TESTNET_ENABLED__ export __SIGNET_ENABLED__ export __LIQUID_ENABLED__ @@ -54,6 +57,7 @@ export __NGINX_PORT__ export __BLOCK_WEIGHT_UNITS__ export __MEMPOOL_BLOCKS_AMOUNT__ export __BASE_MODULE__ +export __ROOT_NETWORK__ export __MEMPOOL_WEBSITE_URL__ export __LIQUID_WEBSITE_URL__ export __MINING_DASHBOARD__ From c00d2f3763e7949fa2ab931325cadc94e5351f5b Mon Sep 17 00:00:00 2001 From: softsimon Date: Fri, 21 Jun 2024 19:32:25 +0900 Subject: [PATCH 28/35] Hack networkMatches --- frontend/src/app/services/state.service.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index d339e688a..0d502747c 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -206,8 +206,6 @@ export class StateService { this.env.MINING_DASHBOARD = false; } - this.network = this.env.ROOT_NETWORK; - if (this.isBrowser) { this.setNetworkBasedonUrl(window.location.pathname); this.setLightningBasedonUrl(window.location.pathname); @@ -331,7 +329,12 @@ export class StateService { // (?:preview\/)? optional "preview" prefix (non-capturing) // (testnet|signet)/ network string (captured as networkMatches[1]) // ($|\/) network string must end or end with a slash - const networkMatches = url.match(/^\/(?:[a-z]{2}(?:-[A-Z]{2})?\/)?(?:preview\/)?(testnet4?|signet)($|\/)/); + let networkMatches: object = url.match(/^\/(?:[a-z]{2}(?:-[A-Z]{2})?\/)?(?:preview\/)?(testnet4?|signet)($|\/)/); + + if (!networkMatches && this.env.ROOT_NETWORK) { + networkMatches = { 1: this.env.ROOT_NETWORK }; + } + switch (networkMatches && networkMatches[1]) { case 'signet': if (this.network !== 'signet') { @@ -363,8 +366,8 @@ export class StateService { this.networkChanged$.next(this.env.BASE_MODULE); } } else if (this.network !== '') { - this.network = this.env.ROOT_NETWORK; - this.networkChanged$.next(this.env.ROOT_NETWORK); + this.network = ''; + this.networkChanged$.next(''); } } } From b41382dfeea20912ba14cd337359119f881c3d63 Mon Sep 17 00:00:00 2001 From: softsimon Date: Sat, 22 Jun 2024 08:20:36 +0900 Subject: [PATCH 29/35] Local dev accelerations proxy --- frontend/proxy.conf.local-esplora.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/frontend/proxy.conf.local-esplora.js b/frontend/proxy.conf.local-esplora.js index 0cdc9d459..cfed3d02b 100644 --- a/frontend/proxy.conf.local-esplora.js +++ b/frontend/proxy.conf.local-esplora.js @@ -78,6 +78,17 @@ PROXY_CONFIG.push(...[ "^/testnet": "" }, }, + /* Optional proxy to route dev to official acceleration services + { + context: ['/api/v1/services/accelerator/**'], + target: `https://mempool.space/api/v1/services/accelerator/`, + secure: false, + changeOrigin: true, + proxyTimeout: 30000, + pathRewrite: { + "^/api/v1/services/accelerator": "" + }, + */ { context: ['/api/v1/services/**'], target: `http://localhost:9000`, From 2e893e0aea214e8f714b7af420d787a35ebccb14 Mon Sep 17 00:00:00 2001 From: softsimon Date: Sat, 22 Jun 2024 08:21:56 +0900 Subject: [PATCH 30/35] adding missing }, to proxy conf --- frontend/proxy.conf.local-esplora.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/proxy.conf.local-esplora.js b/frontend/proxy.conf.local-esplora.js index cfed3d02b..905910294 100644 --- a/frontend/proxy.conf.local-esplora.js +++ b/frontend/proxy.conf.local-esplora.js @@ -87,6 +87,7 @@ PROXY_CONFIG.push(...[ proxyTimeout: 30000, pathRewrite: { "^/api/v1/services/accelerator": "" + }, }, */ { From da1ad1c3165a60f357356144a65b29a4cb17467a Mon Sep 17 00:00:00 2001 From: softsimon Date: Sat, 22 Jun 2024 09:31:24 +0900 Subject: [PATCH 31/35] Unit tests: nonstandard --- backend/src/__tests__/api/common.ts | 40 +++++++++---- .../api/test-data/non-standard-txs.json | 52 +++++++++++++++++ .../api/test-data/transactions-random.json | 58 +++++++++++++++++++ 3 files changed, 138 insertions(+), 12 deletions(-) create mode 100644 backend/src/__tests__/api/test-data/non-standard-txs.json diff --git a/backend/src/__tests__/api/common.ts b/backend/src/__tests__/api/common.ts index 10d6df868..74a7db88f 100644 --- a/backend/src/__tests__/api/common.ts +++ b/backend/src/__tests__/api/common.ts @@ -4,21 +4,37 @@ import { MempoolTransactionExtended } from '../../mempool.interfaces'; const randomTransactions = require('./test-data/transactions-random.json'); const replacedTransactions = require('./test-data/transactions-replaced.json'); const rbfTransactions = require('./test-data/transactions-rbfs.json'); +const nonStandardTransactions = require('./test-data/non-standard-txs.json'); -describe('Mempool Utils', () => { - test('should detect RBF transactions with fast method', () => { +describe('Common', () => { + describe('RBF', () => { const newTransactions = rbfTransactions.concat(randomTransactions); - const result: { [txid: string]: MempoolTransactionExtended[] } = Common.findRbfTransactions(newTransactions, replacedTransactions); - expect(Object.values(result).length).toEqual(2); - expect(result).toHaveProperty('7219d95161f3718335991ac6d967d24eedec370908c9879bb1e192e6d797d0a6'); - expect(result).toHaveProperty('5387881d695d4564d397026dc5f740f816f8390b4b2c5ec8c20309122712a875'); + test('should detect RBF transactions with fast method', () => { + const result: { [txid: string]: MempoolTransactionExtended[] } = Common.findRbfTransactions(newTransactions, replacedTransactions); + expect(Object.values(result).length).toEqual(2); + expect(result).toHaveProperty('7219d95161f3718335991ac6d967d24eedec370908c9879bb1e192e6d797d0a6'); + expect(result).toHaveProperty('5387881d695d4564d397026dc5f740f816f8390b4b2c5ec8c20309122712a875'); + }); + + test('should detect RBF transactions with scalable method', () => { + const result: { [txid: string]: MempoolTransactionExtended[] } = Common.findRbfTransactions(newTransactions, replacedTransactions, true); + expect(Object.values(result).length).toEqual(2); + expect(result).toHaveProperty('7219d95161f3718335991ac6d967d24eedec370908c9879bb1e192e6d797d0a6'); + expect(result).toHaveProperty('5387881d695d4564d397026dc5f740f816f8390b4b2c5ec8c20309122712a875'); + }); }); - test.only('should detect RBF transactions with scalable method', () => { - const newTransactions = rbfTransactions.concat(randomTransactions); - const result: { [txid: string]: MempoolTransactionExtended[] } = Common.findRbfTransactions(newTransactions, replacedTransactions, true); - expect(Object.values(result).length).toEqual(2); - expect(result).toHaveProperty('7219d95161f3718335991ac6d967d24eedec370908c9879bb1e192e6d797d0a6'); - expect(result).toHaveProperty('5387881d695d4564d397026dc5f740f816f8390b4b2c5ec8c20309122712a875'); + describe('Mempool Goggles', () => { + test('should detect nonstandard transactions', () => { + nonStandardTransactions.forEach((tx) => { + expect(Common.isNonStandard(tx)).toEqual(true); + }); + }); + + test('should not misclassify as nonstandard transactions', () => { + randomTransactions.forEach((tx) => { + expect(Common.isNonStandard(tx)).toEqual(false); + }); + }); }); }); diff --git a/backend/src/__tests__/api/test-data/non-standard-txs.json b/backend/src/__tests__/api/test-data/non-standard-txs.json new file mode 100644 index 000000000..286fe2945 --- /dev/null +++ b/backend/src/__tests__/api/test-data/non-standard-txs.json @@ -0,0 +1,52 @@ +[ + { + "txid": "50136231cb7eeeffb17fc41d1cca213426abe5bf3760e3d6421cad0c0edad367", + "version": 1, + "locktime": 0, + "vin": [ + { + "txid": "c7f86fb7b830124057475b282809f3474ef3565daa3de0b599980fb9e84ab019", + "vout": 4217, + "prevout": { + "scriptpubkey": "001466197b5eadd8067ec194a457e1044b6d1fbdd3b3", + "scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 66197b5eadd8067ec194a457e1044b6d1fbdd3b3", + "scriptpubkey_type": "v0_p2wpkh", + "scriptpubkey_address": "bc1qvcvhkh4dmqr8asv553t7zpztd50mm5ang4na33", + "value": 106 + }, + "scriptsig": "", + "scriptsig_asm": "", + "witness": [ + "3043021f2af6060a142c6cfd7428adad6a50745d2424813d7ced5c0bbcca85e70de1be022021440ca1c8c3ed49ecd1b64dca6911adcd430c5d3dd60d77ffe0072953999f5b01", + "02ead5c34e3d2c506574b562f857576e11380b6ba15d9f0ad7b7303fdaa9c1513d" + ], + "is_coinbase": false, + "sequence": 4294967295 + } + ], + "vout": [ + { + "scriptpubkey": "6a023a29", + "scriptpubkey_asm": "OP_RETURN OP_PUSHBYTES_2 3a29", + "scriptpubkey_type": "op_return", + "value": 0 + }, + { + "scriptpubkey": "6a036d7648", + "scriptpubkey_asm": "OP_RETURN OP_PUSHBYTES_3 6d7648", + "scriptpubkey_type": "op_return", + "value": 0 + } + ], + "size": 186, + "weight": 420, + "sigops": 1, + "fee": 106, + "status": { + "confirmed": true, + "block_height": 836361, + "block_hash": "0000000000000000000341cc26cda4af82cd25f7063c448772228cbf2836915b", + "block_time": 1711448028 + } + } +] \ No newline at end of file diff --git a/backend/src/__tests__/api/test-data/transactions-random.json b/backend/src/__tests__/api/test-data/transactions-random.json index 6bf4a0edd..ceab6e3c6 100644 --- a/backend/src/__tests__/api/test-data/transactions-random.json +++ b/backend/src/__tests__/api/test-data/transactions-random.json @@ -273,5 +273,63 @@ }, "bestDescendant": null, "cpfpChecked": true + }, + { + "txid": "20b984492b5264162a4c92c9a34bc7fa08b67d669de7b4c5982ad3cb28aaecf6", + "version": 2, + "locktime": 0, + "vin": [ + { + "txid": "3adda6afd547193793c248e667c2b7dbf26d705003de65e3a25e5be698286aef", + "vout": 2, + "prevout": { + "scriptpubkey": "0014989cf12774fc705609610c7b9419f2d1c4807644", + "scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 989cf12774fc705609610c7b9419f2d1c4807644", + "scriptpubkey_type": "v0_p2wpkh", + "scriptpubkey_address": "bc1qnzw0zfm5l3c9vztpp3aegx0j68zgqajyffr2r6", + "value": 27619 + }, + "scriptsig": "", + "scriptsig_asm": "", + "witness": [ + "304402205d7f1e0d928982645c2bcc4c730c4545c382d6520c2a14eebc71594702cd06b302200511d452ce51c79017536f50acb115eefe7c04506ad12b9307d2b5d56b999beb01", + "03716cb4f0430fe69c596a12c6680c55803150645989b406772838d548cde7cca5" + ], + "is_coinbase": false, + "sequence": 4294967295 + } + ], + "vout": [ + { + "scriptpubkey": "6a5d0614c0a2331441", + "scriptpubkey_asm": "OP_RETURN OP_PUSHNUM_13 OP_PUSHBYTES_6 14c0a2331441", + "scriptpubkey_type": "op_return", + "value": 0 + }, + { + "scriptpubkey": "5114d71c6c3ea7ba7e6ee477a0bfd82c20c78997882c", + "scriptpubkey_asm": "OP_PUSHNUM_1 OP_PUSHBYTES_20 d71c6c3ea7ba7e6ee477a0bfd82c20c78997882c", + "scriptpubkey_type": "unknown", + "scriptpubkey_address": "bc1p6uwxc048hflxaerh5zlastpqc7ye0zpvq7gq2a", + "value": 546 + }, + { + "scriptpubkey": "0014989cf12774fc705609610c7b9419f2d1c4807644", + "scriptpubkey_asm": "OP_0 OP_PUSHBYTES_20 989cf12774fc705609610c7b9419f2d1c4807644", + "scriptpubkey_type": "v0_p2wpkh", + "scriptpubkey_address": "bc1qnzw0zfm5l3c9vztpp3aegx0j68zgqajyffr2r6", + "value": 23073 + } + ], + "size": 240, + "weight": 633, + "sigops": 1, + "fee": 4000, + "status": { + "confirmed": true, + "block_height": 848136, + "block_hash": "00000000000000000002c69c7a3010fcd596c0c7451c23e7cd1f5e19ebf8ee6d", + "block_time": 1718517071 + } } ] \ No newline at end of file From c581be0e97befd43d56e6a314d9dc46c5f04dd0a Mon Sep 17 00:00:00 2001 From: softsimon Date: Sat, 22 Jun 2024 10:37:14 +0900 Subject: [PATCH 32/35] Missing da causes invalid json --- backend/src/api/websocket-handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index dc17f1b3f..3c8c06ecf 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -1300,7 +1300,7 @@ class WebsocketHandler { // and zips it together into a valid JSON object private serializeResponse(response): string { return '{' - + Object.keys(response).map(key => `"${key}": ${response[key]}`).join(', ') + + Object.keys(response).filter(key => response[key] != null).map(key => `"${key}": ${response[key]}`).join(', ') + '}'; } From b44b790e28bd0b1cf2f61a49881cde2b544b2922 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sat, 22 Jun 2024 04:10:32 +0000 Subject: [PATCH 33/35] configurable block size & count in rust gbt --- backend/src/__tests__/gbt/gbt-tests.ts | 2 +- backend/src/api/mempool-blocks.ts | 6 +++--- rust/gbt/src/gbt.rs | 18 ++++++++++------- rust/gbt/src/lib.rs | 27 +++++++++++++++++++++++--- 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/backend/src/__tests__/gbt/gbt-tests.ts b/backend/src/__tests__/gbt/gbt-tests.ts index 8a3995f71..561963aaa 100644 --- a/backend/src/__tests__/gbt/gbt-tests.ts +++ b/backend/src/__tests__/gbt/gbt-tests.ts @@ -13,7 +13,7 @@ const vectorBuffer: Buffer = fs.readFileSync(path.join(__dirname, './', './test- describe('Rust GBT', () => { test('should produce the same template as getBlockTemplate from Bitcoin Core', async () => { - const rustGbt = new GbtGenerator(); + const rustGbt = new GbtGenerator(4_000_000, 8); const { mempool, maxUid } = mempoolFromArrayBuffer(vectorBuffer.buffer); const result = await rustGbt.make(mempool, [], maxUid); diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index 55f613c0d..3c1c434bd 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -16,7 +16,7 @@ class MempoolBlocks { private mempoolBlockDeltas: MempoolBlockDelta[] = []; private txSelectionWorker: Worker | null = null; private rustInitialized: boolean = false; - private rustGbtGenerator: GbtGenerator = new GbtGenerator(); + private rustGbtGenerator: GbtGenerator = new GbtGenerator(config.MEMPOOL.BLOCK_WEIGHT_UNITS, config.MEMPOOL.MEMPOOL_BLOCKS_AMOUNT); private nextUid: number = 1; private uidMap: Map = new Map(); // map short numerical uids to full txids @@ -230,7 +230,7 @@ class MempoolBlocks { private resetRustGbt(): void { this.rustInitialized = false; - this.rustGbtGenerator = new GbtGenerator(); + this.rustGbtGenerator = new GbtGenerator(config.MEMPOOL.BLOCK_WEIGHT_UNITS, config.MEMPOOL.MEMPOOL_BLOCKS_AMOUNT); } public async $rustMakeBlockTemplates(txids: string[], newMempool: { [txid: string]: MempoolTransactionExtended }, candidates: GbtCandidates | undefined, saveResults: boolean = false, useAccelerations: boolean = false, accelerationPool?: number): Promise { @@ -262,7 +262,7 @@ class MempoolBlocks { }); // run the block construction algorithm in a separate thread, and wait for a result - const rustGbt = saveResults ? this.rustGbtGenerator : new GbtGenerator(); + const rustGbt = saveResults ? this.rustGbtGenerator : new GbtGenerator(config.MEMPOOL.BLOCK_WEIGHT_UNITS, config.MEMPOOL.MEMPOOL_BLOCKS_AMOUNT); try { const { blocks, blockWeights, rates, clusters, overflow } = this.convertNapiResultTxids( await rustGbt.make(transactions as RustThreadTransaction[], convertedAccelerations as RustThreadAcceleration[], this.nextUid), diff --git a/rust/gbt/src/gbt.rs b/rust/gbt/src/gbt.rs index 38bf826a6..b8d3eb65e 100644 --- a/rust/gbt/src/gbt.rs +++ b/rust/gbt/src/gbt.rs @@ -8,11 +8,9 @@ use crate::{ GbtResult, ThreadTransactionsMap, thread_acceleration::ThreadAcceleration, }; -const MAX_BLOCK_WEIGHT_UNITS: u32 = 4_000_000 - 4_000; const BLOCK_SIGOPS: u32 = 80_000; const BLOCK_RESERVED_WEIGHT: u32 = 4_000; const BLOCK_RESERVED_SIGOPS: u32 = 400; -const MAX_BLOCKS: usize = 8; type AuditPool = Vec>>; type ModifiedQueue = PriorityQueue; @@ -53,7 +51,13 @@ impl Ord for TxPriority { // TODO: Make gbt smaller to fix these lints. #[allow(clippy::too_many_lines)] #[allow(clippy::cognitive_complexity)] -pub fn gbt(mempool: &mut ThreadTransactionsMap, accelerations: &[ThreadAcceleration], max_uid: usize) -> GbtResult { +pub fn gbt( + mempool: &mut ThreadTransactionsMap, + accelerations: &[ThreadAcceleration], + max_uid: usize, + max_block_weight: u32, + max_blocks: usize, +) -> GbtResult { let mut indexed_accelerations = Vec::with_capacity(max_uid + 1); indexed_accelerations.resize(max_uid + 1, None); for acceleration in accelerations { @@ -146,9 +150,9 @@ pub fn gbt(mempool: &mut ThreadTransactionsMap, accelerations: &[ThreadAccelerat modified.pop(); } - if blocks.len() < (MAX_BLOCKS - 1) + if blocks.len() < (max_blocks - 1) && ((block_weight + (4 * next_tx.ancestor_sigop_adjusted_vsize()) - >= MAX_BLOCK_WEIGHT_UNITS) + >= max_block_weight) || (block_sigops + next_tx.ancestor_sigops() > BLOCK_SIGOPS)) { // hold this package in an overflow list while we check for smaller options @@ -201,9 +205,9 @@ pub fn gbt(mempool: &mut ThreadTransactionsMap, accelerations: &[ThreadAccelerat // this block is full let exceeded_package_tries = - failures > 1000 && block_weight > (MAX_BLOCK_WEIGHT_UNITS - BLOCK_RESERVED_WEIGHT); + failures > 1000 && block_weight > (max_block_weight - BLOCK_RESERVED_WEIGHT); let queue_is_empty = mempool_stack.is_empty() && modified.is_empty(); - if (exceeded_package_tries || queue_is_empty) && blocks.len() < (MAX_BLOCKS - 1) { + if (exceeded_package_tries || queue_is_empty) && blocks.len() < (max_blocks - 1) { // finalize this block if transactions.is_empty() { info!("trying to push an empty block! breaking loop! mempool {:#?} | modified {:#?} | overflow {:#?}", mempool_stack.len(), modified.len(), overflow.len()); diff --git a/rust/gbt/src/lib.rs b/rust/gbt/src/lib.rs index edc9714ee..1568990e4 100644 --- a/rust/gbt/src/lib.rs +++ b/rust/gbt/src/lib.rs @@ -35,6 +35,8 @@ type ThreadTransactionsMap = HashMap; #[napi] pub struct GbtGenerator { thread_transactions: Arc>, + max_block_weight: u32, + max_blocks: usize, } #[napi::module_init] @@ -65,10 +67,12 @@ impl GbtGenerator { #[napi(constructor)] #[allow(clippy::new_without_default)] #[must_use] - pub fn new() -> Self { + pub fn new(max_block_weight: u32, max_blocks: u32) -> Self { debug!("Created new GbtGenerator"); Self { thread_transactions: Arc::new(Mutex::new(u32hashmap_with_capacity(STARTING_CAPACITY))), + max_block_weight, + max_blocks: max_blocks as usize, } } @@ -76,12 +80,19 @@ impl GbtGenerator { /// /// Rejects if the thread panics or if the Mutex is poisoned. #[napi] - pub async fn make(&self, mempool: Vec, accelerations: Vec, max_uid: u32) -> Result { + pub async fn make( + &self, + mempool: Vec, + accelerations: Vec, + max_uid: u32, + ) -> Result { trace!("make: Current State {:#?}", self.thread_transactions); run_task( Arc::clone(&self.thread_transactions), accelerations, max_uid as usize, + self.max_block_weight, + self.max_blocks, move |map| { for tx in mempool { map.insert(tx.uid, tx); @@ -107,6 +118,8 @@ impl GbtGenerator { Arc::clone(&self.thread_transactions), accelerations, max_uid as usize, + self.max_block_weight, + self.max_blocks, move |map| { for tx in new_txs { map.insert(tx.uid, tx); @@ -149,6 +162,8 @@ async fn run_task( thread_transactions: Arc>, accelerations: Vec, max_uid: usize, + max_block_weight: u32, + max_blocks: usize, callback: F, ) -> Result where @@ -166,7 +181,13 @@ where callback(&mut map); info!("Starting gbt algorithm for {} elements...", map.len()); - let result = gbt::gbt(&mut map, &accelerations, max_uid); + let result = gbt::gbt( + &mut map, + &accelerations, + max_uid, + max_block_weight, + max_blocks as usize, + ); info!("Finished gbt algorithm for {} elements...", map.len()); debug!( From fa9a8bdba808de9e8390a148433d8ceabfc66742 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sat, 22 Jun 2024 04:30:36 +0000 Subject: [PATCH 34/35] rust gbt restore 4kWU reserve --- rust/gbt/src/gbt.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/gbt/src/gbt.rs b/rust/gbt/src/gbt.rs index b8d3eb65e..6c9e81f5f 100644 --- a/rust/gbt/src/gbt.rs +++ b/rust/gbt/src/gbt.rs @@ -152,7 +152,7 @@ pub fn gbt( if blocks.len() < (max_blocks - 1) && ((block_weight + (4 * next_tx.ancestor_sigop_adjusted_vsize()) - >= max_block_weight) + >= max_block_weight - 4_000) || (block_sigops + next_tx.ancestor_sigops() > BLOCK_SIGOPS)) { // hold this package in an overflow list while we check for smaller options @@ -205,7 +205,7 @@ pub fn gbt( // this block is full let exceeded_package_tries = - failures > 1000 && block_weight > (max_block_weight - BLOCK_RESERVED_WEIGHT); + failures > 1000 && block_weight > (max_block_weight - 4_000 - BLOCK_RESERVED_WEIGHT); let queue_is_empty = mempool_stack.is_empty() && modified.is_empty(); if (exceeded_package_tries || queue_is_empty) && blocks.len() < (max_blocks - 1) { // finalize this block From 36bc1db1953cf0bef77076206d530d9acf732b6f Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sat, 22 Jun 2024 04:38:06 +0000 Subject: [PATCH 35/35] Fix hardcoded median weight units in calcEffectiveFeeStatistics --- backend/src/api/common.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index 9a9f7886b..04f380418 100644 --- a/backend/src/api/common.ts +++ b/backend/src/api/common.ts @@ -904,9 +904,10 @@ export class Common { let medianFee = 0; let medianWeight = 0; - // calculate the "medianFee" as the average fee rate of the middle 10000 weight units of transactions - const leftBound = 1995000; - const rightBound = 2005000; + // calculate the "medianFee" as the average fee rate of the middle 0.25% weight units of transactions + const halfWidth = config.MEMPOOL.BLOCK_WEIGHT_UNITS / 800; + const leftBound = Math.floor((config.MEMPOOL.BLOCK_WEIGHT_UNITS / 2) - halfWidth); + const rightBound = Math.ceil((config.MEMPOOL.BLOCK_WEIGHT_UNITS / 2) + halfWidth); for (let i = 0; i < sortedTxs.length && weightCount < rightBound; i++) { const left = weightCount; const right = weightCount + sortedTxs[i].weight;