From d6f1b51fa6afaac66cc3fd9bc18bdd33b9665441 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 22 Mar 2024 07:13:21 +0000 Subject: [PATCH] Show goggles badges on block overview tooltip --- .../block-overview-graph.component.html | 2 + .../block-overview-graph/tx-view.ts | 2 + .../block-overview-tooltip.component.html | 74 +++++++++++-------- .../block-overview-tooltip.component.scss | 51 ++++++++++++- .../block-overview-tooltip.component.ts | 38 +++++++--- frontend/src/app/shared/filters.utils.ts | 63 +++++++++------- 6 files changed, 158 insertions(+), 72 deletions(-) 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 1d13e8b4e..f93ab0987 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 @@ -12,6 +12,8 @@ [clickable]="!!selectedTx" [auditEnabled]="auditHighlighting" [blockConversion]="blockConversion" + [filterFlags]="activeFilterFlags" + [filterMode]="filterMode" >
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 b6008ef1d..f9f6eeeb7 100644 --- a/frontend/src/app/components/block-overview-graph/tx-view.ts +++ b/frontend/src/app/components/block-overview-graph/tx-view.ts @@ -30,6 +30,7 @@ export default class TxView implements TransactionStripped { feerate: number; acc?: boolean; rate?: number; + flags: number; bigintFlags?: bigint | null = 0b00000100_00000000_00000000_00000000n; status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'rbf' | 'accelerated'; context?: 'projected' | 'actual'; @@ -59,6 +60,7 @@ export default class TxView implements TransactionStripped { this.acc = tx.acc; this.rate = tx.rate; this.status = tx.status; + this.flags = tx.flags || 0; this.bigintFlags = tx.flags ? (BigInt(tx.flags) | (this.acc ? TransactionFlags.acceleration : 0n)): 0n; this.initialised = false; this.vertexArray = scene.vertexArray; 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 8fb687ebd..a00ac94eb 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 @@ -6,61 +6,71 @@ [style.left]="tooltipPosition.x + 'px'" [style.top]="tooltipPosition.y + 'px'" > - +
- - + - - + + - - + + - - + - - - + + - - + + - - + + - - - - - - - - - - - - - + + - - - + {{ activeFilters.rbf }} + +
Transaction + Transaction {{ txid | shortenString : 16}}
AmountAmount
Fee{{ fee | number }} sat   Fee{{ fee | number }} sat  
Fee rate + Fee rate
Effective fee rateAccelerated fee rate + Effective fee rateAccelerated fee rate
Virtual sizeVirtual size
WeightWeight
Audit statusMatchRemovedMarginal fee rateHigh sigop countRecently broadcastedRecently CPFP'dAddedMarginal fee rateConflictingAcceleratedAudit status + + Match + Removed + Marginal fee rate + High sigop count + Recently broadcasted + Recently CPFP'd + Added + Marginal fee rate + Conflicting + Accelerated + +
Accelerated
+
+ + Accelerated + + {{ filter.label }} + +
+
diff --git a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.scss b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.scss index 81a79eb67..89bffe84c 100644 --- a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.scss +++ b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.scss @@ -10,6 +10,7 @@ padding: 10px 15px; text-align: left; min-width: 320px; + max-width: 320px; pointer-events: none; z-index: 11; @@ -18,8 +19,14 @@ } } -.td-width { - padding-right: 10px; +th, td { + &.label { + width: 30%; + } + &.value { + width: 70%; + text-align: end; + } } .badge.badge-accelerated { @@ -29,6 +36,46 @@ animation: acceleratePulse 1s infinite; } +.tags { + display: flex; + flex-wrap: wrap; + row-gap: 0.25em; + margin-top: 0.2em; + max-width: 100%; + + .badge { + border-radius: 0.2rem; + padding: 0.2em 0.5em; + margin-right: 0.25em; + } + + .filter-tag { + background: #181b2daf; + border: solid 1px #105fb0; + color: white; + transition: background-color 300ms; + + &.matching { + background-color: #105fb0; + } + } + + &.any-mode { + .filter-tag { + border: solid 1px #1a9436; + &.matching { + background-color: #1a9436; + } + } + } + + .goggles-icon { + height: 0.9em; + margin-top: -5px; + margin-right: 0.2em; + } +} + @keyframes acceleratePulse { 0% { background-color: #653b9c; box-shadow: #ad7de57f 0px 0px 12px -2px; } 50% { background-color: #8457bb; box-shadow: #ad7de5 0px 0px 18px -2px;} 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 6b23276c8..a11a48ee3 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 @@ -1,8 +1,8 @@ -import { Component, ElementRef, ViewChild, Input, OnChanges, ChangeDetectionStrategy } from '@angular/core'; +import { Component, ElementRef, ViewChild, Input, OnChanges, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; 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 { TransactionFlags } from '../../shared/filters.utils'; +import { Filter, FilterMode, TransactionFlags, toFilters } from '../../shared/filters.utils'; @Component({ selector: 'app-block-overview-tooltip', @@ -15,6 +15,8 @@ export class BlockOverviewTooltipComponent implements OnChanges { @Input() clickable: boolean; @Input() auditEnabled: boolean = false; @Input() blockConversion: Price; + @Input() filterFlags: bigint | null = null; + @Input() filterMode: FilterMode = 'and'; txid = ''; fee = 0; @@ -24,12 +26,16 @@ export class BlockOverviewTooltipComponent implements OnChanges { effectiveRate; acceleration; hasEffectiveRate: boolean = false; + filters: Filter[] = []; + activeFilters: { [key: string]: boolean } = {}; tooltipPosition: Position = { x: 0, y: 0 }; @ViewChild('tooltip') tooltipElement: ElementRef; - constructor() {} + constructor( + private cd: ChangeDetectorRef, + ) {} ngOnChanges(changes): void { if (changes.cursorPosition && changes.cursorPosition.currentValue) { @@ -48,17 +54,25 @@ export class BlockOverviewTooltipComponent implements OnChanges { this.tooltipPosition = { x, y }; } - if (changes.tx) { - const tx = changes.tx.currentValue || {}; - this.txid = tx.txid || ''; - this.fee = tx.fee || 0; - this.value = tx.value || 0; - this.vsize = tx.vsize || 1; + if (this.tx && (changes.tx || changes.filterFlags || changes.filterMode)) { + this.txid = this.tx.txid || ''; + this.fee = this.tx.fee || 0; + this.value = this.tx.value || 0; + this.vsize = this.tx.vsize || 1; this.feeRate = this.fee / this.vsize; - this.effectiveRate = tx.rate; - this.acceleration = tx.acc; + this.effectiveRate = this.tx.rate; + this.acceleration = this.tx.acc; + const txFlags = BigInt(this.tx.flags) || 0n; this.hasEffectiveRate = Math.abs((this.fee / this.vsize) - this.effectiveRate) > 0.05 - || (tx.bigintFlags && (tx.bigintFlags & (TransactionFlags.cpfp_child | TransactionFlags.cpfp_parent)) > 0n); + || (txFlags && (txFlags & (TransactionFlags.cpfp_child | TransactionFlags.cpfp_parent)) > 0n); + this.filters = this.tx.flags ? toFilters(txFlags).filter(f => f.tooltip) : []; + this.activeFilters = {} + for (const filter of this.filters) { + if (this.filterFlags && (this.filterFlags & BigInt(filter.flag))) { + this.activeFilters[filter.key] = true; + } + } + this.cd.markForCheck(); } } } diff --git a/frontend/src/app/shared/filters.utils.ts b/frontend/src/app/shared/filters.utils.ts index ad4730059..2a4155dff 100644 --- a/frontend/src/app/shared/filters.utils.ts +++ b/frontend/src/app/shared/filters.utils.ts @@ -5,6 +5,7 @@ export interface Filter { toggle?: string, group?: string, important?: boolean, + tooltip?: boolean, } export type FilterMode = 'and' | 'or'; @@ -61,42 +62,52 @@ export function toFlags(filters: string[]): bigint { return flag; } +export function toFilters(flags: bigint): Filter[] { + const filters = []; + for (const filter of Object.values(TransactionFilters).filter(f => f !== undefined)) { + if (flags & filter.flag) { + filters.push(filter); + } + } + return filters; +} + export const TransactionFilters: { [key: string]: Filter } = { /* features */ - rbf: { key: 'rbf', label: 'RBF enabled', flag: TransactionFlags.rbf, toggle: 'rbf', important: true }, - no_rbf: { key: 'no_rbf', label: 'RBF disabled', flag: TransactionFlags.no_rbf, toggle: 'rbf', important: true }, - v1: { key: 'v1', label: 'Version 1', flag: TransactionFlags.v1, toggle: 'version' }, - v2: { key: 'v2', label: 'Version 2', flag: TransactionFlags.v2, toggle: 'version' }, - v3: { key: 'v3', label: 'Version 3', flag: TransactionFlags.v3, toggle: 'version' }, - nonstandard: { key: 'nonstandard', label: 'Non-Standard', flag: TransactionFlags.nonstandard, important: true }, + rbf: { key: 'rbf', label: 'RBF enabled', flag: TransactionFlags.rbf, toggle: 'rbf', important: true, tooltip: true, }, + no_rbf: { key: 'no_rbf', label: 'RBF disabled', flag: TransactionFlags.no_rbf, toggle: 'rbf', important: true, tooltip: true, }, + v1: { key: 'v1', label: 'Version 1', flag: TransactionFlags.v1, toggle: 'version', tooltip: true, }, + v2: { key: 'v2', label: 'Version 2', flag: TransactionFlags.v2, toggle: 'version', tooltip: true, }, + v3: { key: 'v3', label: 'Version 3', flag: TransactionFlags.v3, toggle: 'version', tooltip: true, }, + nonstandard: { key: 'nonstandard', label: 'Non-Standard', flag: TransactionFlags.nonstandard, important: true, tooltip: true, }, /* address types */ - p2pk: { key: 'p2pk', label: 'P2PK', flag: TransactionFlags.p2pk, important: true }, - p2ms: { key: 'p2ms', label: 'Bare multisig', flag: TransactionFlags.p2ms, important: true }, - p2pkh: { key: 'p2pkh', label: 'P2PKH', flag: TransactionFlags.p2pkh, important: true }, - p2sh: { key: 'p2sh', label: 'P2SH', flag: TransactionFlags.p2sh, important: true }, - p2wpkh: { key: 'p2wpkh', label: 'P2WPKH', flag: TransactionFlags.p2wpkh, important: true }, - p2wsh: { key: 'p2wsh', label: 'P2WSH', flag: TransactionFlags.p2wsh, important: true }, - p2tr: { key: 'p2tr', label: 'Taproot', flag: TransactionFlags.p2tr, important: true }, + p2pk: { key: 'p2pk', label: 'P2PK', flag: TransactionFlags.p2pk, important: true, tooltip: true, }, + p2ms: { key: 'p2ms', label: 'Bare multisig', flag: TransactionFlags.p2ms, important: true, tooltip: true, }, + p2pkh: { key: 'p2pkh', label: 'P2PKH', flag: TransactionFlags.p2pkh, important: true, tooltip: false, }, + p2sh: { key: 'p2sh', label: 'P2SH', flag: TransactionFlags.p2sh, important: true, tooltip: false, }, + p2wpkh: { key: 'p2wpkh', label: 'P2WPKH', flag: TransactionFlags.p2wpkh, important: true, tooltip: false, }, + p2wsh: { key: 'p2wsh', label: 'P2WSH', flag: TransactionFlags.p2wsh, important: true, tooltip: false, }, + p2tr: { key: 'p2tr', label: 'Taproot', flag: TransactionFlags.p2tr, important: true, tooltip: false, }, /* behavior */ - cpfp_parent: { key: 'cpfp_parent', label: 'Paid for by child', flag: TransactionFlags.cpfp_parent, important: true }, - cpfp_child: { key: 'cpfp_child', label: 'Pays for parent', flag: TransactionFlags.cpfp_child, important: true }, - replacement: { key: 'replacement', label: 'Replacement', flag: TransactionFlags.replacement, important: true }, + cpfp_parent: { key: 'cpfp_parent', label: 'Paid for by child', flag: TransactionFlags.cpfp_parent, important: true, tooltip: true, }, + cpfp_child: { key: 'cpfp_child', label: 'Pays for parent', flag: TransactionFlags.cpfp_child, important: true, tooltip: true, }, + replacement: { key: 'replacement', label: 'Replacement', flag: TransactionFlags.replacement, important: true, tooltip: true, }, acceleration: window?.['__env']?.ACCELERATOR ? { key: 'acceleration', label: 'Accelerated', flag: TransactionFlags.acceleration, important: false } : undefined, /* data */ - op_return: { key: 'op_return', label: 'OP_RETURN', flag: TransactionFlags.op_return, important: true }, - fake_pubkey: { key: 'fake_pubkey', label: 'Fake pubkey', flag: TransactionFlags.fake_pubkey }, - inscription: { key: 'inscription', label: 'Inscription', flag: TransactionFlags.inscription, important: true }, - fake_scripthash: { key: 'fake_scripthash', label: 'Fake scripthash', flag: TransactionFlags.fake_scripthash }, + op_return: { key: 'op_return', label: 'OP_RETURN', flag: TransactionFlags.op_return, important: true, tooltip: true, }, + fake_pubkey: { key: 'fake_pubkey', label: 'Fake pubkey', flag: TransactionFlags.fake_pubkey, tooltip: true, }, + inscription: { key: 'inscription', label: 'Inscription', flag: TransactionFlags.inscription, important: true, tooltip: true, }, + fake_scripthash: { key: 'fake_scripthash', label: 'Fake scripthash', flag: TransactionFlags.fake_scripthash, tooltip: true,}, /* heuristics */ - coinjoin: { key: 'coinjoin', label: 'Coinjoin', flag: TransactionFlags.coinjoin, important: true }, - consolidation: { key: 'consolidation', label: 'Consolidation', flag: TransactionFlags.consolidation }, - batch_payout: { key: 'batch_payout', label: 'Batch payment', flag: TransactionFlags.batch_payout }, + coinjoin: { key: 'coinjoin', label: 'Coinjoin', flag: TransactionFlags.coinjoin, important: true, tooltip: true, }, + consolidation: { key: 'consolidation', label: 'Consolidation', flag: TransactionFlags.consolidation, tooltip: true, }, + batch_payout: { key: 'batch_payout', label: 'Batch payment', flag: TransactionFlags.batch_payout, tooltip: true, }, /* sighash */ sighash_all: { key: 'sighash_all', label: 'sighash_all', flag: TransactionFlags.sighash_all }, - sighash_none: { key: 'sighash_none', label: 'sighash_none', flag: TransactionFlags.sighash_none }, - sighash_single: { key: 'sighash_single', label: 'sighash_single', flag: TransactionFlags.sighash_single }, + sighash_none: { key: 'sighash_none', label: 'sighash_none', flag: TransactionFlags.sighash_none, tooltip: true, }, + sighash_single: { key: 'sighash_single', label: 'sighash_single', flag: TransactionFlags.sighash_single, tooltip: true, }, sighash_default: { key: 'sighash_default', label: 'sighash_default', flag: TransactionFlags.sighash_default }, - sighash_acp: { key: 'sighash_acp', label: 'sighash_anyonecanpay', flag: TransactionFlags.sighash_acp }, + sighash_acp: { key: 'sighash_acp', label: 'sighash_anyonecanpay', flag: TransactionFlags.sighash_acp, tooltip: true, }, }; export const FilterGroups: { label: string, filters: Filter[]}[] = [