From 855b20834efe4495eca4232a36bda16ff9ff201c Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 31 Mar 2024 03:45:48 +0000 Subject: [PATCH 1/5] Display first seen time on block visualiation tooltips --- backend/src/api/common.ts | 1 + backend/src/api/mempool-blocks.ts | 4 ++- backend/src/mempool.interfaces.ts | 3 ++- .../block-overview-graph.component.html | 1 + .../block-overview-graph.component.ts | 1 + .../block-overview-graph/tx-view.ts | 2 ++ .../block-overview-tooltip.component.html | 8 ++++++ .../block-overview-tooltip.component.ts | 4 +++ .../block-view/block-view.component.html | 1 + .../app/components/block/block.component.html | 5 ++-- .../eight-blocks/eight-blocks.component.html | 1 + .../src/app/components/time/time.component.ts | 25 ++++++++++++++++++- .../src/app/interfaces/node-api.interface.ts | 1 + .../src/app/interfaces/websocket.interface.ts | 3 ++- frontend/src/app/shared/common.utils.ts | 3 ++- 15 files changed, 56 insertions(+), 7 deletions(-) diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index d8cf0d73f..5053d4da3 100644 --- a/backend/src/api/common.ts +++ b/backend/src/api/common.ts @@ -552,6 +552,7 @@ export class Common { value: tx.vout.reduce((acc, vout) => acc + (vout.value ? vout.value : 0), 0), acc: tx.acceleration || undefined, rate: tx.effectiveFeePerVsize, + time: tx.firstSeen || undefined, }; } diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index 687fdbef4..3af2a9967 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -598,7 +598,8 @@ class MempoolBlocks { tx.value, Math.round((tx.rate || (tx.fee / tx.vsize)) * 100) / 100, tx.flags, - 1 + tx.time || 0, + 1, ]; } else { return [ @@ -608,6 +609,7 @@ class MempoolBlocks { tx.value, Math.round((tx.rate || (tx.fee / tx.vsize)) * 100) / 100, tx.flags, + tx.time || 0, ]; } } diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index 5b31f13a6..a41e3a1ba 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -200,6 +200,7 @@ export interface TransactionStripped { value: number; acc?: boolean; rate?: number; // effective fee rate + time?: number; } export interface TransactionClassified extends TransactionStripped { @@ -207,7 +208,7 @@ export interface TransactionClassified extends TransactionStripped { } // [txid, fee, vsize, value, rate, flags, acceleration?] -export type TransactionCompressed = [string, number, number, number, number, number, 1?]; +export type TransactionCompressed = [string, number, number, number, number, number, number, 1?]; // [txid, rate, flags, acceleration?] export type MempoolDeltaChange = [string, number, number, (1|0)]; diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.html b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.html index 702718742..2ef07d12c 100644 --- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.html +++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.html @@ -14,6 +14,7 @@ [blockConversion]="blockConversion" [filterFlags]="activeFilterFlags" [filterMode]="filterMode" + [relativeTime]="relativeTime" >
diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts index f7e9d297f..f2a1f1c7c 100644 --- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts +++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts @@ -46,6 +46,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On @Input() excludeFilters: string[] = []; @Input() filterFlags: bigint | null = null; @Input() filterMode: FilterMode = 'and'; + @Input() relativeTime: number | null; @Input() blockConversion: Price; @Input() overrideColors: ((tx: TxView) => Color) | null = null; @Output() txClickEvent = new EventEmitter<{ tx: TransactionStripped, keyModifier: boolean}>(); diff --git a/frontend/src/app/components/block-overview-graph/tx-view.ts b/frontend/src/app/components/block-overview-graph/tx-view.ts index f9f6eeeb7..fec0e7090 100644 --- a/frontend/src/app/components/block-overview-graph/tx-view.ts +++ b/frontend/src/app/components/block-overview-graph/tx-view.ts @@ -32,6 +32,7 @@ export default class TxView implements TransactionStripped { rate?: number; flags: number; bigintFlags?: bigint | null = 0b00000100_00000000_00000000_00000000n; + time?: number; status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'rbf' | 'accelerated'; context?: 'projected' | 'actual'; scene?: BlockScene; @@ -53,6 +54,7 @@ export default class TxView implements TransactionStripped { this.scene = scene; this.context = tx.context; this.txid = tx.txid; + this.time = tx.time || 0; this.fee = tx.fee; this.vsize = tx.vsize; this.value = tx.value; diff --git a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html index 1ef0d1686..ec60803a5 100644 --- a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html +++ b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html @@ -14,6 +14,14 @@ {{ txid | shortenString : 16}} + + First seen + + + + First seen + + Amount diff --git a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts index f163e74fc..c6f656796 100644 --- a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts +++ b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts @@ -3,6 +3,7 @@ import { Position } from '../../components/block-overview-graph/sprite-types.js' import { Price } from '../../services/price.service'; import { TransactionStripped } from '../../interfaces/node-api.interface.js'; import { Filter, FilterMode, TransactionFlags, toFilters } from '../../shared/filters.utils'; +import { Block } from '../../interfaces/electrs.interface.js'; @Component({ selector: 'app-block-overview-tooltip', @@ -11,6 +12,7 @@ import { Filter, FilterMode, TransactionFlags, toFilters } from '../../shared/fi }) export class BlockOverviewTooltipComponent implements OnChanges { @Input() tx: TransactionStripped | void; + @Input() relativeTime?: number; @Input() cursorPosition: Position; @Input() clickable: boolean; @Input() auditEnabled: boolean = false; @@ -19,6 +21,7 @@ export class BlockOverviewTooltipComponent implements OnChanges { @Input() filterMode: FilterMode = 'and'; txid = ''; + time: number = 0; fee = 0; value = 0; vsize = 1; @@ -56,6 +59,7 @@ export class BlockOverviewTooltipComponent implements OnChanges { if (this.tx && (changes.tx || changes.filterFlags || changes.filterMode)) { this.txid = this.tx.txid || ''; + this.time = this.tx.time || 0; this.fee = this.tx.fee || 0; this.value = this.tx.value || 0; this.vsize = this.tx.vsize || 1; diff --git a/frontend/src/app/components/block-view/block-view.component.html b/frontend/src/app/components/block-view/block-view.component.html index 9a2ddf373..f0dc94e2c 100644 --- a/frontend/src/app/components/block-view/block-view.component.html +++ b/frontend/src/app/components/block-view/block-view.component.html @@ -8,6 +8,7 @@ [orientation]="'top'" [flip]="false" [disableSpinner]="true" + [relativeTime]="block?.timestamp" (txClickEvent)="onTxClick($event)" >
diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html index 0c0655c01..279830ba5 100644 --- a/frontend/src/app/components/block/block.component.html +++ b/frontend/src/app/components/block/block.component.html @@ -117,6 +117,7 @@ [blockConversion]="blockConversion" [showFilters]="true" [excludeFilters]="['replacement']" + [relativeTime]="block?.timestamp" (txClickEvent)="onTxClick($event)" > @@ -232,7 +233,7 @@ + [showFilters]="true" [excludeFilters]="['replacement']" [relativeTime]="block?.timestamp"> @@ -247,7 +248,7 @@ + [showFilters]="true" [excludeFilters]="['replacement']" [relativeTime]="block?.timestamp"> diff --git a/frontend/src/app/components/eight-blocks/eight-blocks.component.html b/frontend/src/app/components/eight-blocks/eight-blocks.component.html index 59390c953..414a693d3 100644 --- a/frontend/src/app/components/eight-blocks/eight-blocks.component.html +++ b/frontend/src/app/components/eight-blocks/eight-blocks.component.html @@ -12,6 +12,7 @@ [animationDuration]="animationDuration" [animationOffset]="animationOffset" [disableSpinner]="true" + [relativeTime]="blockInfo[i]?.timestamp" (txClickEvent)="onTxClick($event)" >
diff --git a/frontend/src/app/components/time/time.component.ts b/frontend/src/app/components/time/time.component.ts index 679604ff5..61cc799dc 100644 --- a/frontend/src/app/components/time/time.component.ts +++ b/frontend/src/app/components/time/time.component.ts @@ -23,7 +23,7 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy { @Input() time: number; @Input() dateString: number; - @Input() kind: 'plain' | 'since' | 'until' | 'span' = 'plain'; + @Input() kind: 'plain' | 'since' | 'until' | 'span' | 'before' = 'plain'; @Input() fastRender = false; @Input() fixedRender = false; @Input() relative = false; @@ -206,6 +206,29 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy { } } break; + case 'before': + if (number === 1) { + switch (unit) { // singular (1 day) + case 'year': return $localize`:@@time-span:${dateStrings.i18nYear}:DATE: before`; break; + case 'month': return $localize`:@@time-span:${dateStrings.i18nMonth}:DATE: before`; break; + case 'week': return $localize`:@@time-span:${dateStrings.i18nWeek}:DATE: before`; break; + case 'day': return $localize`:@@time-span:${dateStrings.i18nDay}:DATE: before`; break; + case 'hour': return $localize`:@@time-span:${dateStrings.i18nHour}:DATE: before`; break; + case 'minute': return $localize`:@@time-span:${dateStrings.i18nMinute}:DATE: before`; break; + case 'second': return $localize`:@@time-span:${dateStrings.i18nSecond}:DATE: before`; break; + } + } else { + switch (unit) { // plural (2 days) + case 'year': return $localize`:@@time-span:${dateStrings.i18nYears}:DATE: before`; break; + case 'month': return $localize`:@@time-span:${dateStrings.i18nMonths}:DATE: before`; break; + case 'week': return $localize`:@@time-span:${dateStrings.i18nWeeks}:DATE: before`; break; + case 'day': return $localize`:@@time-span:${dateStrings.i18nDays}:DATE: before`; break; + case 'hour': return $localize`:@@time-span:${dateStrings.i18nHours}:DATE: before`; break; + case 'minute': return $localize`:@@time-span:${dateStrings.i18nMinutes}:DATE: before`; break; + case 'second': return $localize`:@@time-span:${dateStrings.i18nSeconds}:DATE: before`; break; + } + } + break; default: if (number === 1) { switch (unit) { // singular (1 day) diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index f8057fda5..9680d2069 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -230,6 +230,7 @@ export interface TransactionStripped { rate?: number; // effective fee rate acc?: boolean; flags?: number | null; + time?: number; status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'rbf' | 'accelerated'; context?: 'projected' | 'actual'; } diff --git a/frontend/src/app/interfaces/websocket.interface.ts b/frontend/src/app/interfaces/websocket.interface.ts index 9553fef02..a36126051 100644 --- a/frontend/src/app/interfaces/websocket.interface.ts +++ b/frontend/src/app/interfaces/websocket.interface.ts @@ -100,12 +100,13 @@ export interface TransactionStripped { acc?: boolean; // is accelerated? rate?: number; // effective fee rate flags?: number; + time?: number; status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'rbf' | 'accelerated'; context?: 'projected' | 'actual'; } // [txid, fee, vsize, value, rate, flags, acceleration?] -export type TransactionCompressed = [string, number, number, number, number, number, 1?]; +export type TransactionCompressed = [string, number, number, number, number, number, number, 1?]; // [txid, rate, flags, acceleration?] export type MempoolDeltaChange = [string, number, number, (1|0)]; diff --git a/frontend/src/app/shared/common.utils.ts b/frontend/src/app/shared/common.utils.ts index 18a330fab..482d9567f 100644 --- a/frontend/src/app/shared/common.utils.ts +++ b/frontend/src/app/shared/common.utils.ts @@ -164,7 +164,8 @@ export function uncompressTx(tx: TransactionCompressed): TransactionStripped { value: tx[3], rate: tx[4], flags: tx[5], - acc: !!tx[6], + time: tx[6], + acc: !!tx[7], }; } From 9b456954b1a0d0d9703ee337c4b9919d802e47f4 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 1 Apr 2024 06:19:26 +0000 Subject: [PATCH 2/5] Change "first seen ... before" to "confirmed ... after" --- .../block-overview-tooltip.component.html | 4 ++-- .../src/app/components/time/time.component.ts | 24 +------------------ 2 files changed, 3 insertions(+), 25 deletions(-) diff --git a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html index ec60803a5..f4c0fdf3f 100644 --- a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html +++ b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html @@ -19,8 +19,8 @@ - First seen - + Confirmed + Amount diff --git a/frontend/src/app/components/time/time.component.ts b/frontend/src/app/components/time/time.component.ts index 61cc799dc..5b4a93ca6 100644 --- a/frontend/src/app/components/time/time.component.ts +++ b/frontend/src/app/components/time/time.component.ts @@ -23,7 +23,7 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy { @Input() time: number; @Input() dateString: number; - @Input() kind: 'plain' | 'since' | 'until' | 'span' | 'before' = 'plain'; + @Input() kind: 'plain' | 'since' | 'until' | 'span' = 'plain'; @Input() fastRender = false; @Input() fixedRender = false; @Input() relative = false; @@ -206,28 +206,6 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy { } } break; - case 'before': - if (number === 1) { - switch (unit) { // singular (1 day) - case 'year': return $localize`:@@time-span:${dateStrings.i18nYear}:DATE: before`; break; - case 'month': return $localize`:@@time-span:${dateStrings.i18nMonth}:DATE: before`; break; - case 'week': return $localize`:@@time-span:${dateStrings.i18nWeek}:DATE: before`; break; - case 'day': return $localize`:@@time-span:${dateStrings.i18nDay}:DATE: before`; break; - case 'hour': return $localize`:@@time-span:${dateStrings.i18nHour}:DATE: before`; break; - case 'minute': return $localize`:@@time-span:${dateStrings.i18nMinute}:DATE: before`; break; - case 'second': return $localize`:@@time-span:${dateStrings.i18nSecond}:DATE: before`; break; - } - } else { - switch (unit) { // plural (2 days) - case 'year': return $localize`:@@time-span:${dateStrings.i18nYears}:DATE: before`; break; - case 'month': return $localize`:@@time-span:${dateStrings.i18nMonths}:DATE: before`; break; - case 'week': return $localize`:@@time-span:${dateStrings.i18nWeeks}:DATE: before`; break; - case 'day': return $localize`:@@time-span:${dateStrings.i18nDays}:DATE: before`; break; - case 'hour': return $localize`:@@time-span:${dateStrings.i18nHours}:DATE: before`; break; - case 'minute': return $localize`:@@time-span:${dateStrings.i18nMinutes}:DATE: before`; break; - case 'second': return $localize`:@@time-span:${dateStrings.i18nSeconds}:DATE: before`; break; - } - } break; default: if (number === 1) { From 93956d0ed4a48a56a0ae355c1d42a1b6dc566234 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 1 Apr 2024 07:00:46 +0000 Subject: [PATCH 3/5] Refactor first seen tooltip labels --- .../block-overview-tooltip.component.html | 26 ++++++++++++----- .../block-overview-tooltip.component.ts | 17 +++++++++++ .../src/app/components/time/time.component.ts | 28 +++++++++++++++++-- 3 files changed, 62 insertions(+), 9 deletions(-) diff --git a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html index f4c0fdf3f..974c4c1db 100644 --- a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html +++ b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html @@ -14,13 +14,25 @@ {{ txid | shortenString : 16}} - - First seen - - - - Confirmed - + + + + First seen + + + + First seen + + + + First seen + + + + Confirmed + + + Amount diff --git a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts index c6f656796..cdb4c7367 100644 --- a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts +++ b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts @@ -29,6 +29,7 @@ export class BlockOverviewTooltipComponent implements OnChanges { effectiveRate; acceleration; hasEffectiveRate: boolean = false; + timeMode: 'mempool' | 'mined' | 'missed' | 'after' = 'mempool'; filters: Filter[] = []; activeFilters: { [key: string]: boolean } = {}; @@ -76,6 +77,22 @@ export class BlockOverviewTooltipComponent implements OnChanges { this.activeFilters[filter.key] = true; } } + + if (!this.relativeTime) { + this.timeMode = 'mempool'; + } else { + if (this.tx?.context === 'actual' || this.tx?.status === 'found') { + this.timeMode = 'mined'; + } else { + const time = this.relativeTime || Date.now(); + if (this.time <= time) { + this.timeMode = 'missed'; + } else { + this.timeMode = 'after'; + } + } + } + this.cd.markForCheck(); } } diff --git a/frontend/src/app/components/time/time.component.ts b/frontend/src/app/components/time/time.component.ts index 5b4a93ca6..954e3d157 100644 --- a/frontend/src/app/components/time/time.component.ts +++ b/frontend/src/app/components/time/time.component.ts @@ -23,7 +23,7 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy { @Input() time: number; @Input() dateString: number; - @Input() kind: 'plain' | 'since' | 'until' | 'span' = 'plain'; + @Input() kind: 'plain' | 'since' | 'until' | 'span' | 'before' = 'plain'; @Input() fastRender = false; @Input() fixedRender = false; @Input() relative = false; @@ -86,7 +86,9 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy { seconds = Math.floor(this.time); } - if (seconds < 60) { + if (seconds < 1 && this.kind === 'span') { + return $localize`:@@date-base.immediately:Immediately`; + } else if (seconds < 60) { if (this.relative || this.kind === 'since') { return $localize`:@@date-base.just-now:Just now`; } else if (this.kind === 'until') { @@ -206,6 +208,28 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy { } } break; + case 'before': + if (number === 1) { + switch (unit) { // singular (1 day) + case 'year': return $localize`:@@time-span:${dateStrings.i18nYear}:DATE: before`; break; + case 'month': return $localize`:@@time-span:${dateStrings.i18nMonth}:DATE: before`; break; + case 'week': return $localize`:@@time-span:${dateStrings.i18nWeek}:DATE: before`; break; + case 'day': return $localize`:@@time-span:${dateStrings.i18nDay}:DATE: before`; break; + case 'hour': return $localize`:@@time-span:${dateStrings.i18nHour}:DATE: before`; break; + case 'minute': return $localize`:@@time-span:${dateStrings.i18nMinute}:DATE: before`; break; + case 'second': return $localize`:@@time-span:${dateStrings.i18nSecond}:DATE: before`; break; + } + } else { + switch (unit) { // plural (2 days) + case 'year': return $localize`:@@time-span:${dateStrings.i18nYears}:DATE: before`; break; + case 'month': return $localize`:@@time-span:${dateStrings.i18nMonths}:DATE: before`; break; + case 'week': return $localize`:@@time-span:${dateStrings.i18nWeeks}:DATE: before`; break; + case 'day': return $localize`:@@time-span:${dateStrings.i18nDays}:DATE: before`; break; + case 'hour': return $localize`:@@time-span:${dateStrings.i18nHours}:DATE: before`; break; + case 'minute': return $localize`:@@time-span:${dateStrings.i18nMinutes}:DATE: before`; break; + case 'second': return $localize`:@@time-span:${dateStrings.i18nSeconds}:DATE: before`; break; + } + } break; default: if (number === 1) { From bd4e223aedb27818c0eeda20d18b143b66ea33df Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 1 Apr 2024 08:07:09 +0000 Subject: [PATCH 4/5] Add "prioritized" category to audits --- backend/src/api/audit.ts | 23 ++++++++++++++----- backend/src/api/database-migration.ts | 7 +++++- backend/src/api/websocket-handler.ts | 3 ++- backend/src/mempool.interfaces.ts | 1 + backend/src/replication/AuditReplication.ts | 1 + .../repositories/BlocksAuditsRepository.ts | 8 ++++--- .../block-overview-graph.component.ts | 4 ++-- .../block-overview-graph/block-scene.ts | 2 +- .../block-overview-graph/tx-view.ts | 2 +- .../components/block-overview-graph/utils.ts | 4 +++- .../block-overview-tooltip.component.html | 1 + .../app/components/block/block.component.ts | 6 +++++ .../fee-distribution-graph.component.ts | 2 +- .../mempool-block-overview.component.ts | 3 ++- .../mempool-block/mempool-block.component.ts | 3 ++- .../transaction/transaction.component.html | 5 ++-- .../transaction/transaction.component.ts | 5 +++- .../src/app/dashboard/dashboard.component.ts | 4 ++-- .../src/app/interfaces/node-api.interface.ts | 3 ++- .../src/app/interfaces/websocket.interface.ts | 15 +----------- frontend/src/app/services/state.service.ts | 4 ++-- frontend/src/app/shared/common.utils.ts | 3 ++- 22 files changed, 67 insertions(+), 42 deletions(-) diff --git a/backend/src/api/audit.ts b/backend/src/api/audit.ts index 0ddfc2cc2..8fa8cbdb1 100644 --- a/backend/src/api/audit.ts +++ b/backend/src/api/audit.ts @@ -7,13 +7,14 @@ const PROPAGATION_MARGIN = 180; // in seconds, time since a transaction is first class Audit { auditBlock(transactions: MempoolTransactionExtended[], projectedBlocks: MempoolBlockWithTransactions[], mempool: { [txId: string]: MempoolTransactionExtended }, useAccelerations: boolean = false) - : { censored: string[], added: string[], fresh: string[], sigop: string[], fullrbf: string[], accelerated: string[], score: number, similarity: number } { + : { censored: string[], added: string[], prioritized: string[], fresh: string[], sigop: string[], fullrbf: string[], accelerated: string[], score: number, similarity: number } { if (!projectedBlocks?.[0]?.transactionIds || !mempool) { - return { censored: [], added: [], fresh: [], sigop: [], fullrbf: [], accelerated: [], score: 1, similarity: 1 }; + return { censored: [], added: [], prioritized: [], fresh: [], sigop: [], fullrbf: [], accelerated: [], score: 1, similarity: 1 }; } const matches: string[] = []; // present in both mined block and template const added: string[] = []; // present in mined block, not in template + const prioritized: string[] = [] // present in the mined block, not in the template, but further down in the mempool const fresh: string[] = []; // missing, but firstSeen or lastBoosted within PROPAGATION_MARGIN const rbf: string[] = []; // either missing or present, and either part of a full-rbf replacement, or a conflict with the mined block const accelerated: string[] = []; // prioritized by the mempool accelerator @@ -68,12 +69,17 @@ class Audit { // we can expect an honest miner to include 'displaced' transactions in place of recent arrivals and censored txs // these displaced transactions should occupy the first N weight units of the next projected block - let displacedWeightRemaining = displacedWeight; + let displacedWeightRemaining = displacedWeight + 4000; let index = 0; let lastFeeRate = Infinity; let failures = 0; - while (projectedBlocks[1] && index < projectedBlocks[1].transactionIds.length && failures < 500) { - const txid = projectedBlocks[1].transactionIds[index]; + let blockIndex = 1; + while (projectedBlocks[blockIndex] && failures < 500) { + if (index >= projectedBlocks[blockIndex].transactionIds.length) { + index = 0; + blockIndex++; + } + const txid = projectedBlocks[blockIndex].transactionIds[index]; const tx = mempool[txid]; if (tx) { const fits = (tx.weight - displacedWeightRemaining) < 4000; @@ -106,7 +112,11 @@ class Audit { if (rbfCache.has(tx.txid)) { rbf.push(tx.txid); } else if (!isDisplaced[tx.txid]) { - added.push(tx.txid); + if (mempool[tx.txid]) { + prioritized.push(tx.txid); + } else { + added.push(tx.txid); + } } overflowWeight += tx.weight; } @@ -155,6 +165,7 @@ class Audit { return { censored: Object.keys(isCensored), added, + prioritized, fresh, sigop: [], fullrbf: rbf, diff --git a/backend/src/api/database-migration.ts b/backend/src/api/database-migration.ts index cf9acbc53..ae68582af 100644 --- a/backend/src/api/database-migration.ts +++ b/backend/src/api/database-migration.ts @@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository'; import { RowDataPacket } from 'mysql2'; class DatabaseMigration { - private static currentVersion = 75; + private static currentVersion = 76; private queryTimeout = 3600_000; private statisticsAddedIndexed = false; private uniqueLogs: string[] = []; @@ -654,6 +654,11 @@ class DatabaseMigration { await this.$executeQuery('ALTER TABLE `prices` ADD `ZAR` float DEFAULT "-1"'); await this.updateToSchemaVersion(75); } + + if (databaseSchemaVersion < 76 && isBitcoin === true) { + await this.$executeQuery('ALTER TABLE `blocks_audits` ADD prioritized_txs JSON DEFAULT "[]"'); + await this.updateToSchemaVersion(76); + } } /** diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index a20088128..1ed9ac9cd 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -868,7 +868,7 @@ class WebsocketHandler { } if (Common.indexingEnabled()) { - const { censored, added, fresh, sigop, fullrbf, accelerated, score, similarity } = Audit.auditBlock(transactions, projectedBlocks, auditMempool); + const { censored, added, prioritized, fresh, sigop, fullrbf, accelerated, score, similarity } = Audit.auditBlock(transactions, projectedBlocks, auditMempool); const matchRate = Math.round(score * 100 * 100) / 100; const stripped = projectedBlocks[0]?.transactions ? projectedBlocks[0].transactions : []; @@ -894,6 +894,7 @@ class WebsocketHandler { height: block.height, hash: block.id, addedTxs: added, + prioritizedTxs: prioritized, missingTxs: censored, freshTxs: fresh, sigopTxs: sigop, diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index a41e3a1ba..092a91e0e 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -37,6 +37,7 @@ export interface BlockAudit { sigopTxs: string[], fullrbfTxs: string[], addedTxs: string[], + prioritizedTxs: string[], acceleratedTxs: string[], matchRate: number, expectedFees?: number, diff --git a/backend/src/replication/AuditReplication.ts b/backend/src/replication/AuditReplication.ts index 503c61613..4ea629839 100644 --- a/backend/src/replication/AuditReplication.ts +++ b/backend/src/replication/AuditReplication.ts @@ -114,6 +114,7 @@ class AuditReplication { time: auditSummary.timestamp || auditSummary.time, missingTxs: auditSummary.missingTxs || [], addedTxs: auditSummary.addedTxs || [], + prioritizedTxs: auditSummary.prioritizedTxs || [], freshTxs: auditSummary.freshTxs || [], sigopTxs: auditSummary.sigopTxs || [], fullrbfTxs: auditSummary.fullrbfTxs || [], diff --git a/backend/src/repositories/BlocksAuditsRepository.ts b/backend/src/repositories/BlocksAuditsRepository.ts index 62f28c56f..daf1ba52d 100644 --- a/backend/src/repositories/BlocksAuditsRepository.ts +++ b/backend/src/repositories/BlocksAuditsRepository.ts @@ -6,9 +6,9 @@ import { BlockAudit, AuditScore } from '../mempool.interfaces'; class BlocksAuditRepositories { public async $saveAudit(audit: BlockAudit): Promise { try { - await DB.query(`INSERT INTO blocks_audits(time, height, hash, missing_txs, added_txs, fresh_txs, sigop_txs, fullrbf_txs, accelerated_txs, match_rate, expected_fees, expected_weight) - VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [audit.time, audit.height, audit.hash, JSON.stringify(audit.missingTxs), - JSON.stringify(audit.addedTxs), JSON.stringify(audit.freshTxs), JSON.stringify(audit.sigopTxs), JSON.stringify(audit.fullrbfTxs), JSON.stringify(audit.acceleratedTxs), audit.matchRate, audit.expectedFees, audit.expectedWeight]); + await DB.query(`INSERT INTO blocks_audits(time, height, hash, missing_txs, added_txs, prioritized_txs, fresh_txs, sigop_txs, fullrbf_txs, accelerated_txs, match_rate, expected_fees, expected_weight) + VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [audit.time, audit.height, audit.hash, JSON.stringify(audit.missingTxs), + JSON.stringify(audit.addedTxs), JSON.stringify(audit.prioritizedTxs), JSON.stringify(audit.freshTxs), JSON.stringify(audit.sigopTxs), JSON.stringify(audit.fullrbfTxs), JSON.stringify(audit.acceleratedTxs), audit.matchRate, audit.expectedFees, audit.expectedWeight]); } catch (e: any) { if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart logger.debug(`Cannot save block audit for block ${audit.hash} because it has already been indexed, ignoring`); @@ -66,6 +66,7 @@ class BlocksAuditRepositories { template, missing_txs as missingTxs, added_txs as addedTxs, + prioritized_txs as prioritizedTxs, fresh_txs as freshTxs, sigop_txs as sigopTxs, fullrbf_txs as fullrbfTxs, @@ -81,6 +82,7 @@ class BlocksAuditRepositories { if (rows.length) { rows[0].missingTxs = JSON.parse(rows[0].missingTxs); rows[0].addedTxs = JSON.parse(rows[0].addedTxs); + rows[0].prioritizedTxs = JSON.parse(rows[0].prioritizedTxs); rows[0].freshTxs = JSON.parse(rows[0].freshTxs); rows[0].sigopTxs = JSON.parse(rows[0].sigopTxs); rows[0].fullrbfTxs = JSON.parse(rows[0].fullrbfTxs); diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts index f2a1f1c7c..003531fce 100644 --- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts +++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts @@ -1,5 +1,5 @@ import { Component, ElementRef, ViewChild, HostListener, Input, Output, EventEmitter, NgZone, AfterViewInit, OnDestroy, OnChanges } from '@angular/core'; -import { TransactionStripped } from '../../interfaces/websocket.interface'; +import { TransactionStripped } from '../../interfaces/node-api.interface'; import { FastVertexArray } from './fast-vertex-array'; import BlockScene from './block-scene'; import TxSprite from './tx-sprite'; @@ -20,7 +20,7 @@ const unmatchedAuditColors = { censored: setOpacity(defaultAuditColors.censored, unmatchedOpacity), missing: setOpacity(defaultAuditColors.missing, unmatchedOpacity), added: setOpacity(defaultAuditColors.added, unmatchedOpacity), - selected: setOpacity(defaultAuditColors.selected, unmatchedOpacity), + prioritized: setOpacity(defaultAuditColors.prioritized, unmatchedOpacity), accelerated: setOpacity(defaultAuditColors.accelerated, unmatchedOpacity), }; diff --git a/frontend/src/app/components/block-overview-graph/block-scene.ts b/frontend/src/app/components/block-overview-graph/block-scene.ts index adcf736fc..5d2196f1e 100644 --- a/frontend/src/app/components/block-overview-graph/block-scene.ts +++ b/frontend/src/app/components/block-overview-graph/block-scene.ts @@ -1,6 +1,6 @@ import { FastVertexArray } from './fast-vertex-array'; import TxView from './tx-view'; -import { TransactionStripped } from '../../interfaces/websocket.interface'; +import { TransactionStripped } from '../../interfaces/node-api.interface'; import { Color, Position, Square, ViewUpdateParams } from './sprite-types'; import { defaultColorFunction } from './utils'; diff --git a/frontend/src/app/components/block-overview-graph/tx-view.ts b/frontend/src/app/components/block-overview-graph/tx-view.ts index fec0e7090..742c305f5 100644 --- a/frontend/src/app/components/block-overview-graph/tx-view.ts +++ b/frontend/src/app/components/block-overview-graph/tx-view.ts @@ -33,7 +33,7 @@ export default class TxView implements TransactionStripped { flags: number; bigintFlags?: bigint | null = 0b00000100_00000000_00000000_00000000n; time?: number; - status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'rbf' | 'accelerated'; + status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'prioritized' | 'censored' | 'selected' | 'rbf' | 'accelerated'; context?: 'projected' | 'actual'; scene?: BlockScene; diff --git a/frontend/src/app/components/block-overview-graph/utils.ts b/frontend/src/app/components/block-overview-graph/utils.ts index 9c800ad85..b6c8ccf5e 100644 --- a/frontend/src/app/components/block-overview-graph/utils.ts +++ b/frontend/src/app/components/block-overview-graph/utils.ts @@ -45,7 +45,7 @@ export const defaultAuditColors = { censored: hexToColor('f344df'), missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7), added: hexToColor('0099ff'), - selected: darken(desaturate(hexToColor('0099ff'), 0.3), 0.7), + prioritized: darken(desaturate(hexToColor('0099ff'), 0.3), 0.7), accelerated: hexToColor('8F5FF6'), }; @@ -81,6 +81,8 @@ export function defaultColorFunction( return auditColors.missing; case 'added': return auditColors.added; + case 'prioritized': + return auditColors.prioritized; case 'selected': return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1]; case 'accelerated': diff --git a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html index 974c4c1db..20b7fc8c4 100644 --- a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html +++ b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html @@ -74,6 +74,7 @@ Recently broadcasted Recently CPFP'd Added + Prioritized Marginal fee rate Conflicting Accelerated diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts index 2602c5ecd..8866a9d68 100644 --- a/frontend/src/app/components/block/block.component.ts +++ b/frontend/src/app/components/block/block.component.ts @@ -371,6 +371,7 @@ export class BlockComponent implements OnInit, OnDestroy { const inTemplate = {}; const inBlock = {}; const isAdded = {}; + const isPrioritized = {}; const isCensored = {}; const isMissing = {}; const isSelected = {}; @@ -394,6 +395,9 @@ export class BlockComponent implements OnInit, OnDestroy { 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; } @@ -443,6 +447,8 @@ export class BlockComponent implements OnInit, OnDestroy { 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]) { diff --git a/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.ts b/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.ts index d7a63710f..ca5b3f452 100644 --- a/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.ts +++ b/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.ts @@ -1,6 +1,6 @@ import { HostListener, OnChanges, OnDestroy } from '@angular/core'; import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core'; -import { TransactionStripped } from '../../interfaces/websocket.interface'; +import { TransactionStripped } from '../../interfaces/node-api.interface'; import { StateService } from '../../services/state.service'; import { VbytesPipe } from '../../shared/pipes/bytes-pipe/vbytes.pipe'; import { selectPowerOfTen } from '../../bitcoin.utils'; diff --git a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts index 29825491c..48cd6cccb 100644 --- a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts +++ b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts @@ -1,7 +1,8 @@ import { Component, ComponentRef, ViewChild, HostListener, Input, Output, EventEmitter, OnInit, OnDestroy, OnChanges, ChangeDetectionStrategy, ChangeDetectorRef, AfterViewInit } from '@angular/core'; import { StateService } from '../../services/state.service'; -import { MempoolBlockDelta, TransactionStripped } from '../../interfaces/websocket.interface'; +import { MempoolBlockDelta } from '../../interfaces/websocket.interface'; +import { TransactionStripped } from '../../interfaces/node-api.interface'; import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component'; import { Subscription, BehaviorSubject, merge, of, timer } from 'rxjs'; import { switchMap, filter, concatMap, map } from 'rxjs/operators'; diff --git a/frontend/src/app/components/mempool-block/mempool-block.component.ts b/frontend/src/app/components/mempool-block/mempool-block.component.ts index 7f41faffb..430a456ec 100644 --- a/frontend/src/app/components/mempool-block/mempool-block.component.ts +++ b/frontend/src/app/components/mempool-block/mempool-block.component.ts @@ -3,7 +3,8 @@ import { detectWebGL } from '../../shared/graphs.utils'; import { StateService } from '../../services/state.service'; import { ActivatedRoute, ParamMap } from '@angular/router'; import { switchMap, map, tap, filter } from 'rxjs/operators'; -import { MempoolBlock, TransactionStripped } from '../../interfaces/websocket.interface'; +import { MempoolBlock } from '../../interfaces/websocket.interface'; +import { TransactionStripped } from '../../interfaces/node-api.interface'; import { Observable, BehaviorSubject } from 'rxjs'; import { SeoService } from '../../services/seo.service'; import { seoDescriptionNetwork } from '../../shared/common.utils'; diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index f2a580d07..d211edaf2 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -77,8 +77,9 @@ Coinbase Expected in Block Seen in Mempool - Not seen in Mempool - Added + Not seen in Mempool + Added + Prioritized Conflict diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index 79f8ebebe..93fb97fac 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -42,6 +42,7 @@ interface AuditStatus { seen?: boolean; expected?: boolean; added?: boolean; + prioritized?: boolean; delayed?: number; accelerated?: boolean; conflict?: boolean; @@ -317,13 +318,15 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { fetchAudit ? this.apiService.getBlockAudit$(hash).pipe( map(audit => { const isAdded = audit.addedTxs.includes(txid); + const isPrioritized = audit.prioritizedTxs.includes(txid); const isAccelerated = audit.acceleratedTxs.includes(txid); const isConflict = audit.fullrbfTxs.includes(txid); const isExpected = audit.template.some(tx => tx.txid === txid); return { - seen: isExpected || !(isAdded || isConflict), + seen: isExpected || isPrioritized || isAccelerated, expected: isExpected, added: isAdded, + prioritized: isPrioritized, conflict: isConflict, accelerated: isAccelerated, }; diff --git a/frontend/src/app/dashboard/dashboard.component.ts b/frontend/src/app/dashboard/dashboard.component.ts index a3155263e..f396ba6ae 100644 --- a/frontend/src/app/dashboard/dashboard.component.ts +++ b/frontend/src/app/dashboard/dashboard.component.ts @@ -1,8 +1,8 @@ import { AfterViewInit, ChangeDetectionStrategy, Component, HostListener, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core'; import { combineLatest, EMPTY, fromEvent, interval, merge, Observable, of, Subject, Subscription, timer } from 'rxjs'; import { catchError, delayWhen, distinctUntilChanged, filter, map, scan, share, shareReplay, startWith, switchMap, takeUntil, tap, throttleTime } from 'rxjs/operators'; -import { AuditStatus, BlockExtended, CurrentPegs, FederationAddress, FederationUtxo, OptimizedMempoolStats, PegsVolume, RecentPeg } from '../interfaces/node-api.interface'; -import { MempoolInfo, TransactionStripped, ReplacementInfo } from '../interfaces/websocket.interface'; +import { AuditStatus, BlockExtended, CurrentPegs, FederationAddress, FederationUtxo, OptimizedMempoolStats, PegsVolume, RecentPeg, TransactionStripped } from '../interfaces/node-api.interface'; +import { MempoolInfo, ReplacementInfo } from '../interfaces/websocket.interface'; import { ApiService } from '../services/api.service'; import { StateService } from '../services/state.service'; import { WebsocketService } from '../services/websocket.service'; diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index 9680d2069..8441acc14 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -208,6 +208,7 @@ export interface BlockExtended extends Block { export interface BlockAudit extends BlockExtended { missingTxs: string[], addedTxs: string[], + prioritizedTxs: string[], freshTxs: string[], sigopTxs: string[], fullrbfTxs: string[], @@ -231,7 +232,7 @@ export interface TransactionStripped { acc?: boolean; flags?: number | null; time?: number; - status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'rbf' | 'accelerated'; + status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'prioritized' | 'censored' | 'selected' | 'rbf' | 'accelerated'; context?: 'projected' | 'actual'; } diff --git a/frontend/src/app/interfaces/websocket.interface.ts b/frontend/src/app/interfaces/websocket.interface.ts index a36126051..9fef204ba 100644 --- a/frontend/src/app/interfaces/websocket.interface.ts +++ b/frontend/src/app/interfaces/websocket.interface.ts @@ -1,7 +1,7 @@ import { SafeResourceUrl } from '@angular/platform-browser'; import { ILoadingIndicators } from '../services/state.service'; import { Transaction } from './electrs.interface'; -import { BlockExtended, DifficultyAdjustment, RbfTree } from './node-api.interface'; +import { BlockExtended, DifficultyAdjustment, RbfTree, TransactionStripped } from './node-api.interface'; export interface WebsocketResponse { block?: BlockExtended; @@ -92,19 +92,6 @@ export interface MempoolInfo { minrelaytxfee: number; // (numeric) Current minimum relay fee for transactions } -export interface TransactionStripped { - txid: string; - fee: number; - vsize: number; - value: number; - acc?: boolean; // is accelerated? - rate?: number; // effective fee rate - flags?: number; - time?: number; - status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'rbf' | 'accelerated'; - context?: 'projected' | 'actual'; -} - // [txid, fee, vsize, value, rate, flags, acceleration?] export type TransactionCompressed = [string, number, number, number, number, number, number, 1?]; // [txid, rate, flags, acceleration?] diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index f9de61b69..92b98ebca 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -1,8 +1,8 @@ import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core'; import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable, merge } from 'rxjs'; import { Transaction } from '../interfaces/electrs.interface'; -import { HealthCheckHost, IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, TransactionStripped } from '../interfaces/websocket.interface'; -import { BlockExtended, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface'; +import { HealthCheckHost, IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo } from '../interfaces/websocket.interface'; +import { BlockExtended, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree, TransactionStripped } from '../interfaces/node-api.interface'; import { Router, NavigationStart } from '@angular/router'; import { isPlatformBrowser } from '@angular/common'; import { filter, map, scan, shareReplay } from 'rxjs/operators'; diff --git a/frontend/src/app/shared/common.utils.ts b/frontend/src/app/shared/common.utils.ts index 482d9567f..be1e32c73 100644 --- a/frontend/src/app/shared/common.utils.ts +++ b/frontend/src/app/shared/common.utils.ts @@ -1,4 +1,5 @@ -import { MempoolBlockDelta, MempoolBlockDeltaCompressed, MempoolDeltaChange, TransactionCompressed, TransactionStripped } from "../interfaces/websocket.interface"; +import { MempoolBlockDelta, MempoolBlockDeltaCompressed, MempoolDeltaChange, TransactionCompressed } from "../interfaces/websocket.interface"; +import { TransactionStripped } from "../interfaces/node-api.interface"; export function isMobile(): boolean { return (window.innerWidth <= 767.98); From 03255dd07783da80bc796d68c65664717e196278 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 1 Apr 2024 08:42:23 +0000 Subject: [PATCH 5/5] Improve marginal fee rate detection --- backend/src/api/audit.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/src/api/audit.ts b/backend/src/api/audit.ts index 8fa8cbdb1..fb0d05baa 100644 --- a/backend/src/api/audit.ts +++ b/backend/src/api/audit.ts @@ -83,11 +83,13 @@ class Audit { const tx = mempool[txid]; if (tx) { const fits = (tx.weight - displacedWeightRemaining) < 4000; - const feeMatches = tx.effectiveFeePerVsize >= lastFeeRate; + // 0.005 margin of error for any remaining vsize rounding issues + const feeMatches = tx.effectiveFeePerVsize >= (lastFeeRate - 0.005); if (fits || feeMatches) { isDisplaced[txid] = true; if (fits) { - lastFeeRate = Math.min(lastFeeRate, tx.effectiveFeePerVsize); + // (tx.effectiveFeePerVsize * tx.vsize) / Math.ceil(tx.vsize) attempts to correct for vsize rounding in the simple non-CPFP case + lastFeeRate = Math.min(lastFeeRate, (tx.effectiveFeePerVsize * tx.vsize) / Math.ceil(tx.vsize)); } if (tx.firstSeen == null || (now - (tx?.firstSeen || 0)) > PROPAGATION_MARGIN) { displacedWeightRemaining -= tx.weight;