From 3d9133c47e5de24edc1a3a91ca797d2b81e3459b Mon Sep 17 00:00:00 2001 From: natsoni Date: Thu, 25 Jul 2024 11:52:55 +0200 Subject: [PATCH 1/3] Add fee delta to acceleration data --- backend/src/api/bitcoin/bitcoin.routes.ts | 1 + backend/src/api/mempool-blocks.ts | 2 ++ backend/src/api/websocket-handler.ts | 4 ++++ backend/src/mempool.interfaces.ts | 4 +++- .../src/app/components/transaction/transaction.component.ts | 1 + frontend/src/app/interfaces/electrs.interface.ts | 1 + frontend/src/app/interfaces/node-api.interface.ts | 1 + 7 files changed, 13 insertions(+), 1 deletion(-) diff --git a/backend/src/api/bitcoin/bitcoin.routes.ts b/backend/src/api/bitcoin/bitcoin.routes.ts index a65af3f19..6225a9c1d 100644 --- a/backend/src/api/bitcoin/bitcoin.routes.ts +++ b/backend/src/api/bitcoin/bitcoin.routes.ts @@ -165,6 +165,7 @@ class BitcoinRoutes { acceleration: tx.acceleration, acceleratedBy: tx.acceleratedBy || undefined, acceleratedAt: tx.acceleratedAt || undefined, + feeDelta: tx.feeDelta || undefined, }); return; } diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index e655601e5..5d9dcf8f4 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -453,6 +453,7 @@ class MempoolBlocks { mempoolTx.acceleration = true; mempoolTx.acceleratedBy = isAcceleratedBy[txid] || acceleration?.pools; mempoolTx.acceleratedAt = acceleration?.added; + mempoolTx.feeDelta = acceleration?.feeDelta; for (const ancestor of mempoolTx.ancestors || []) { if (!mempool[ancestor.txid].acceleration) { mempool[ancestor.txid].cpfpDirty = true; @@ -460,6 +461,7 @@ class MempoolBlocks { mempool[ancestor.txid].acceleration = true; mempool[ancestor.txid].acceleratedBy = mempoolTx.acceleratedBy; mempool[ancestor.txid].acceleratedAt = mempoolTx.acceleratedAt; + mempool[ancestor.txid].feeDelta = mempoolTx.feeDelta; isAcceleratedBy[ancestor.txid] = mempoolTx.acceleratedBy; } } else { diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 32d306ad2..e57b8221b 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -823,6 +823,7 @@ class WebsocketHandler { accelerated: mempoolTx.acceleration || undefined, acceleratedBy: mempoolTx.acceleratedBy || undefined, acceleratedAt: mempoolTx.acceleratedAt || undefined, + feeDelta: mempoolTx.feeDelta || undefined, }, accelerationPositions: memPool.getAccelerationPositions(mempoolTx.txid), }; @@ -864,6 +865,7 @@ class WebsocketHandler { accelerated: mempoolTx.acceleration || undefined, acceleratedBy: mempoolTx.acceleratedBy || undefined, acceleratedAt: mempoolTx.acceleratedAt || undefined, + feeDelta: mempoolTx.feeDelta || undefined, }; if (!mempoolTx.cpfpChecked) { calculateMempoolTxCpfp(mempoolTx, newMempool); @@ -1138,6 +1140,7 @@ class WebsocketHandler { accelerated: mempoolTx.acceleration || undefined, acceleratedBy: mempoolTx.acceleratedBy || undefined, acceleratedAt: mempoolTx.acceleratedAt || undefined, + feeDelta: mempoolTx.feeDelta || undefined, }, accelerationPositions: memPool.getAccelerationPositions(mempoolTx.txid), }); @@ -1160,6 +1163,7 @@ class WebsocketHandler { accelerated: mempoolTx.acceleration || undefined, acceleratedBy: mempoolTx.acceleratedBy || undefined, acceleratedAt: mempoolTx.acceleratedAt || undefined, + feeDelta: mempoolTx.feeDelta || undefined, }; } } diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index 0ad60f4b9..2dd0f17dd 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -126,6 +126,7 @@ export interface TransactionExtended extends IEsploraApi.Transaction { acceleration?: boolean; acceleratedBy?: number[]; acceleratedAt?: number; + feeDelta?: number; replacement?: boolean; uid?: number; flags?: number; @@ -449,7 +450,7 @@ export interface OptimizedStatistic { export interface TxTrackingInfo { replacedBy?: string, - position?: { block: number, vsize: number, accelerated?: boolean, acceleratedBy?: number[], acceleratedAt?: number }, + position?: { block: number, vsize: number, accelerated?: boolean, acceleratedBy?: number[], acceleratedAt?: number, feeDelta?: number }, cpfp?: { ancestors?: Ancestor[], bestDescendant?: Ancestor | null, @@ -462,6 +463,7 @@ export interface TxTrackingInfo { accelerated?: boolean, acceleratedBy?: number[], acceleratedAt?: number, + feeDelta?: number, confirmed?: boolean } diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index 924addaa0..107e471fc 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -816,6 +816,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { this.tx.acceleration = cpfpInfo.acceleration; this.tx.acceleratedBy = cpfpInfo.acceleratedBy; this.tx.acceleratedAt = cpfpInfo.acceleratedAt; + this.tx.feeDelta = cpfpInfo.feeDelta; this.setIsAccelerated(firstCpfp); } diff --git a/frontend/src/app/interfaces/electrs.interface.ts b/frontend/src/app/interfaces/electrs.interface.ts index 942386d8f..2167099c2 100644 --- a/frontend/src/app/interfaces/electrs.interface.ts +++ b/frontend/src/app/interfaces/electrs.interface.ts @@ -22,6 +22,7 @@ export interface Transaction { acceleration?: boolean; acceleratedBy?: number[]; acceleratedAt?: number; + feeDelta?: number; deleteAfter?: number; _unblinded?: any; _deduced?: boolean; diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index 9a00faadc..077bfa775 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -31,6 +31,7 @@ export interface CpfpInfo { acceleration?: boolean; acceleratedBy?: number[]; acceleratedAt?: number; + feeDelta?: number; } export interface RbfInfo { From 01311d0ba14a07234f4cd2ce4f42bc93cf28f9e3 Mon Sep 17 00:00:00 2001 From: natsoni Date: Fri, 26 Jul 2024 11:21:24 +0200 Subject: [PATCH 2/3] Add tooltip to timeline --- ...celeration-timeline-tooltip.component.html | 62 +++++++++++++++++++ ...celeration-timeline-tooltip.component.scss | 52 ++++++++++++++++ ...acceleration-timeline-tooltip.component.ts | 38 ++++++++++++ .../acceleration-timeline.component.html | 17 +++-- .../acceleration-timeline.component.scss | 9 ++- .../acceleration-timeline.component.ts | 57 ++++++++++++++++- .../transaction/transaction.component.html | 2 +- frontend/src/app/shared/shared.module.ts | 3 + 8 files changed, 232 insertions(+), 8 deletions(-) create mode 100644 frontend/src/app/components/acceleration-timeline/acceleration-timeline-tooltip.component.html create mode 100644 frontend/src/app/components/acceleration-timeline/acceleration-timeline-tooltip.component.scss create mode 100644 frontend/src/app/components/acceleration-timeline/acceleration-timeline-tooltip.component.ts diff --git a/frontend/src/app/components/acceleration-timeline/acceleration-timeline-tooltip.component.html b/frontend/src/app/components/acceleration-timeline/acceleration-timeline-tooltip.component.html new file mode 100644 index 000000000..030def3a6 --- /dev/null +++ b/frontend/src/app/components/acceleration-timeline/acceleration-timeline-tooltip.component.html @@ -0,0 +1,62 @@ +
+ + + + + + + + + + + + + @if (accelerationInfo.status === 'accelerated') { + + } @else { + + } + + + @if (accelerationInfo.status === 'seen') { + + + } @else if (accelerationInfo.status === 'accelerated' || accelerationInfo.status === 'mined') { + + @if (accelerationInfo.status === 'accelerated') { + + } @else { + + } + } + + + + + + +
Status + @if (accelerationInfo.status === 'seen') { + First seen + } @else if (accelerationInfo.status === 'accelerated') { + Accelerated + } @else if (accelerationInfo.status === 'mined') { + Mined + } +
Fee{{ accelerationInfo.fee | number }} sat
Out-of-band fees{{ accelerationInfo.feeDelta | number }} sat{{ accelerationInfo.bidBoost | number }} sat
Fee rateAccelerated fee rate
Accelerated by + + + +
+
diff --git a/frontend/src/app/components/acceleration-timeline/acceleration-timeline-tooltip.component.scss b/frontend/src/app/components/acceleration-timeline/acceleration-timeline-tooltip.component.scss new file mode 100644 index 000000000..29a157507 --- /dev/null +++ b/frontend/src/app/components/acceleration-timeline/acceleration-timeline-tooltip.component.scss @@ -0,0 +1,52 @@ +.acceleration-tooltip { + position: fixed; + z-index: 3; + background: color-mix(in srgb, var(--active-bg) 95%, transparent); + border-radius: 4px; + box-shadow: 1px 1px 10px rgba(0,0,0,0.5); + color: var(--tooltip-grey); + display: flex; + flex-direction: column; + justify-content: space-between; + padding: 10px 15px; + text-align: left; + pointer-events: none; + + .badge.badge-accelerated { + background-color: var(--tertiary); + color: white; + } + + .value { + text-align: end; + } + + .label { + padding-right: 30px; + } + + .pool-logo { + width: 22px; + height: 22px; + position: relative; + top: -1px; + margin-right: 3px; + } + + .highlight { + filter: drop-shadow(0 0 5px #905cf4); + animation: pulse 1s infinite; + } +} + +@keyframes pulse { + 0% { + transform: scale(1); + } + 50% { + transform: scale(1.2); + } + 100% { + transform: scale(1); + } +} \ No newline at end of file diff --git a/frontend/src/app/components/acceleration-timeline/acceleration-timeline-tooltip.component.ts b/frontend/src/app/components/acceleration-timeline/acceleration-timeline-tooltip.component.ts new file mode 100644 index 000000000..b4b3405fc --- /dev/null +++ b/frontend/src/app/components/acceleration-timeline/acceleration-timeline-tooltip.component.ts @@ -0,0 +1,38 @@ +import { Component, ElementRef, ViewChild, Input, OnChanges } from '@angular/core'; + +@Component({ + selector: 'app-acceleration-timeline-tooltip', + templateUrl: './acceleration-timeline-tooltip.component.html', + styleUrls: ['./acceleration-timeline-tooltip.component.scss'], +}) +export class AccelerationTimelineTooltipComponent implements OnChanges { + @Input() accelerationInfo: any; + @Input() cursorPosition: { x: number, y: number }; + + tooltipPosition: any = null; + + @ViewChild('tooltip') tooltipElement: ElementRef; + + constructor() {} + + ngOnChanges(changes): void { + if (changes.cursorPosition && changes.cursorPosition.currentValue) { + let x = Math.max(10, changes.cursorPosition.currentValue.x - 50); + let y = changes.cursorPosition.currentValue.y + 20; + if (this.tooltipElement) { + const elementBounds = this.tooltipElement.nativeElement.getBoundingClientRect(); + if ((x + elementBounds.width) > (window.innerWidth - 10)) { + x = Math.max(0, window.innerWidth - elementBounds.width - 10); + } + if (y + elementBounds.height > (window.innerHeight - 20)) { + y = y - elementBounds.height - 20; + } + } + this.tooltipPosition = { x, y }; + } + } + + hasPoolsData(): boolean { + return Object.keys(this.accelerationInfo.poolsData).length > 0; + } +} diff --git a/frontend/src/app/components/acceleration-timeline/acceleration-timeline.component.html b/frontend/src/app/components/acceleration-timeline/acceleration-timeline.component.html index 28076efa5..8eba6cdeb 100644 --- a/frontend/src/app/components/acceleration-timeline/acceleration-timeline.component.html +++ b/frontend/src/app/components/acceleration-timeline/acceleration-timeline.component.html @@ -26,7 +26,7 @@
-
+
Mined
@@ -58,7 +58,7 @@
-
+
First seen
@@ -80,7 +80,7 @@ } @else {
} -
+
@if (!tx.status.confirmed) {
@@ -113,7 +113,10 @@ } @else {
} -
+
@if (tx.status.confirmed) { @@ -130,4 +133,10 @@
+ + +
diff --git a/frontend/src/app/components/acceleration-timeline/acceleration-timeline.component.scss b/frontend/src/app/components/acceleration-timeline/acceleration-timeline.component.scss index 93a0cdba1..f351a0114 100644 --- a/frontend/src/app/components/acceleration-timeline/acceleration-timeline.component.scss +++ b/frontend/src/app/components/acceleration-timeline/acceleration-timeline.component.scss @@ -152,9 +152,16 @@ margin-bottom: -8px; transform: translateY(-50%); border-radius: 50%; - cursor: pointer; padding: 4px; background: transparent; + transition: background-color 300ms, padding 300ms; + + &.hovering { + cursor: pointer; + &:hover { + padding: 0px; + } + } .shape { position: relative; diff --git a/frontend/src/app/components/acceleration-timeline/acceleration-timeline.component.ts b/frontend/src/app/components/acceleration-timeline/acceleration-timeline.component.ts index c8dbed72b..da0eee4a3 100644 --- a/frontend/src/app/components/acceleration-timeline/acceleration-timeline.component.ts +++ b/frontend/src/app/components/acceleration-timeline/acceleration-timeline.component.ts @@ -1,6 +1,8 @@ -import { Component, Input, OnInit, OnChanges } from '@angular/core'; +import { Component, Input, OnInit, OnChanges, HostListener } from '@angular/core'; import { ETA } from '../../services/eta.service'; import { Transaction } from '../../interfaces/electrs.interface'; +import { Acceleration, SinglePoolStats } from '../../interfaces/node-api.interface'; +import { MiningService } from '../../services/mining.service'; @Component({ selector: 'app-acceleration-timeline', @@ -10,6 +12,7 @@ import { Transaction } from '../../interfaces/electrs.interface'; export class AccelerationTimelineComponent implements OnInit, OnChanges { @Input() transactionTime: number; @Input() tx: Transaction; + @Input() accelerationInfo: Acceleration; @Input() eta: ETA; // A mined transaction has standard ETA and accelerated ETA undefined // A transaction in mempool has either standardETA defined (if accelerated) or acceleratedETA defined (if not accelerated yet) @@ -22,13 +25,25 @@ export class AccelerationTimelineComponent implements OnInit, OnChanges { useAbsoluteTime: boolean = false; interval: number; - constructor() {} + tooltipPosition = null; + hoverInfo: any = null; + poolsData: { [id: number]: SinglePoolStats } = {}; + + constructor( + private miningService: MiningService, + ) {} ngOnInit(): void { this.acceleratedAt = this.tx.acceleratedAt ?? new Date().getTime() / 1000; this.now = Math.floor(new Date().getTime() / 1000); this.useAbsoluteTime = this.tx.status.block_time < this.now - 7 * 24 * 3600; + this.miningService.getPools().subscribe(pools => { + for (const pool of pools) { + this.poolsData[pool.unique_id] = pool; + } + }); + this.interval = window.setInterval(() => { this.now = Math.floor(new Date().getTime() / 1000); this.useAbsoluteTime = this.tx.status.block_time < this.now - 7 * 24 * 3600; @@ -52,4 +67,42 @@ export class AccelerationTimelineComponent implements OnInit, OnChanges { ngOnDestroy(): void { clearInterval(this.interval); } + + onHover(event, status: string): void { + if (status === 'seen') { + this.hoverInfo = { + status, + fee: this.tx.fee, + weight: this.tx.weight + }; + } else if (status === 'accelerated') { + this.hoverInfo = { + status, + fee: this.accelerationInfo?.effectiveFee || this.tx.fee, + weight: this.tx.weight, + feeDelta: this.accelerationInfo?.feeDelta || this.tx.feeDelta, + pools: this.tx.acceleratedBy || this.accelerationInfo?.pools, + poolsData: this.poolsData + }; + } else if (status === 'mined') { + this.hoverInfo = { + status, + fee: this.accelerationInfo?.effectiveFee, + weight: this.tx.weight, + bidBoost: this.accelerationInfo?.bidBoost, + minedByPoolUniqueId: this.accelerationInfo?.minedByPoolUniqueId, + pools: this.tx.acceleratedBy || this.accelerationInfo?.pools, + poolsData: this.poolsData + }; + } + } + + onBlur(event): void { + this.hoverInfo = null; + } + + @HostListener('pointermove', ['$event']) + onPointerMove(event) { + this.tooltipPosition = { x: event.clientX, y: event.clientY }; + } } diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index 9ce22d26c..a6a985fb0 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -167,7 +167,7 @@

Acceleration Timeline

- +
diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 313a43e1f..89bcfafbb 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -68,6 +68,7 @@ import { AddressTransactionsWidgetComponent } from '../components/address-transa import { RbfTimelineComponent } from '../components/rbf-timeline/rbf-timeline.component'; import { AccelerationTimelineComponent } from '../components/acceleration-timeline/acceleration-timeline.component'; import { RbfTimelineTooltipComponent } from '../components/rbf-timeline/rbf-timeline-tooltip.component'; +import { AccelerationTimelineTooltipComponent } from '../components/acceleration-timeline/acceleration-timeline-tooltip.component'; import { PushTransactionComponent } from '../components/push-transaction/push-transaction.component'; import { TestTransactionsComponent } from '../components/test-transactions/test-transactions.component'; import { AssetsFeaturedComponent } from '../components/assets/assets-featured/assets-featured.component'; @@ -180,6 +181,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir RbfTimelineComponent, AccelerationTimelineComponent, RbfTimelineTooltipComponent, + AccelerationTimelineTooltipComponent, PushTransactionComponent, TestTransactionsComponent, AssetsNavComponent, @@ -320,6 +322,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir RbfTimelineComponent, AccelerationTimelineComponent, RbfTimelineTooltipComponent, + AccelerationTimelineTooltipComponent, PushTransactionComponent, TestTransactionsComponent, AssetsNavComponent, From 7531a53b2e9f62ec91f143e176335093747dddbf Mon Sep 17 00:00:00 2001 From: softsimon Date: Fri, 26 Jul 2024 11:06:36 -0500 Subject: [PATCH 3/3] correct i18n --- .../acceleration-timeline-tooltip.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/acceleration-timeline/acceleration-timeline-tooltip.component.html b/frontend/src/app/components/acceleration-timeline/acceleration-timeline-tooltip.component.html index 030def3a6..7dfc15f55 100644 --- a/frontend/src/app/components/acceleration-timeline/acceleration-timeline-tooltip.component.html +++ b/frontend/src/app/components/acceleration-timeline/acceleration-timeline-tooltip.component.html @@ -20,7 +20,7 @@ - Fee + Fee {{ accelerationInfo.fee | number }} sat @@ -45,7 +45,7 @@ } - Accelerated by + Accelerated by