From 125b9e4525132304ca11c24eca871bd392adbc05 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 24 May 2023 15:49:35 -0400 Subject: [PATCH 01/69] Restore liquid max block weight to defaults --- production/mempool-frontend-config.liquid.json | 1 - 1 file changed, 1 deletion(-) diff --git a/production/mempool-frontend-config.liquid.json b/production/mempool-frontend-config.liquid.json index 6a7c79d52..1a4fc2998 100644 --- a/production/mempool-frontend-config.liquid.json +++ b/production/mempool-frontend-config.liquid.json @@ -11,7 +11,6 @@ "LIQUID_WEBSITE_URL": "https://liquid.network", "BISQ_WEBSITE_URL": "https://bisq.markets", "ITEMS_PER_PAGE": 25, - "BLOCK_WEIGHT_UNITS": 300000, "MEMPOOL_BLOCKS_AMOUNT": 2, "KEEP_BLOCKS_AMOUNT": 16 } From f7ec5ca82ee95d0f9e5559a9f578ec463f77d083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Thu, 3 Aug 2023 17:47:30 +0200 Subject: [PATCH 02/69] Update liquid-master-page.component.html ngIf for BISQ_ENABLED was missing --- .../liquid-master-page/liquid-master-page.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/liquid-master-page/liquid-master-page.component.html b/frontend/src/app/components/liquid-master-page/liquid-master-page.component.html index 476c78622..50296b895 100644 --- a/frontend/src/app/components/liquid-master-page/liquid-master-page.component.html +++ b/frontend/src/app/components/liquid-master-page/liquid-master-page.component.html @@ -53,7 +53,7 @@ Signet Testnet - Bisq + Bisq Liquid Liquid Testnet @@ -98,4 +98,4 @@ - \ No newline at end of file + From b3f90e298127ad0199ca3c0312fb3022ecfd4a28 Mon Sep 17 00:00:00 2001 From: hunicus <93150691+hunicus@users.noreply.github.com> Date: Sun, 13 Aug 2023 08:23:57 +0900 Subject: [PATCH 03/69] Add mempool enterprise note to top-level readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dd2e62478..9bc988970 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,9 @@ It is an open-source project developed and operated for the benefit of the Bitco Mempool can be self-hosted on a wide variety of your own hardware, ranging from a simple one-click installation on a Raspberry Pi full-node distro all the way to a robust production instance on a powerful FreeBSD server. -**Most people should use a one-click install method.** Other install methods are meant for developers and others with experience managing servers. +Most people should use a one-click install method. + +Other install methods are meant for developers and others with experience managing servers. If you want support for your own production instance of Mempool, or if you'd like to have your own instance of Mempool run by the mempool.space team on their own global ISP infrastructure—check out Mempool Enterprise®. ## One-Click Installation From 102579baa972b80e08b637d034dfe54804cb7b9b Mon Sep 17 00:00:00 2001 From: hunicus <93150691+hunicus@users.noreply.github.com> Date: Sun, 13 Aug 2023 13:00:47 +0900 Subject: [PATCH 04/69] Add enterprise note to production readme --- production/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/production/README.md b/production/README.md index 87b8bb0a1..8e325bb1b 100644 --- a/production/README.md +++ b/production/README.md @@ -2,7 +2,9 @@ These instructions are for setting up a serious production Mempool website for Bitcoin (mainnet, testnet, signet), Liquid (mainnet, testnet), and Bisq. -Again, this setup is no joke—home users should use [one of the other installation methods](../#installation-methods). Support is only provided to [enterprise sponsors](https://mempool.space/enterprise). +Again, this setup is no joke—home users should use [one of the other installation methods](../#installation-methods). Support is only provided to project sponsors through [Mempool Enterprise®](https://mempool.space/enterprise). + +You can also have the mempool.space team run a highly-performant and highly-available instance of Mempool for you on their own global ISP infrastructure. See Mempool Enterprise® for more details. ### Server Hardware From 0a918b8fa84270754ee3afa47c5dd9acca4c54e7 Mon Sep 17 00:00:00 2001 From: hunicus <93150691+hunicus@users.noreply.github.com> Date: Sun, 13 Aug 2023 13:04:58 +0900 Subject: [PATCH 05/69] Add enterprise note to backend readme --- backend/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/README.md b/backend/README.md index 6a0cb821c..0582aca8c 100644 --- a/backend/README.md +++ b/backend/README.md @@ -2,7 +2,7 @@ These instructions are mostly intended for developers. -If you choose to use these instructions for a production setup, be aware that you will still probably need to do additional configuration for your specific OS, environment, use-case, etc. We do our best here to provide a good starting point, but only proceed if you know what you're doing. Mempool only provides support for custom setups to [enterprise sponsors](https://mempool.space/enterprise). +If you choose to use these instructions for a production setup, be aware that you will still probably need to do additional configuration for your specific OS, environment, use-case, etc. We do our best here to provide a good starting point, but only proceed if you know what you're doing. Mempool only provides support for custom setups to project sponsors through [Mempool Enterprise®](https://mempool.space/enterprise). See other ways to set up Mempool on [the main README](/../../#installation-methods). From 5ac432556b7a029689e5373e4b71dc2efa767e64 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Fri, 8 Dec 2023 17:00:36 +0100 Subject: [PATCH 06/69] [mining] show proper message when we have less than 2 weeks worth of mining pool hashrate data --- frontend/src/app/components/pool/pool.component.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index e52abe433..4457814c3 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -115,13 +115,13 @@ export class PoolComponent implements OnInit { prepareChartOptions(data) { let title: object; - if (data.length === 0) { + if (data.length <= 1) { title = { textStyle: { color: 'grey', fontSize: 15 }, - text: $localize`:@@23555386d8af1ff73f297e89dd4af3f4689fb9dd:Indexing blocks`, + text: $localize`Not enough data yet`, left: 'center', top: 'center' }; @@ -172,14 +172,14 @@ export class PoolComponent implements OnInit { `; }.bind(this) }, - xAxis: data.length === 0 ? undefined : { + xAxis: data.length <= 1 ? undefined : { type: 'time', splitNumber: (this.isMobile()) ? 5 : 10, axisLabel: { hideOverlap: true, } }, - yAxis: data.length === 0 ? undefined : [ + yAxis: data.length <= 1 ? undefined : [ { min: (value) => { return value.min * 0.9; @@ -198,7 +198,7 @@ export class PoolComponent implements OnInit { } }, ], - series: data.length === 0 ? undefined : [ + series: data.length <= 1 ? undefined : [ { zlevel: 0, name: 'Hashrate', @@ -211,7 +211,7 @@ export class PoolComponent implements OnInit { }, }, ], - dataZoom: data.length === 0 ? undefined : [{ + dataZoom: data.length <= 1 ? undefined : [{ type: 'inside', realtime: true, zoomLock: true, From a4810b8be0438f512336a9ab93450f755d33fc09 Mon Sep 17 00:00:00 2001 From: Armin Sabouri Date: Fri, 17 Nov 2023 15:23:52 -0500 Subject: [PATCH 07/69] fix: incorrect `HTTP_PORT` docker compose field in docker README.md The override is [BACKEND_HTTP_PORT defined here](https://github.com/mempool/mempool/blob/59c513f2a52a96d125944b42230e1fb7b13b130e/docker/backend/start.sh#L7) --- docker/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/README.md b/docker/README.md index 8dc1f264a..444324af8 100644 --- a/docker/README.md +++ b/docker/README.md @@ -124,7 +124,7 @@ Corresponding `docker-compose.yml` overrides: environment: MEMPOOL_NETWORK: "" MEMPOOL_BACKEND: "" - MEMPOOL_HTTP_PORT: "" + BACKEND_HTTP_PORT: "" MEMPOOL_SPAWN_CLUSTER_PROCS: "" MEMPOOL_API_URL_PREFIX: "" MEMPOOL_POLL_RATE_MS: "" From 88d2a3a50d35418bf05ab555c92f361bdbe271f0 Mon Sep 17 00:00:00 2001 From: Armin Sabouri Date: Fri, 17 Nov 2023 15:31:39 -0500 Subject: [PATCH 08/69] sign CLA for 0xBEEFCAF3 --- contributors/0xBEEFCAF3.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 contributors/0xBEEFCAF3.txt diff --git a/contributors/0xBEEFCAF3.txt b/contributors/0xBEEFCAF3.txt new file mode 100644 index 000000000..e00999be1 --- /dev/null +++ b/contributors/0xBEEFCAF3.txt @@ -0,0 +1,3 @@ +I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of November 17, 2023. + +Signed: 0xBEEFCAF3 From d3f7190b7f68b6a269eae48bdbc4debad4e986be Mon Sep 17 00:00:00 2001 From: softsimon Date: Tue, 12 Dec 2023 20:49:08 +0700 Subject: [PATCH 09/69] Acceleration preview loader --- .../accelerate-preview/accelerate-preview.component.html | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html index 455dffef4..2d2c9c3f3 100644 --- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html +++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.html @@ -25,7 +25,7 @@ > - +
@@ -243,4 +243,9 @@
-
\ No newline at end of file + + + +
+
+
\ No newline at end of file From 173bc127cb29e74468a85e9350eb426bd9554ad0 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 5 Dec 2023 06:54:31 +0000 Subject: [PATCH 10/69] Block viz filters proof of concept --- backend/src/api/common.ts | 107 +++++++++++++++++- backend/src/api/mempool-blocks.ts | 7 +- backend/src/api/mempool.ts | 3 + backend/src/mempool.interfaces.ts | 45 +++++++- .../block-overview-graph.component.ts | 9 ++ .../block-overview-graph/block-scene.ts | 17 ++- .../block-overview-graph/tx-view.ts | 4 +- .../block-overview-tooltip.component.ts | 2 +- .../mempool-block-view.component.html | 2 +- .../mempool-block-view.component.ts | 7 ++ .../mempool-block.component.html | 2 +- .../mempool-block/mempool-block.component.ts | 10 +- .../src/app/interfaces/node-api.interface.ts | 36 ++++++ .../src/app/interfaces/websocket.interface.ts | 1 + 14 files changed, 239 insertions(+), 13 deletions(-) diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index b6f8ab657..9bae2d906 100644 --- a/backend/src/api/common.ts +++ b/backend/src/api/common.ts @@ -1,9 +1,11 @@ import * as bitcoinjs from 'bitcoinjs-lib'; import { Request } from 'express'; -import { Ancestor, CpfpInfo, CpfpSummary, CpfpCluster, EffectiveFeeStats, MempoolBlockWithTransactions, TransactionExtended, MempoolTransactionExtended, TransactionStripped, WorkingEffectiveFeeStats } from '../mempool.interfaces'; +import { Ancestor, CpfpInfo, CpfpSummary, CpfpCluster, EffectiveFeeStats, MempoolBlockWithTransactions, TransactionExtended, MempoolTransactionExtended, TransactionStripped, WorkingEffectiveFeeStats, TransactionClassified, TransactionFlags } from '../mempool.interfaces'; import config from '../config'; import { NodeSocket } from '../repositories/NodesSocketsRepository'; import { isIP } from 'net'; +import rbfCache from './rbf-cache'; +import transactionUtils from './transaction-utils'; export class Common { static nativeAssetId = config.MEMPOOL.NETWORK === 'liquidtestnet' ? '144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49' @@ -138,6 +140,109 @@ export class Common { return matches; } + static setSighashFlags(flags: bigint, signature: string): bigint { + switch(signature.slice(-2)) { + case '01': return flags | TransactionFlags.sighash_all; + case '02': return flags | TransactionFlags.sighash_none; + case '03': return flags | TransactionFlags.sighash_single; + case '81': return flags | TransactionFlags.sighash_all | TransactionFlags.sighash_acp; + case '82': return flags | TransactionFlags.sighash_none | TransactionFlags.sighash_acp; + case '83': return flags | TransactionFlags.sighash_single | TransactionFlags.sighash_acp; + default: return flags | TransactionFlags.sighash_default; // taproot only + } + } + + static getTransactionFlags(tx: TransactionExtended): number { + let flags = 0n; + if (tx.version === 1) { + flags |= TransactionFlags.v1; + } else if (tx.version === 2) { + flags |= TransactionFlags.v2; + } + const inValues = {}; + const outValues = {}; + let rbf = false; + for (const vin of tx.vin) { + if (vin.sequence < 0xfffffffe) { + rbf = true; + } + switch (vin.prevout?.scriptpubkey_type) { + case 'p2pk': { + flags |= TransactionFlags.p2pk; + flags = this.setSighashFlags(flags, vin.scriptsig); + } break; + case 'multisig': flags |= TransactionFlags.p2ms; break; + case 'p2pkh': flags |= TransactionFlags.p2pkh; break; + case 'p2sh': flags |= TransactionFlags.p2sh; break; + case 'v0_p2wpkh': flags |= TransactionFlags.p2wpkh; break; + case 'v0_p2wsh': flags |= TransactionFlags.p2wsh; break; + case 'v1_p2tr': { + flags |= TransactionFlags.p2tr; + if (vin.witness.length > 2) { + const asm = vin.inner_witnessscript_asm || transactionUtils.convertScriptSigAsm(vin.witness[vin.witness.length - 2]); + if (asm?.includes('OP_0 OP_IF')) { + flags |= TransactionFlags.inscription; + } + } + } break; + } + inValues[vin.prevout?.value || Math.random()] = (inValues[vin.prevout?.value || Math.random()] || 0) + 1; + } + if (rbf) { + flags |= TransactionFlags.rbf; + } else { + flags |= TransactionFlags.no_rbf; + } + for (const vout of tx.vout) { + switch (vout.scriptpubkey_type) { + case 'p2pk': flags |= TransactionFlags.p2pk; break; + case 'multisig': { + flags |= TransactionFlags.p2ms; + // TODO - detect fake multisig data embedding + } break; + case 'p2pkh': flags |= TransactionFlags.p2pkh; break; + case 'p2sh': flags |= TransactionFlags.p2sh; break; + case 'v0_p2wpkh': flags |= TransactionFlags.p2wpkh; break; + case 'v0_p2wsh': flags |= TransactionFlags.p2wsh; break; + case 'v1_p2tr': flags |= TransactionFlags.p2tr; break; + case 'op_return': flags |= TransactionFlags.op_return; break; + } + outValues[vout.value || Math.random()] = (outValues[vout.value || Math.random()] || 0) + 1; + } + if (tx.ancestors?.length) { + flags |= TransactionFlags.cpfp_child; + } + if (tx.descendants?.length) { + flags |= TransactionFlags.cpfp_parent; + } + if (rbfCache.getRbfTree(tx.txid)) { + flags |= TransactionFlags.replacement; + } + // fast but bad heuristic to detect possible coinjoins + // (at least 5 inputs and 5 outputs, less than half of which are unique amounts) + if (tx.vin.length >= 5 && tx.vout.length >= 5 && (Object.keys(inValues).length + Object.keys(outValues).length) <= (tx.vin.length + tx.vout.length) / 2 ) { + flags |= TransactionFlags.coinjoin; + } + // more than 5:1 input:output ratio + if (tx.vin.length / tx.vout.length >= 5) { + flags |= TransactionFlags.consolidation; + } + // less than 1:5 input:output ratio + if (tx.vin.length / tx.vout.length <= 0.2) { + flags |= TransactionFlags.batch_payout; + } + + return Number(flags); + } + + static classifyTransaction(tx: TransactionExtended): TransactionClassified { + const flags = this.getTransactionFlags(tx); + return { + ...this.stripTransaction(tx), + flags, + }; + } + static stripTransaction(tx: TransactionExtended): TransactionStripped { return { txid: tx.txid, diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index 15f9b6cf7..a7f00f6e8 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -1,6 +1,6 @@ import { GbtGenerator, GbtResult, ThreadTransaction as RustThreadTransaction, ThreadAcceleration as RustThreadAcceleration } from 'rust-gbt'; import logger from '../logger'; -import { MempoolBlock, MempoolTransactionExtended, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor, CompactThreadTransaction, EffectiveFeeStats, PoolTag } from '../mempool.interfaces'; +import { MempoolBlock, MempoolTransactionExtended, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor, CompactThreadTransaction, EffectiveFeeStats, PoolTag, TransactionClassified } from '../mempool.interfaces'; import { Common, OnlineFeeStatsCalculator } from './common'; import config from '../config'; import { Worker } from 'worker_threads'; @@ -169,7 +169,7 @@ class MempoolBlocks { private calculateMempoolDeltas(prevBlocks: MempoolBlockWithTransactions[], mempoolBlocks: MempoolBlockWithTransactions[]): MempoolBlockDelta[] { const mempoolBlockDeltas: MempoolBlockDelta[] = []; for (let i = 0; i < Math.max(mempoolBlocks.length, prevBlocks.length); i++) { - let added: TransactionStripped[] = []; + let added: TransactionClassified[] = []; let removed: string[] = []; const changed: { txid: string, rate: number | undefined, acc: boolean | undefined }[] = []; if (mempoolBlocks[i] && !prevBlocks[i]) { @@ -582,6 +582,7 @@ class MempoolBlocks { const deltas = this.calculateMempoolDeltas(this.mempoolBlocks, mempoolBlocks); this.mempoolBlocks = mempoolBlocks; this.mempoolBlockDeltas = deltas; + } return mempoolBlocks; @@ -599,7 +600,7 @@ class MempoolBlocks { medianFee: feeStats.medianFee, // Common.percentile(transactions.map((tx) => tx.effectiveFeePerVsize), config.MEMPOOL.RECOMMENDED_FEE_PERCENTILE), feeRange: feeStats.feeRange, //Common.getFeesInRange(transactions, rangeLength), transactionIds: transactionIds, - transactions: transactions.map((tx) => Common.stripTransaction(tx)), + transactions: transactions.map((tx) => Common.classifyTransaction(tx)), }; } diff --git a/backend/src/api/mempool.ts b/backend/src/api/mempool.ts index fa13db418..a5bc8407a 100644 --- a/backend/src/api/mempool.ts +++ b/backend/src/api/mempool.ts @@ -100,6 +100,9 @@ class Mempool { if (this.mempoolCache[txid].order == null) { this.mempoolCache[txid].order = transactionUtils.txidToOrdering(txid); } + for (const vin of this.mempoolCache[txid].vin) { + transactionUtils.addInnerScriptsToVin(vin); + } count++; if (config.MEMPOOL.CACHE_ENABLED && config.REDIS.ENABLED) { await redisCache.$addTransaction(this.mempoolCache[txid]); diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index cb212512c..f50274304 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -61,13 +61,13 @@ export interface MempoolBlock { export interface MempoolBlockWithTransactions extends MempoolBlock { transactionIds: string[]; - transactions: TransactionStripped[]; + transactions: TransactionClassified[]; } export interface MempoolBlockDelta { - added: TransactionStripped[]; + added: TransactionClassified[]; removed: string[]; - changed: { txid: string, rate: number | undefined }[]; + changed: { txid: string, rate: number | undefined, flags?: number }[]; } interface VinStrippedToScriptsig { @@ -190,6 +190,45 @@ export interface TransactionStripped { rate?: number; // effective fee rate } +export interface TransactionClassified extends TransactionStripped { + flags: number; +} + +// binary flags for transaction classification +export const TransactionFlags = { + // features + rbf: 0b00000001n, + no_rbf: 0b00000010n, + v1: 0b00000100n, + v2: 0b00001000n, + // address types + p2pk: 0b00000001_00000000n, + p2ms: 0b00000010_00000000n, + p2pkh: 0b00000100_00000000n, + p2sh: 0b00001000_00000000n, + p2wpkh: 0b00010000_00000000n, + p2wsh: 0b00100000_00000000n, + p2tr: 0b01000000_00000000n, + // behavior + cpfp_parent: 0b00000001_00000000_00000000n, + cpfp_child: 0b00000010_00000000_00000000n, + replacement: 0b00000100_00000000_00000000n, + // data + op_return: 0b00000001_00000000_00000000_00000000n, + fake_multisig: 0b00000010_00000000_00000000_00000000n, + inscription: 0b00000100_00000000_00000000_00000000n, + // heuristics + coinjoin: 0b00000001_00000000_00000000_00000000_00000000n, + consolidation: 0b00000010_00000000_00000000_00000000_00000000n, + batch_payout: 0b00000100_00000000_00000000_00000000_00000000n, + // sighash + sighash_all: 0b00000001_00000000_00000000_00000000_00000000_00000000n, + sighash_none: 0b00000010_00000000_00000000_00000000_00000000_00000000n, + sighash_single: 0b00000100_00000000_00000000_00000000_00000000_00000000n, + sighash_default:0b00001000_00000000_00000000_00000000_00000000_00000000n, + sighash_acp: 0b00010000_00000000_00000000_00000000_00000000_00000000n, +}; + export interface BlockExtension { totalFees: number; medianFee: number; // median fee rate 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 1fc173a2d..5eaee25a1 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 @@ -26,6 +26,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On @Input() mirrorTxid: string | void; @Input() unavailable: boolean = false; @Input() auditHighlighting: boolean = false; + @Input() filterFlags: bigint | null = 0b00000100_00000000_00000000_00000000n; @Input() blockConversion: Price; @Input() overrideColors: ((tx: TxView) => Color) | null = null; @Output() txClickEvent = new EventEmitter<{ tx: TransactionStripped, keyModifier: boolean}>(); @@ -462,6 +463,14 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On } } + setFilterFlags(flags: bigint | null): void { + if (this.scene) { + console.log('setting filter flags to ', this.filterFlags.toString(2)); + this.scene.setFilterFlags(flags); + this.start(); + } + } + onTxClick(cssX: number, cssY: number, keyModifier: boolean = false) { const x = cssX * window.devicePixelRatio; const y = cssY * window.devicePixelRatio; 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 77b7c2e05..b6cf0ce59 100644 --- a/frontend/src/app/components/block-overview-graph/block-scene.ts +++ b/frontend/src/app/components/block-overview-graph/block-scene.ts @@ -27,6 +27,7 @@ export default class BlockScene { configAnimationOffset: number | null; animationOffset: number; highlightingEnabled: boolean; + filterFlags: bigint | null = 0b00000100_00000000_00000000_00000000n; width: number; height: number; gridWidth: number; @@ -277,6 +278,20 @@ export default class BlockScene { this.animateUntil = Math.max(this.animateUntil, tx.update(update)); } + private updateTxColor(tx: TxView, startTime: number, delay: number, animate: boolean = true, duration?: number): void { + if (tx.dirty || this.dirty) { + const txColor = tx.getColor(); + this.applyTxUpdate(tx, { + display: { + color: txColor + }, + duration: animate ? (duration || this.animationDuration) : 1, + start: startTime, + delay: animate ? delay : 0, + }); + } + } + private updateTx(tx: TxView, startTime: number, delay: number, direction: string = 'left', animate: boolean = true): void { if (tx.dirty || this.dirty) { this.saveGridToScreenPosition(tx); @@ -325,7 +340,7 @@ export default class BlockScene { } else { this.applyTxUpdate(tx, { display: { - position: tx.screenPosition + position: tx.screenPosition, }, duration: animate ? this.animationDuration : 0, minDuration: animate ? (this.animationDuration / 2) : 0, 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 4e2d855e6..da36b9880 100644 --- a/frontend/src/app/components/block-overview-graph/tx-view.ts +++ b/frontend/src/app/components/block-overview-graph/tx-view.ts @@ -1,9 +1,9 @@ import TxSprite from './tx-sprite'; import { FastVertexArray } from './fast-vertex-array'; -import { TransactionStripped } from '../../interfaces/websocket.interface'; import { SpriteUpdateParams, Square, Color, ViewUpdateParams } from './sprite-types'; import { hexToColor } from './utils'; import BlockScene from './block-scene'; +import { TransactionStripped } from '../../interfaces/node-api.interface'; const hoverTransitionTime = 300; const defaultHoverColor = hexToColor('1bd8f4'); @@ -29,6 +29,7 @@ export default class TxView implements TransactionStripped { feerate: number; acc?: boolean; rate?: number; + bigintFlags?: bigint | null = 0b00000100_00000000_00000000_00000000n; status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'rbf' | 'accelerated'; context?: 'projected' | 'actual'; scene?: BlockScene; @@ -57,6 +58,7 @@ export default class TxView implements TransactionStripped { this.acc = tx.acc; this.rate = tx.rate; this.status = tx.status; + this.bigintFlags = tx.flags ? BigInt(tx.flags) : 0n; this.initialised = false; this.vertexArray = scene.vertexArray; 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 65d0f984c..a6e2a2697 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,7 +1,7 @@ import { Component, ElementRef, ViewChild, Input, OnChanges, ChangeDetectionStrategy } from '@angular/core'; -import { TransactionStripped } from '../../interfaces/websocket.interface'; import { Position } from '../../components/block-overview-graph/sprite-types.js'; import { Price } from '../../services/price.service'; +import { TransactionStripped } from '../../interfaces/node-api.interface.js'; @Component({ selector: 'app-block-overview-tooltip', diff --git a/frontend/src/app/components/mempool-block-view/mempool-block-view.component.html b/frontend/src/app/components/mempool-block-view/mempool-block-view.component.html index 9d51ff4e9..33ccf439b 100644 --- a/frontend/src/app/components/mempool-block-view/mempool-block-view.component.html +++ b/frontend/src/app/components/mempool-block-view/mempool-block-view.component.html @@ -1,5 +1,5 @@
- +
\ No newline at end of file diff --git a/frontend/src/app/components/mempool-block-view/mempool-block-view.component.ts b/frontend/src/app/components/mempool-block-view/mempool-block-view.component.ts index ebeb0801c..a671033cf 100644 --- a/frontend/src/app/components/mempool-block-view/mempool-block-view.component.ts +++ b/frontend/src/app/components/mempool-block-view/mempool-block-view.component.ts @@ -27,6 +27,7 @@ export class MempoolBlockViewComponent implements OnInit, OnDestroy { autofit: boolean = false; resolution: number = 80; index: number = 0; + filterFlags: bigint | null = 0n; routeParamsSubscription: Subscription; queryParamsSubscription: Subscription; @@ -38,6 +39,8 @@ export class MempoolBlockViewComponent implements OnInit, OnDestroy { ) { } ngOnInit(): void { + window['setFlags'] = this.setFilterFlags.bind(this); + this.websocketService.want(['blocks', 'mempool-blocks']); this.routeParamsSubscription = this.route.paramMap @@ -82,4 +85,8 @@ export class MempoolBlockViewComponent implements OnInit, OnDestroy { this.routeParamsSubscription.unsubscribe(); this.queryParamsSubscription.unsubscribe(); } + + setFilterFlags(flags: bigint | null) { + this.filterFlags = flags; + } } diff --git a/frontend/src/app/components/mempool-block/mempool-block.component.html b/frontend/src/app/components/mempool-block/mempool-block.component.html index b089a6d74..af9225fe6 100644 --- a/frontend/src/app/components/mempool-block/mempool-block.component.html +++ b/frontend/src/app/components/mempool-block/mempool-block.component.html @@ -46,7 +46,7 @@
- +
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 c11bedacd..4b8d4de66 100644 --- a/frontend/src/app/components/mempool-block/mempool-block.component.ts +++ b/frontend/src/app/components/mempool-block/mempool-block.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, OnDestroy, ChangeDetectionStrategy } from '@angular/core'; +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; import { StateService } from '../../services/state.service'; import { ActivatedRoute, ParamMap } from '@angular/router'; import { switchMap, map, tap, filter } from 'rxjs/operators'; @@ -21,6 +21,7 @@ export class MempoolBlockComponent implements OnInit, OnDestroy { mempoolBlockTransactions$: Observable; ordinal$: BehaviorSubject = new BehaviorSubject(''); previewTx: TransactionStripped | void; + filterFlags: bigint | null = 0n; webGlEnabled: boolean; constructor( @@ -28,11 +29,13 @@ export class MempoolBlockComponent implements OnInit, OnDestroy { public stateService: StateService, private seoService: SeoService, private websocketService: WebsocketService, + private cd: ChangeDetectorRef, ) { this.webGlEnabled = detectWebGL(); } ngOnInit(): void { + window['setFlags'] = this.setFilterFlags.bind(this); this.websocketService.want(['blocks', 'mempool-blocks']); this.mempoolBlock$ = this.route.paramMap @@ -89,6 +92,11 @@ export class MempoolBlockComponent implements OnInit, OnDestroy { setTxPreview(event: TransactionStripped | void): void { this.previewTx = event; } + + setFilterFlags(flags: bigint | null) { + this.filterFlags = flags; + this.cd.markForCheck(); + } } function detectWebGL() { diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index 862272330..3075491a8 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -180,10 +180,46 @@ export interface TransactionStripped { value: number; rate?: number; // effective fee rate acc?: boolean; + flags?: number | null; status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'rbf' | 'accelerated'; context?: 'projected' | 'actual'; } +// binary flags for transaction classification +export const TransactionFlags = { + // features + rbf: 0b00000001n, + no_rbf: 0b00000010n, + v1: 0b00000100n, + v2: 0b00001000n, + // address types + p2pk: 0b00000001_00000000n, + p2ms: 0b00000010_00000000n, + p2pkh: 0b00000100_00000000n, + p2sh: 0b00001000_00000000n, + p2wpkh: 0b00010000_00000000n, + p2wsh: 0b00100000_00000000n, + p2tr: 0b01000000_00000000n, + // behavior + cpfp_parent: 0b00000001_00000000_00000000n, + cpfp_child: 0b00000010_00000000_00000000n, + replacement: 0b00000100_00000000_00000000n, + // data + op_return: 0b00000001_00000000_00000000_00000000n, + fake_multisig: 0b00000010_00000000_00000000_00000000n, + inscription: 0b00000100_00000000_00000000_00000000n, + // heuristics + coinjoin: 0b00000001_00000000_00000000_00000000_00000000n, + consolidation: 0b00000010_00000000_00000000_00000000_00000000n, + batch_payout: 0b00000100_00000000_00000000_00000000_00000000n, + // sighash + sighash_all: 0b00000001_00000000_00000000_00000000_00000000_00000000n, + sighash_none: 0b00000010_00000000_00000000_00000000_00000000_00000000n, + sighash_single: 0b00000100_00000000_00000000_00000000_00000000_00000000n, + sighash_default:0b00001000_00000000_00000000_00000000_00000000_00000000n, + sighash_acp: 0b00010000_00000000_00000000_00000000_00000000_00000000n, +}; + export interface RbfTransaction extends TransactionStripped { rbf?: boolean; mined?: boolean, diff --git a/frontend/src/app/interfaces/websocket.interface.ts b/frontend/src/app/interfaces/websocket.interface.ts index 1d0414de7..20bc42bde 100644 --- a/frontend/src/app/interfaces/websocket.interface.ts +++ b/frontend/src/app/interfaces/websocket.interface.ts @@ -90,6 +90,7 @@ export interface TransactionStripped { value: number; acc?: boolean; // is accelerated? rate?: number; // effective fee rate + flags?: number; status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'rbf' | 'accelerated'; context?: 'projected' | 'actual'; } From e12f43e741665c282fb3d1bf3a702c351ad76cfb Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 13 Dec 2023 10:56:33 +0000 Subject: [PATCH 11/69] Add sighash filter flags --- backend/src/api/common.ts | 88 +++++++++++++++++++++++++++++++++++---- 1 file changed, 81 insertions(+), 7 deletions(-) diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index 9bae2d906..00f4328ce 100644 --- a/backend/src/api/common.ts +++ b/backend/src/api/common.ts @@ -140,6 +140,65 @@ export class Common { return matches; } + static setSchnorrSighashFlags(flags: bigint, witness: string[]): bigint { + // no witness items + if (!witness?.length) { + return flags; + } + const hasAnnex = witness.length > 1 && witness[witness.length - 1].startsWith('50'); + if (witness?.length === (hasAnnex ? 2 : 1)) { + // keypath spend, signature is the only witness item + if (witness[0].length === 130) { + flags |= this.setSighashFlags(flags, witness[0]); + } else { + flags |= TransactionFlags.sighash_default; + } + } else { + // scriptpath spend, all items except for the script, control block and annex could be signatures + for (let i = 0; i < witness.length - (hasAnnex ? 3 : 2); i++) { + // handle probable signatures + if (witness[i].length === 130) { + flags |= this.setSighashFlags(flags, witness[i]); + } else if (witness[i].length === 128) { + flags |= TransactionFlags.sighash_default; + } + } + } + return flags; + } + + static isDERSig(w: string): boolean { + // heuristic to detect probable DER signatures + return (w.length >= 18 + && w.startsWith('30') // minimum DER signature length is 8 bytes + sighash flag (see https://mempool.space/testnet/tx/c6c232a36395fa338da458b86ff1327395a9afc28c5d2daa4273e410089fd433) + && ['01, 02, 03, 81, 82, 83'].includes(w.slice(-2)) // signature must end with a valid sighash flag + && (w.length === parseInt(w.slice(2, 4), 16) + 6) // second byte encodes the combined length of the R and S components + ); + } + + static setSegwitSighashFlags(flags: bigint, witness: string[]): bigint { + for (const w of witness) { + if (this.isDERSig(w)) { + flags |= this.setSighashFlags(flags, w); + } + } + return flags; + } + + static setLegacySighashFlags(flags: bigint, scriptsig_asm: string): bigint { + for (const item of scriptsig_asm.split(' ')) { + // skip op_codes + if (item.startsWith('OP_')) { + continue; + } + // check pushed data + if (this.isDERSig(item)) { + flags |= this.setSighashFlags(flags, item); + } + } + return flags; + } + static setSighashFlags(flags: bigint, signature: string): bigint { switch(signature.slice(-2)) { case '01': return flags | TransactionFlags.sighash_all; @@ -159,18 +218,16 @@ export class Common { } else if (tx.version === 2) { flags |= TransactionFlags.v2; } + const reusedAddresses: { [address: string ]: number } = {}; const inValues = {}; const outValues = {}; let rbf = false; for (const vin of tx.vin) { if (vin.sequence < 0xfffffffe) { - rbf = true; + rbf = true; } switch (vin.prevout?.scriptpubkey_type) { - case 'p2pk': { - flags |= TransactionFlags.p2pk; - flags = this.setSighashFlags(flags, vin.scriptsig); - } break; + case 'p2pk': flags |= TransactionFlags.p2pk; break; case 'multisig': flags |= TransactionFlags.p2ms; break; case 'p2pkh': flags |= TransactionFlags.p2pkh; break; case 'p2sh': flags |= TransactionFlags.p2sh; break; @@ -186,6 +243,19 @@ export class Common { } } break; } + + // sighash flags + if (vin.prevout?.scriptpubkey_type === 'v1_p2tr') { + flags |= this.setSchnorrSighashFlags(flags, vin.witness); + } else if (vin.witness) { + flags |= this.setSegwitSighashFlags(flags, vin.witness); + } else if (vin.scriptsig_asm) { + flags |= this.setLegacySighashFlags(flags, vin.scriptsig_asm); + } + + if (vin.prevout?.scriptpubkey_address) { + reusedAddresses[vin.prevout?.scriptpubkey_address] = (reusedAddresses[vin.prevout?.scriptpubkey_address] || 0) + 1; + } inValues[vin.prevout?.value || Math.random()] = (inValues[vin.prevout?.value || Math.random()] || 0) + 1; } if (rbf) { @@ -207,6 +277,9 @@ export class Common { case 'v1_p2tr': flags |= TransactionFlags.p2tr; break; case 'op_return': flags |= TransactionFlags.op_return; break; } + if (vout.scriptpubkey_address) { + reusedAddresses[vout.scriptpubkey_address] = (reusedAddresses[vout.scriptpubkey_address] || 0) + 1; + } outValues[vout.value || Math.random()] = (outValues[vout.value || Math.random()] || 0) + 1; } if (tx.ancestors?.length) { @@ -219,8 +292,9 @@ export class Common { flags |= TransactionFlags.replacement; } // fast but bad heuristic to detect possible coinjoins - // (at least 5 inputs and 5 outputs, less than half of which are unique amounts) - if (tx.vin.length >= 5 && tx.vout.length >= 5 && (Object.keys(inValues).length + Object.keys(outValues).length) <= (tx.vin.length + tx.vout.length) / 2 ) { + // (at least 5 inputs and 5 outputs, less than half of which are unique amounts, with no address reuse) + const addressReuse = Object.values(reusedAddresses).reduce((acc, count) => Math.max(acc, count), 0) > 1; + if (!addressReuse && tx.vin.length >= 5 && tx.vout.length >= 5 && (Object.keys(inValues).length + Object.keys(outValues).length) <= (tx.vin.length + tx.vout.length) / 2 ) { flags |= TransactionFlags.coinjoin; } // more than 5:1 input:output ratio From 24dbe5d4ee45bf6fe7701d848c317392606660e3 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 13 Dec 2023 10:59:28 +0000 Subject: [PATCH 12/69] Add block viz filter UI --- .../block-filters.component.html | 22 ++++ .../block-filters.component.scss | 104 ++++++++++++++++++ .../block-filters/block-filters.component.ts | 65 +++++++++++ .../block-overview-graph.component.html | 1 + .../block-overview-graph.component.ts | 55 +++++++-- .../block-overview-graph/block-scene.ts | 63 +---------- .../components/block-overview-graph/utils.ts | 72 ++++++++++++ .../mempool-block-overview.component.html | 1 + .../mempool-block-overview.component.ts | 1 + .../mempool-block-view.component.html | 2 +- .../mempool-block.component.html | 4 +- .../mempool-block/mempool-block.component.ts | 7 -- .../src/app/interfaces/node-api.interface.ts | 35 ------ frontend/src/app/shared/filters.utils.ts | 87 +++++++++++++++ frontend/src/app/shared/shared.module.ts | 3 + 15 files changed, 408 insertions(+), 114 deletions(-) create mode 100644 frontend/src/app/components/block-filters/block-filters.component.html create mode 100644 frontend/src/app/components/block-filters/block-filters.component.scss create mode 100644 frontend/src/app/components/block-filters/block-filters.component.ts create mode 100644 frontend/src/app/shared/filters.utils.ts diff --git a/frontend/src/app/components/block-filters/block-filters.component.html b/frontend/src/app/components/block-filters/block-filters.component.html new file mode 100644 index 000000000..ff86e6b3b --- /dev/null +++ b/frontend/src/app/components/block-filters/block-filters.component.html @@ -0,0 +1,22 @@ +
+
+ +
+ + + +
+
+
+ +
{{ group.label }}
+
+ + + +
+
+
+
\ No newline at end of file diff --git a/frontend/src/app/components/block-filters/block-filters.component.scss b/frontend/src/app/components/block-filters/block-filters.component.scss new file mode 100644 index 000000000..ee9e7f4d3 --- /dev/null +++ b/frontend/src/app/components/block-filters/block-filters.component.scss @@ -0,0 +1,104 @@ +.block-filters { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + padding: 1em; + z-index: 10; + pointer-events: none; + + .filter-bar, .active-tags { + display: flex; + flex-direction: row; + align-items: center; + } + + .active-tags { + flex-wrap: wrap; + row-gap: 0.25em; + margin-left: 0.5em; + } + + .menu-toggle { + opacity: 0; + cursor: pointer; + color: white; + background: none; + border: solid 2px white; + border-radius: 0.35em; + pointer-events: all; + } + + .filter-menu { + h5 { + font-size: 0.8rem; + color: white; + margin: 0; + margin-top: 0.5em; + } + } + + .filter-group { + display: flex; + flex-direction: row; + flex-wrap: wrap; + row-gap: 0.25em; + margin-bottom: 0.5em; + } + + .filter-tag { + font-size: 0.9em; + background: #181b2daf; + border: solid 1px #105fb0; + color: white; + border-radius: 0.2rem; + padding: 0.2em 0.5em; + transition: background-color 300ms; + margin-right: 0.25em; + pointer-events: all; + + &.selected { + background-color: #105fb0; + } + } + + :host-context(.block-overview-graph:hover) &, &:hover, &:active { + .menu-toggle { + opacity: 0.5; + background: #181b2d; + + &:hover { + opacity: 1; + background: #181b2d7f; + } + } + + &.menu-open, &.filters-active { + .menu-toggle { + opacity: 1; + background: none; + + &:hover { + background: #181b2d7f; + } + } + } + } + + &.menu-open, &.filters-active { + .menu-toggle { + opacity: 1; + background: none; + + &:hover { + background: #181b2d7f; + } + } + } + + &.menu-open { + pointer-events: all; + background: #181b2d7f; + } +} \ No newline at end of file diff --git a/frontend/src/app/components/block-filters/block-filters.component.ts b/frontend/src/app/components/block-filters/block-filters.component.ts new file mode 100644 index 000000000..fc154bb69 --- /dev/null +++ b/frontend/src/app/components/block-filters/block-filters.component.ts @@ -0,0 +1,65 @@ +import { Component, OnChanges, EventEmitter, Output, SimpleChanges, HostListener } from '@angular/core'; +import { FilterGroups, TransactionFilters, Filter, TransactionFlags } from '../../shared/filters.utils'; + + +@Component({ + selector: 'app-block-filters', + templateUrl: './block-filters.component.html', + styleUrls: ['./block-filters.component.scss'], +}) +export class BlockFiltersComponent implements OnChanges { + @Output() onFilterChanged: EventEmitter = new EventEmitter(); + + filters = TransactionFilters; + filterGroups = FilterGroups; + activeFilters: string[] = []; + filterFlags: { [key: string]: boolean } = {}; + menuOpen: boolean = false; + + constructor() {} + + ngOnChanges(changes: SimpleChanges): void { + + } + + toggleFilter(key): void { + const filter = this.filters[key]; + this.filterFlags[key] = !this.filterFlags[key]; + if (this.filterFlags[key]) { + // remove any other flags in the same toggle group + if (filter.toggle) { + this.activeFilters.forEach(f => { + if (this.filters[f].toggle === filter.toggle) { + this.filterFlags[f] = false; + } + }); + this.activeFilters = this.activeFilters.filter(f => this.filters[f].toggle !== filter.toggle); + } + // add new active filter + this.activeFilters.push(key); + } else { + // remove active filter + this.activeFilters = this.activeFilters.filter(f => f != key); + } + this.onFilterChanged.emit(this.getBooleanFlags()); + } + + getBooleanFlags(): bigint | null { + let flags = 0n; + for (const key of Object.keys(this.filterFlags)) { + if (this.filterFlags[key]) { + flags |= this.filters[key].flag; + } + } + return flags || null; + } + + @HostListener('document:click', ['$event']) + onClick(event): boolean { + // click away from menu + if (!event.target.closest('button')) { + this.menuOpen = false; + } + return true; + } +} \ No newline at end of file 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 a625a0385..9f7408323 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 @@ -13,5 +13,6 @@ [auditEnabled]="auditHighlighting" [blockConversion]="blockConversion" > + 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 5eaee25a1..716ba540e 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 @@ -8,6 +8,19 @@ import { Color, Position } from './sprite-types'; import { Price } from '../../services/price.service'; import { StateService } from '../../services/state.service'; import { Subscription } from 'rxjs'; +import { defaultColorFunction, setOpacity, defaultFeeColors, defaultAuditFeeColors, defaultMarginalFeeColors, defaultAuditColors } from './utils'; + +const unmatchedOpacity = 0.2; +const unmatchedFeeColors = defaultFeeColors.map(c => setOpacity(c, unmatchedOpacity)); +const unmatchedAuditFeeColors = defaultAuditFeeColors.map(c => setOpacity(c, unmatchedOpacity)); +const unmatchedMarginalFeeColors = defaultMarginalFeeColors.map(c => setOpacity(c, unmatchedOpacity)); +const unmatchedAuditColors = { + censored: setOpacity(defaultAuditColors.censored, unmatchedOpacity), + missing: setOpacity(defaultAuditColors.missing, unmatchedOpacity), + added: setOpacity(defaultAuditColors.added, unmatchedOpacity), + selected: setOpacity(defaultAuditColors.selected, unmatchedOpacity), + accelerated: setOpacity(defaultAuditColors.accelerated, unmatchedOpacity), +}; @Component({ selector: 'app-block-overview-graph', @@ -26,7 +39,8 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On @Input() mirrorTxid: string | void; @Input() unavailable: boolean = false; @Input() auditHighlighting: boolean = false; - @Input() filterFlags: bigint | null = 0b00000100_00000000_00000000_00000000n; + @Input() showFilters: boolean = false; + @Input() filterFlags: bigint | null = null; @Input() blockConversion: Price; @Input() overrideColors: ((tx: TxView) => Color) | null = null; @Output() txClickEvent = new EventEmitter<{ tx: TransactionStripped, keyModifier: boolean}>(); @@ -93,7 +107,18 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On if (changes.auditHighlighting) { this.setHighlightingEnabled(this.auditHighlighting); } - if (changes.overrideColor) { + if (changes.overrideColor && this.scene) { + this.scene.setColorFunction(this.overrideColors); + } + if ((changes.filterFlags || changes.showFilters) && this.scene) { + this.setFilterFlags(this.filterFlags); + } + } + + setFilterFlags(flags: bigint | null): void { + if (flags != null) { + this.scene.setColorFunction(this.getFilterColorFunction(flags)); + } else { this.scene.setColorFunction(this.overrideColors); } } @@ -375,6 +400,8 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On onPointerMove(event) { if (event.target === this.canvas.nativeElement) { this.setPreviewTx(event.offsetX, event.offsetY, false); + } else { + this.onPointerLeave(event); } } @@ -463,14 +490,6 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On } } - setFilterFlags(flags: bigint | null): void { - if (this.scene) { - console.log('setting filter flags to ', this.filterFlags.toString(2)); - this.scene.setFilterFlags(flags); - this.start(); - } - } - onTxClick(cssX: number, cssY: number, keyModifier: boolean = false) { const x = cssX * window.devicePixelRatio; const y = cssY * window.devicePixelRatio; @@ -483,6 +502,22 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On onTxHover(hoverId: string) { this.txHoverEvent.emit(hoverId); } + + getFilterColorFunction(flags: bigint): ((tx: TxView) => Color) { + return (tx: TxView) => { + if ((tx.bigintFlags & flags) === flags) { + return defaultColorFunction(tx); + } else { + return defaultColorFunction( + tx, + unmatchedFeeColors, + unmatchedAuditFeeColors, + unmatchedMarginalFeeColors, + unmatchedAuditColors + ); + } + }; + } } // WebGL shader attributes 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 b6cf0ce59..cb589527d 100644 --- a/frontend/src/app/components/block-overview-graph/block-scene.ts +++ b/frontend/src/app/components/block-overview-graph/block-scene.ts @@ -2,19 +2,7 @@ import { FastVertexArray } from './fast-vertex-array'; import TxView from './tx-view'; import { TransactionStripped } from '../../interfaces/websocket.interface'; import { Color, Position, Square, ViewUpdateParams } from './sprite-types'; -import { feeLevels, mempoolFeeColors } from '../../app.constants'; -import { darken, desaturate, hexToColor } from './utils'; - -const feeColors = mempoolFeeColors.map(hexToColor); -const auditFeeColors = feeColors.map((color) => darken(desaturate(color, 0.3), 0.9)); -const marginalFeeColors = feeColors.map((color) => darken(desaturate(color, 0.8), 1.1)); -const auditColors = { - censored: hexToColor('f344df'), - missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7), - added: hexToColor('0099ff'), - selected: darken(desaturate(hexToColor('0099ff'), 0.3), 0.7), - accelerated: hexToColor('8F5FF6'), -}; +import { defaultColorFunction } from './utils'; export default class BlockScene { scene: { count: number, offset: { x: number, y: number}}; @@ -79,7 +67,7 @@ export default class BlockScene { } setColorFunction(colorFunction: ((tx: TxView) => Color) | null): void { - this.getColor = colorFunction; + this.getColor = colorFunction || defaultColorFunction; this.dirty = true; if (this.initialised && this.scene) { this.updateColors(performance.now(), 50); @@ -280,7 +268,7 @@ export default class BlockScene { private updateTxColor(tx: TxView, startTime: number, delay: number, animate: boolean = true, duration?: number): void { if (tx.dirty || this.dirty) { - const txColor = tx.getColor(); + const txColor = this.getColor(tx); this.applyTxUpdate(tx, { display: { color: txColor @@ -918,49 +906,4 @@ class BlockLayout { function feeRateDescending(a: TxView, b: TxView) { return b.feerate - a.feerate; -} - -function defaultColorFunction(tx: TxView): Color { - const rate = tx.fee / tx.vsize; // color by simple single-tx fee rate - const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1; - const feeLevelColor = feeColors[feeLevelIndex] || feeColors[mempoolFeeColors.length - 1]; - // Normal mode - if (!tx.scene?.highlightingEnabled) { - if (tx.acc) { - return auditColors.accelerated; - } else { - return feeLevelColor; - } - return feeLevelColor; - } - // Block audit - switch(tx.status) { - case 'censored': - return auditColors.censored; - case 'missing': - case 'sigop': - case 'rbf': - return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1]; - case 'fresh': - case 'freshcpfp': - return auditColors.missing; - case 'added': - return auditColors.added; - case 'selected': - return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1]; - case 'accelerated': - return auditColors.accelerated; - case 'found': - if (tx.context === 'projected') { - return auditFeeColors[feeLevelIndex] || auditFeeColors[mempoolFeeColors.length - 1]; - } else { - return feeLevelColor; - } - default: - if (tx.acc) { - return auditColors.accelerated; - } else { - return feeLevelColor; - } - } } \ No newline at end of file diff --git a/frontend/src/app/components/block-overview-graph/utils.ts b/frontend/src/app/components/block-overview-graph/utils.ts index a0bb8e868..9c800ad85 100644 --- a/frontend/src/app/components/block-overview-graph/utils.ts +++ b/frontend/src/app/components/block-overview-graph/utils.ts @@ -1,4 +1,6 @@ +import { feeLevels, mempoolFeeColors } from '../../app.constants'; import { Color } from './sprite-types'; +import TxView from './tx-view'; export function hexToColor(hex: string): Color { return { @@ -25,5 +27,75 @@ export function darken(color: Color, amount: number): Color { g: color.g * amount, b: color.b * amount, a: color.a, + }; +} + +export function setOpacity(color: Color, opacity: number): Color { + return { + ...color, + a: opacity + }; +} + +// precomputed colors +export const defaultFeeColors = mempoolFeeColors.map(hexToColor); +export const defaultAuditFeeColors = defaultFeeColors.map((color) => darken(desaturate(color, 0.3), 0.9)); +export const defaultMarginalFeeColors = defaultFeeColors.map((color) => darken(desaturate(color, 0.8), 1.1)); +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), + accelerated: hexToColor('8F5FF6'), +}; + +export function defaultColorFunction( + tx: TxView, + feeColors: Color[] = defaultFeeColors, + auditFeeColors: Color[] = defaultAuditFeeColors, + marginalFeeColors: Color[] = defaultMarginalFeeColors, + auditColors: { [status: string]: Color } = defaultAuditColors +): Color { + const rate = tx.fee / tx.vsize; // color by simple single-tx fee rate + const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1; + const feeLevelColor = feeColors[feeLevelIndex] || feeColors[mempoolFeeColors.length - 1]; + // Normal mode + if (!tx.scene?.highlightingEnabled) { + if (tx.acc) { + return auditColors.accelerated; + } else { + return feeLevelColor; + } + return feeLevelColor; + } + // Block audit + switch(tx.status) { + case 'censored': + return auditColors.censored; + case 'missing': + case 'sigop': + case 'rbf': + return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1]; + case 'fresh': + case 'freshcpfp': + return auditColors.missing; + case 'added': + return auditColors.added; + case 'selected': + return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1]; + case 'accelerated': + return auditColors.accelerated; + case 'found': + if (tx.context === 'projected') { + return auditFeeColors[feeLevelIndex] || auditFeeColors[mempoolFeeColors.length - 1]; + } else { + return feeLevelColor; + } + default: + if (tx.acc) { + return auditColors.accelerated; + } else { + return feeLevelColor; + } } } \ No newline at end of file diff --git a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.html b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.html index 1e0cba48c..85e7eebb1 100644 --- a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.html +++ b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.html @@ -5,6 +5,7 @@ [blockLimit]="stateService.blockVSize" [orientation]="timeLtr ? 'right' : 'left'" [flip]="true" + [showFilters]="showFilters" [overrideColors]="overrideColors" (txClickEvent)="onTxClick($event)" > 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 09eac989e..4beda043a 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 @@ -18,6 +18,7 @@ import TxView from '../block-overview-graph/tx-view'; }) export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChanges, AfterViewInit { @Input() index: number; + @Input() showFilters: boolean = false; @Input() overrideColors: ((tx: TxView) => Color) | null = null; @Output() txPreviewEvent = new EventEmitter(); diff --git a/frontend/src/app/components/mempool-block-view/mempool-block-view.component.html b/frontend/src/app/components/mempool-block-view/mempool-block-view.component.html index 33ccf439b..2fafb31cd 100644 --- a/frontend/src/app/components/mempool-block-view/mempool-block-view.component.html +++ b/frontend/src/app/components/mempool-block-view/mempool-block-view.component.html @@ -1,5 +1,5 @@
- +
\ No newline at end of file diff --git a/frontend/src/app/components/mempool-block/mempool-block.component.html b/frontend/src/app/components/mempool-block/mempool-block.component.html index af9225fe6..d2aa1aed2 100644 --- a/frontend/src/app/components/mempool-block/mempool-block.component.html +++ b/frontend/src/app/components/mempool-block/mempool-block.component.html @@ -46,7 +46,9 @@
- +
+ +
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 4b8d4de66..bb6e7791f 100644 --- a/frontend/src/app/components/mempool-block/mempool-block.component.ts +++ b/frontend/src/app/components/mempool-block/mempool-block.component.ts @@ -21,7 +21,6 @@ export class MempoolBlockComponent implements OnInit, OnDestroy { mempoolBlockTransactions$: Observable; ordinal$: BehaviorSubject = new BehaviorSubject(''); previewTx: TransactionStripped | void; - filterFlags: bigint | null = 0n; webGlEnabled: boolean; constructor( @@ -35,7 +34,6 @@ export class MempoolBlockComponent implements OnInit, OnDestroy { } ngOnInit(): void { - window['setFlags'] = this.setFilterFlags.bind(this); this.websocketService.want(['blocks', 'mempool-blocks']); this.mempoolBlock$ = this.route.paramMap @@ -92,11 +90,6 @@ export class MempoolBlockComponent implements OnInit, OnDestroy { setTxPreview(event: TransactionStripped | void): void { this.previewTx = event; } - - setFilterFlags(flags: bigint | null) { - this.filterFlags = flags; - this.cd.markForCheck(); - } } function detectWebGL() { diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index 3075491a8..e225eb758 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -185,41 +185,6 @@ export interface TransactionStripped { context?: 'projected' | 'actual'; } -// binary flags for transaction classification -export const TransactionFlags = { - // features - rbf: 0b00000001n, - no_rbf: 0b00000010n, - v1: 0b00000100n, - v2: 0b00001000n, - // address types - p2pk: 0b00000001_00000000n, - p2ms: 0b00000010_00000000n, - p2pkh: 0b00000100_00000000n, - p2sh: 0b00001000_00000000n, - p2wpkh: 0b00010000_00000000n, - p2wsh: 0b00100000_00000000n, - p2tr: 0b01000000_00000000n, - // behavior - cpfp_parent: 0b00000001_00000000_00000000n, - cpfp_child: 0b00000010_00000000_00000000n, - replacement: 0b00000100_00000000_00000000n, - // data - op_return: 0b00000001_00000000_00000000_00000000n, - fake_multisig: 0b00000010_00000000_00000000_00000000n, - inscription: 0b00000100_00000000_00000000_00000000n, - // heuristics - coinjoin: 0b00000001_00000000_00000000_00000000_00000000n, - consolidation: 0b00000010_00000000_00000000_00000000_00000000n, - batch_payout: 0b00000100_00000000_00000000_00000000_00000000n, - // sighash - sighash_all: 0b00000001_00000000_00000000_00000000_00000000_00000000n, - sighash_none: 0b00000010_00000000_00000000_00000000_00000000_00000000n, - sighash_single: 0b00000100_00000000_00000000_00000000_00000000_00000000n, - sighash_default:0b00001000_00000000_00000000_00000000_00000000_00000000n, - sighash_acp: 0b00010000_00000000_00000000_00000000_00000000_00000000n, -}; - export interface RbfTransaction extends TransactionStripped { rbf?: boolean; mined?: boolean, diff --git a/frontend/src/app/shared/filters.utils.ts b/frontend/src/app/shared/filters.utils.ts new file mode 100644 index 000000000..f7e2f91c1 --- /dev/null +++ b/frontend/src/app/shared/filters.utils.ts @@ -0,0 +1,87 @@ +export interface Filter { + key: string, + label: string, + flag: bigint, + toggle?: string, + group?: string, +} + +// binary flags for transaction classification +export const TransactionFlags = { + // features + rbf: 0b00000001n, + no_rbf: 0b00000010n, + v1: 0b00000100n, + v2: 0b00001000n, + multisig: 0b00010000n, + // address types + p2pk: 0b00000001_00000000n, + p2ms: 0b00000010_00000000n, + p2pkh: 0b00000100_00000000n, + p2sh: 0b00001000_00000000n, + p2wpkh: 0b00010000_00000000n, + p2wsh: 0b00100000_00000000n, + p2tr: 0b01000000_00000000n, + // behavior + cpfp_parent: 0b00000001_00000000_00000000n, + cpfp_child: 0b00000010_00000000_00000000n, + replacement: 0b00000100_00000000_00000000n, + // data + op_return: 0b00000001_00000000_00000000_00000000n, + fake_multisig: 0b00000010_00000000_00000000_00000000n, + inscription: 0b00000100_00000000_00000000_00000000n, + // heuristics + coinjoin: 0b00000001_00000000_00000000_00000000_00000000n, + consolidation: 0b00000010_00000000_00000000_00000000_00000000n, + batch_payout: 0b00000100_00000000_00000000_00000000_00000000n, + // sighash + sighash_all: 0b00000001_00000000_00000000_00000000_00000000_00000000n, + sighash_none: 0b00000010_00000000_00000000_00000000_00000000_00000000n, + sighash_single: 0b00000100_00000000_00000000_00000000_00000000_00000000n, + sighash_default:0b00001000_00000000_00000000_00000000_00000000_00000000n, + sighash_acp: 0b00010000_00000000_00000000_00000000_00000000_00000000n, +}; + +export const TransactionFilters: { [key: string]: Filter } = { + // features + rbf: { key: 'rbf', label: 'RBF enabled', flag: TransactionFlags.rbf, toggle: 'rbf' }, + no_rbf: { key: 'no_rbf', label: 'RBF disabled', flag: TransactionFlags.no_rbf, toggle: 'rbf' }, + v1: { key: 'v1', label: 'Version 1', flag: TransactionFlags.v1, toggle: 'version' }, + v2: { key: 'v2', label: 'Version 2', flag: TransactionFlags.v2, toggle: 'version' }, + multisig: { key: 'multisig', label: 'Multisig', flag: TransactionFlags.multisig }, + // address types + p2pk: { key: 'p2pk', label: 'P2PK', flag: TransactionFlags.p2pk }, + p2ms: { key: 'p2ms', label: 'Bare multisig', flag: TransactionFlags.p2ms }, + p2pkh: { key: 'p2pkh', label: 'P2PKH', flag: TransactionFlags.p2pkh }, + p2sh: { key: 'p2sh', label: 'P2SH', flag: TransactionFlags.p2sh }, + p2wpkh: { key: 'p2wpkh', label: 'P2WPKH', flag: TransactionFlags.p2wpkh }, + p2wsh: { key: 'p2wsh', label: 'P2WSH', flag: TransactionFlags.p2wsh }, + p2tr: { key: 'p2tr', label: 'Taproot', flag: TransactionFlags.p2tr }, + // behavior + cpfp_parent: { key: 'cpfp_parent', label: 'Paid for by child', flag: TransactionFlags.cpfp_parent }, + cpfp_child: { key: 'cpfp_child', label: 'Pays for parent', flag: TransactionFlags.cpfp_child }, + replacement: { key: 'replacement', label: 'Replacement', flag: TransactionFlags.replacement }, + // data + op_return: { key: 'op_return', label: 'OP_RETURN', flag: TransactionFlags.op_return }, + // fake_multisig: { key: 'fake_multisig', label: 'Fake multisig', flag: TransactionFlags.fake_multisig }, + inscription: { key: 'inscription', label: 'Inscription', flag: TransactionFlags.inscription }, + // heuristics + coinjoin: { key: 'coinjoin', label: 'Coinjoin', flag: TransactionFlags.coinjoin }, + consolidation: { key: 'consolidation', label: 'Consolidation', flag: TransactionFlags.consolidation }, + batch_payout: { key: 'batch_payout', label: 'Batch payment', flag: TransactionFlags.batch_payout }, + // 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_default: { key: 'sighash_default', label: 'sighash_default', flag: TransactionFlags.sighash_default }, + sighash_acp: { key: 'sighash_acp', label: 'sighash_anyonecanpay', flag: TransactionFlags.sighash_acp }, +}; + +export const FilterGroups: { label: string, filters: Filter[]}[] = [ + { label: 'Features', filters: ['rbf', 'no_rbf', 'v1', 'v2', 'multisig'] }, + { label: 'Address Types', filters: ['p2pk', 'p2ms', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh', 'p2tr'] }, + { label: 'Behavior', filters: ['cpfp_parent', 'cpfp_child', 'replacement'] }, + { label: 'Data', filters: ['op_return', 'fake_multisig', 'inscription'] }, + { label: 'Heuristics', filters: ['coinjoin', 'consolidation', 'batch_payout'] }, + { label: 'Sighash Flags', filters: ['sighash_all', 'sighash_none', 'sighash_single', 'sighash_default', 'sighash_acp'] }, +].map(group => ({ label: group.label, filters: group.filters.map(filter => TransactionFilters[filter] || null).filter(f => f != null) })); \ No newline at end of file diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 52123f995..9bcfb932c 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -44,6 +44,7 @@ import { StartComponent } from '../components/start/start.component'; import { TransactionsListComponent } from '../components/transactions-list/transactions-list.component'; import { BlockOverviewGraphComponent } from '../components/block-overview-graph/block-overview-graph.component'; import { BlockOverviewTooltipComponent } from '../components/block-overview-tooltip/block-overview-tooltip.component'; +import { BlockFiltersComponent } from '../components/block-filters/block-filters.component'; import { AddressComponent } from '../components/address/address.component'; import { SearchFormComponent } from '../components/search-form/search-form.component'; import { AddressLabelsComponent } from '../components/address-labels/address-labels.component'; @@ -141,6 +142,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir StartComponent, BlockOverviewGraphComponent, BlockOverviewTooltipComponent, + BlockFiltersComponent, TransactionsListComponent, AddressComponent, SearchFormComponent, @@ -266,6 +268,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir StartComponent, BlockOverviewGraphComponent, BlockOverviewTooltipComponent, + BlockFiltersComponent, TransactionsListComponent, AddressComponent, SearchFormComponent, From 5777561744d8e0525c8f397ff1cd345c8cfe9d23 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 13 Dec 2023 11:04:45 +0000 Subject: [PATCH 13/69] Tidy up block filter code, disable multisig flag --- .../block-filters/block-filters.component.ts | 12 +++--------- frontend/src/app/shared/filters.utils.ts | 14 +++++++------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/frontend/src/app/components/block-filters/block-filters.component.ts b/frontend/src/app/components/block-filters/block-filters.component.ts index fc154bb69..97b23a4db 100644 --- a/frontend/src/app/components/block-filters/block-filters.component.ts +++ b/frontend/src/app/components/block-filters/block-filters.component.ts @@ -1,5 +1,5 @@ -import { Component, OnChanges, EventEmitter, Output, SimpleChanges, HostListener } from '@angular/core'; -import { FilterGroups, TransactionFilters, Filter, TransactionFlags } from '../../shared/filters.utils'; +import { Component, EventEmitter, Output, HostListener } from '@angular/core'; +import { FilterGroups, TransactionFilters } from '../../shared/filters.utils'; @Component({ @@ -7,7 +7,7 @@ import { FilterGroups, TransactionFilters, Filter, TransactionFlags } from '../. templateUrl: './block-filters.component.html', styleUrls: ['./block-filters.component.scss'], }) -export class BlockFiltersComponent implements OnChanges { +export class BlockFiltersComponent { @Output() onFilterChanged: EventEmitter = new EventEmitter(); filters = TransactionFilters; @@ -16,12 +16,6 @@ export class BlockFiltersComponent implements OnChanges { filterFlags: { [key: string]: boolean } = {}; menuOpen: boolean = false; - constructor() {} - - ngOnChanges(changes: SimpleChanges): void { - - } - toggleFilter(key): void { const filter = this.filters[key]; this.filterFlags[key] = !this.filterFlags[key]; diff --git a/frontend/src/app/shared/filters.utils.ts b/frontend/src/app/shared/filters.utils.ts index f7e2f91c1..72cb5976a 100644 --- a/frontend/src/app/shared/filters.utils.ts +++ b/frontend/src/app/shared/filters.utils.ts @@ -43,13 +43,13 @@ export const TransactionFlags = { }; export const TransactionFilters: { [key: string]: Filter } = { - // features + /* features */ rbf: { key: 'rbf', label: 'RBF enabled', flag: TransactionFlags.rbf, toggle: 'rbf' }, no_rbf: { key: 'no_rbf', label: 'RBF disabled', flag: TransactionFlags.no_rbf, toggle: 'rbf' }, v1: { key: 'v1', label: 'Version 1', flag: TransactionFlags.v1, toggle: 'version' }, v2: { key: 'v2', label: 'Version 2', flag: TransactionFlags.v2, toggle: 'version' }, - multisig: { key: 'multisig', label: 'Multisig', flag: TransactionFlags.multisig }, - // address types + // multisig: { key: 'multisig', label: 'Multisig', flag: TransactionFlags.multisig }, + /* address types */ p2pk: { key: 'p2pk', label: 'P2PK', flag: TransactionFlags.p2pk }, p2ms: { key: 'p2ms', label: 'Bare multisig', flag: TransactionFlags.p2ms }, p2pkh: { key: 'p2pkh', label: 'P2PKH', flag: TransactionFlags.p2pkh }, @@ -57,19 +57,19 @@ export const TransactionFilters: { [key: string]: Filter } = { p2wpkh: { key: 'p2wpkh', label: 'P2WPKH', flag: TransactionFlags.p2wpkh }, p2wsh: { key: 'p2wsh', label: 'P2WSH', flag: TransactionFlags.p2wsh }, p2tr: { key: 'p2tr', label: 'Taproot', flag: TransactionFlags.p2tr }, - // behavior + /* behavior */ cpfp_parent: { key: 'cpfp_parent', label: 'Paid for by child', flag: TransactionFlags.cpfp_parent }, cpfp_child: { key: 'cpfp_child', label: 'Pays for parent', flag: TransactionFlags.cpfp_child }, replacement: { key: 'replacement', label: 'Replacement', flag: TransactionFlags.replacement }, - // data + /* data */ op_return: { key: 'op_return', label: 'OP_RETURN', flag: TransactionFlags.op_return }, // fake_multisig: { key: 'fake_multisig', label: 'Fake multisig', flag: TransactionFlags.fake_multisig }, inscription: { key: 'inscription', label: 'Inscription', flag: TransactionFlags.inscription }, - // heuristics + /* heuristics */ coinjoin: { key: 'coinjoin', label: 'Coinjoin', flag: TransactionFlags.coinjoin }, consolidation: { key: 'consolidation', label: 'Consolidation', flag: TransactionFlags.consolidation }, batch_payout: { key: 'batch_payout', label: 'Batch payment', flag: TransactionFlags.batch_payout }, - // sighash + /* 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 }, From c019355c9fb7a17b12efb453ca2c600f8d46ef1b Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 13 Dec 2023 11:29:10 +0000 Subject: [PATCH 14/69] Adapt block filter UI for small screens --- .../block-filters.component.html | 11 +++++-- .../block-filters.component.scss | 24 ++++++++++++++ .../block-filters/block-filters.component.ts | 15 +++++++-- .../block-overview-graph.component.html | 2 +- frontend/src/app/shared/filters.utils.ts | 31 ++++++++++--------- 5 files changed, 63 insertions(+), 20 deletions(-) diff --git a/frontend/src/app/components/block-filters/block-filters.component.html b/frontend/src/app/components/block-filters/block-filters.component.html index ff86e6b3b..90b66ddc3 100644 --- a/frontend/src/app/components/block-filters/block-filters.component.html +++ b/frontend/src/app/components/block-filters/block-filters.component.html @@ -1,4 +1,4 @@ -
+
-
+
{{ group.label }}
@@ -19,4 +19,11 @@
+
+ + + + + +
\ No newline at end of file diff --git a/frontend/src/app/components/block-filters/block-filters.component.scss b/frontend/src/app/components/block-filters/block-filters.component.scss index ee9e7f4d3..20b565293 100644 --- a/frontend/src/app/components/block-filters/block-filters.component.scss +++ b/frontend/src/app/components/block-filters/block-filters.component.scss @@ -101,4 +101,28 @@ pointer-events: all; background: #181b2d7f; } + + &.small { + .filter-tag { + font-size: 0.8em; + } + } + + &.vsmall { + .filter-menu { + margin-top: 0.25em; + h5 { + display: none; + } + } + .filter-tag { + font-size: 0.7em; + } + } + + &.tiny { + .filter-tag { + font-size: 0.5em; + } + } } \ No newline at end of file diff --git a/frontend/src/app/components/block-filters/block-filters.component.ts b/frontend/src/app/components/block-filters/block-filters.component.ts index 97b23a4db..ce0dd76ab 100644 --- a/frontend/src/app/components/block-filters/block-filters.component.ts +++ b/frontend/src/app/components/block-filters/block-filters.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Output, HostListener } from '@angular/core'; +import { Component, EventEmitter, Output, HostListener, Input, ChangeDetectorRef, OnChanges, SimpleChanges } from '@angular/core'; import { FilterGroups, TransactionFilters } from '../../shared/filters.utils'; @@ -7,7 +7,8 @@ import { FilterGroups, TransactionFilters } from '../../shared/filters.utils'; templateUrl: './block-filters.component.html', styleUrls: ['./block-filters.component.scss'], }) -export class BlockFiltersComponent { +export class BlockFiltersComponent implements OnChanges { + @Input() cssWidth: number = 800; @Output() onFilterChanged: EventEmitter = new EventEmitter(); filters = TransactionFilters; @@ -16,6 +17,16 @@ export class BlockFiltersComponent { filterFlags: { [key: string]: boolean } = {}; menuOpen: boolean = false; + constructor( + private cd: ChangeDetectorRef, + ) {} + + ngOnChanges(changes: SimpleChanges): void { + if (changes.cssWidth) { + this.cd.markForCheck(); + } + } + toggleFilter(key): void { const filter = this.filters[key]; this.filterFlags[key] = !this.filterFlags[key]; 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 9f7408323..251b84a73 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 @@ -13,6 +13,6 @@ [auditEnabled]="auditHighlighting" [blockConversion]="blockConversion" > - +
diff --git a/frontend/src/app/shared/filters.utils.ts b/frontend/src/app/shared/filters.utils.ts index 72cb5976a..4a8cb6a15 100644 --- a/frontend/src/app/shared/filters.utils.ts +++ b/frontend/src/app/shared/filters.utils.ts @@ -4,6 +4,7 @@ export interface Filter { flag: bigint, toggle?: string, group?: string, + important?: boolean, } // binary flags for transaction classification @@ -44,29 +45,29 @@ export const TransactionFlags = { export const TransactionFilters: { [key: string]: Filter } = { /* features */ - rbf: { key: 'rbf', label: 'RBF enabled', flag: TransactionFlags.rbf, toggle: 'rbf' }, - no_rbf: { key: 'no_rbf', label: 'RBF disabled', flag: TransactionFlags.no_rbf, toggle: 'rbf' }, + 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' }, // multisig: { key: 'multisig', label: 'Multisig', flag: TransactionFlags.multisig }, /* address types */ - p2pk: { key: 'p2pk', label: 'P2PK', flag: TransactionFlags.p2pk }, - p2ms: { key: 'p2ms', label: 'Bare multisig', flag: TransactionFlags.p2ms }, - p2pkh: { key: 'p2pkh', label: 'P2PKH', flag: TransactionFlags.p2pkh }, - p2sh: { key: 'p2sh', label: 'P2SH', flag: TransactionFlags.p2sh }, - p2wpkh: { key: 'p2wpkh', label: 'P2WPKH', flag: TransactionFlags.p2wpkh }, - p2wsh: { key: 'p2wsh', label: 'P2WSH', flag: TransactionFlags.p2wsh }, - p2tr: { key: 'p2tr', label: 'Taproot', flag: TransactionFlags.p2tr }, + 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 }, /* behavior */ - cpfp_parent: { key: 'cpfp_parent', label: 'Paid for by child', flag: TransactionFlags.cpfp_parent }, - cpfp_child: { key: 'cpfp_child', label: 'Pays for parent', flag: TransactionFlags.cpfp_child }, - replacement: { key: 'replacement', label: 'Replacement', flag: TransactionFlags.replacement }, + 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 }, /* data */ - op_return: { key: 'op_return', label: 'OP_RETURN', flag: TransactionFlags.op_return }, + op_return: { key: 'op_return', label: 'OP_RETURN', flag: TransactionFlags.op_return, important: true }, // fake_multisig: { key: 'fake_multisig', label: 'Fake multisig', flag: TransactionFlags.fake_multisig }, - inscription: { key: 'inscription', label: 'Inscription', flag: TransactionFlags.inscription }, + inscription: { key: 'inscription', label: 'Inscription', flag: TransactionFlags.inscription, important: true }, /* heuristics */ - coinjoin: { key: 'coinjoin', label: 'Coinjoin', flag: TransactionFlags.coinjoin }, + 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 }, /* sighash */ From 7cb58ed98854fea8941882762add04f17beaf136 Mon Sep 17 00:00:00 2001 From: takuabonn Date: Wed, 13 Dec 2023 21:48:41 +0900 Subject: [PATCH 15/69] update to use cachedRequest --- .../app/lightning/lightning-api.service.ts | 44 ++++++++++++++++++- .../nodes-networks-chart.component.ts | 4 +- .../lightning-statistics-chart.component.ts | 4 +- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/lightning/lightning-api.service.ts b/frontend/src/app/lightning/lightning-api.service.ts index fda93d446..ee55fb75d 100644 --- a/frontend/src/app/lightning/lightning-api.service.ts +++ b/frontend/src/app/lightning/lightning-api.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http'; -import { Observable } from 'rxjs'; +import { BehaviorSubject, Observable, catchError, filter, of, shareReplay, take, tap } from 'rxjs'; import { StateService } from '../services/state.service'; import { IChannel, INodesRanking, IOldestNodes, ITopNodesPerCapacity, ITopNodesPerChannels } from '../interfaces/node-api.interface'; @@ -9,6 +9,8 @@ import { IChannel, INodesRanking, IOldestNodes, ITopNodesPerCapacity, ITopNodesP }) export class LightningApiService { private apiBasePath = ''; // network path is /testnet, etc. or '' for mainnet + + private requestCache = new Map, expiry: number }>; constructor( private httpClient: HttpClient, @@ -23,6 +25,46 @@ export class LightningApiService { }); } + private generateCacheKey(functionName: string, params: any[]): string { + return functionName + JSON.stringify(params); + } + + // delete expired cache entries + private cleanExpiredCache(): void { + this.requestCache.forEach((value, key) => { + if (value.expiry < Date.now()) { + this.requestCache.delete(key); + } + }); + } + + cachedRequest Observable>( + apiFunction: F, + expireAfter: number, // in ms + ...params: Parameters + ): Observable { + this.cleanExpiredCache(); + + const cacheKey = this.generateCacheKey(apiFunction.name, params); + if (!this.requestCache.has(cacheKey)) { + const subject = new BehaviorSubject(null); + this.requestCache.set(cacheKey, { subject, expiry: Date.now() + expireAfter }); + + apiFunction.bind(this)(...params).pipe( + tap(data => { + subject.next(data as T); + }), + catchError((error) => { + subject.error(error); + return of(null); + }), + shareReplay(1), + ).subscribe(); + } + + return this.requestCache.get(cacheKey).subject.asObservable().pipe(filter(val => val !== null), take(1)); + } + getNode$(publicKey: string): Observable { return this.httpClient.get(this.apiBasePath + '/api/v1/lightning/nodes/' + publicKey); } diff --git a/frontend/src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts b/frontend/src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts index 7352d884d..30f786b16 100644 --- a/frontend/src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts +++ b/frontend/src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts @@ -82,9 +82,9 @@ export class NodesNetworksChartComponent implements OnInit { firstRun = false; this.miningWindowPreference = timespan; this.isLoading = true; - return this.lightningApiService.listStatistics$(timespan) + return this.lightningApiService.cachedRequest(this.lightningApiService.listStatistics$, 250, timespan) .pipe( - tap((response) => { + tap((response:any) => { const data = response.body; const chartData = { tor_nodes: data.map(val => [val.added * 1000, val.tor_nodes]), diff --git a/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts b/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts index 7417a35cd..0e4f66ca0 100644 --- a/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts +++ b/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts @@ -81,9 +81,9 @@ export class LightningStatisticsChartComponent implements OnInit { firstRun = false; this.miningWindowPreference = timespan; this.isLoading = true; - return this.lightningApiService.listStatistics$(timespan) + return this.lightningApiService.cachedRequest(this.lightningApiService.listStatistics$, 250, timespan) .pipe( - tap((response) => { + tap((response:any) => { const data = response.body; this.prepareChartOptions({ channel_count: data.map(val => [val.added * 1000, val.channel_count]), From 1fb3de9dc3b15aaf8cbad31d0fbc8268d468159e Mon Sep 17 00:00:00 2001 From: takuabonn Date: Wed, 13 Dec 2023 23:26:14 +0900 Subject: [PATCH 16/69] update contributors --- contributors/takuabonn.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 contributors/takuabonn.txt diff --git a/contributors/takuabonn.txt b/contributors/takuabonn.txt new file mode 100644 index 000000000..331019e91 --- /dev/null +++ b/contributors/takuabonn.txt @@ -0,0 +1,3 @@ +I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of December 13, 2023. + +Signed: takuabonn \ No newline at end of file From ce195c913397dfca81a120f005b2e2175f19d238 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 13 Dec 2023 16:15:55 +0000 Subject: [PATCH 17/69] Fix ECDSA DER signature detection --- backend/src/api/common.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index 00f4328ce..42dae7eb0 100644 --- a/backend/src/api/common.ts +++ b/backend/src/api/common.ts @@ -171,8 +171,8 @@ export class Common { // heuristic to detect probable DER signatures return (w.length >= 18 && w.startsWith('30') // minimum DER signature length is 8 bytes + sighash flag (see https://mempool.space/testnet/tx/c6c232a36395fa338da458b86ff1327395a9afc28c5d2daa4273e410089fd433) - && ['01, 02, 03, 81, 82, 83'].includes(w.slice(-2)) // signature must end with a valid sighash flag - && (w.length === parseInt(w.slice(2, 4), 16) + 6) // second byte encodes the combined length of the R and S components + && ['01', '02', '03', '81', '82', '83'].includes(w.slice(-2)) // signature must end with a valid sighash flag + && (w.length === (2 * parseInt(w.slice(2, 4), 16)) + 6) // second byte encodes the combined length of the R and S components ); } From 512589dc79fde70a1dec5f6ed443b4b1d84aa789 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Thu, 14 Dec 2023 11:26:17 +0000 Subject: [PATCH 18/69] Add fake pubkey filter --- backend/src/api/common.ts | 32 ++++++++-- backend/src/mempool.interfaces.ts | 2 +- backend/src/utils/secp256k1.ts | 74 ++++++++++++++++++++++++ frontend/src/app/shared/filters.utils.ts | 6 +- 4 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 backend/src/utils/secp256k1.ts diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index 42dae7eb0..751bab5a3 100644 --- a/backend/src/api/common.ts +++ b/backend/src/api/common.ts @@ -6,6 +6,7 @@ import { NodeSocket } from '../repositories/NodesSocketsRepository'; import { isIP } from 'net'; import rbfCache from './rbf-cache'; import transactionUtils from './transaction-utils'; +import { isPoint } from '../utils/secp256k1'; export class Common { static nativeAssetId = config.MEMPOOL.NETWORK === 'liquidtestnet' ? '144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49' @@ -211,6 +212,15 @@ export class Common { } } + static isBurnKey(pubkey: string): boolean { + return [ + '022222222222222222222222222222222222222222222222222222222222222222', + '033333333333333333333333333333333333333333333333333333333333333333', + '020202020202020202020202020202020202020202020202020202020202020202', + '030303030303030303030303030303030303030303030303030303030303030303', + ].includes(pubkey); + } + static getTransactionFlags(tx: TransactionExtended): number { let flags = 0n; if (tx.version === 1) { @@ -249,8 +259,8 @@ export class Common { flags |= this.setSchnorrSighashFlags(flags, vin.witness); } else if (vin.witness) { flags |= this.setSegwitSighashFlags(flags, vin.witness); - } else if (vin.scriptsig_asm) { - flags |= this.setLegacySighashFlags(flags, vin.scriptsig_asm); + } else if (vin.scriptsig?.length) { + flags |= this.setLegacySighashFlags(flags, vin.scriptsig_asm || transactionUtils.convertScriptSigAsm(vin.scriptsig)); } if (vin.prevout?.scriptpubkey_address) { @@ -263,12 +273,23 @@ export class Common { } else { flags |= TransactionFlags.no_rbf; } + let hasFakePubkey = false; for (const vout of tx.vout) { switch (vout.scriptpubkey_type) { - case 'p2pk': flags |= TransactionFlags.p2pk; break; + case 'p2pk': { + flags |= TransactionFlags.p2pk; + // detect fake pubkey (i.e. not a valid DER point on the secp256k1 curve) + hasFakePubkey = hasFakePubkey || !isPoint(vout.scriptpubkey.slice(2, -2)); + } break; case 'multisig': { flags |= TransactionFlags.p2ms; - // TODO - detect fake multisig data embedding + // detect fake pubkeys (i.e. not valid DER points on the secp256k1 curve) + const asm = vout.scriptpubkey_asm || transactionUtils.convertScriptSigAsm(vout.scriptpubkey); + for (const key of (asm?.split(' ') || [])) { + if (!hasFakePubkey && !key.startsWith('OP_')) { + hasFakePubkey = hasFakePubkey || this.isBurnKey(key) || !isPoint(key); + } + } } break; case 'p2pkh': flags |= TransactionFlags.p2pkh; break; case 'p2sh': flags |= TransactionFlags.p2sh; break; @@ -282,6 +303,9 @@ export class Common { } outValues[vout.value || Math.random()] = (outValues[vout.value || Math.random()] || 0) + 1; } + if (hasFakePubkey) { + flags |= TransactionFlags.fake_pubkey; + } if (tx.ancestors?.length) { flags |= TransactionFlags.cpfp_child; } diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index f50274304..4a630f1e4 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -215,7 +215,7 @@ export const TransactionFlags = { replacement: 0b00000100_00000000_00000000n, // data op_return: 0b00000001_00000000_00000000_00000000n, - fake_multisig: 0b00000010_00000000_00000000_00000000n, + fake_pubkey: 0b00000010_00000000_00000000_00000000n, inscription: 0b00000100_00000000_00000000_00000000n, // heuristics coinjoin: 0b00000001_00000000_00000000_00000000_00000000n, diff --git a/backend/src/utils/secp256k1.ts b/backend/src/utils/secp256k1.ts new file mode 100644 index 000000000..cc731f17d --- /dev/null +++ b/backend/src/utils/secp256k1.ts @@ -0,0 +1,74 @@ +function powMod(x: bigint, power: number, modulo: bigint): bigint { + for (let i = 0; i < power; i++) { + x = (x * x) % modulo; + } + return x; +} + +function sqrtMod(x: bigint, P: bigint): bigint { + const b2 = (x * x * x) % P; + const b3 = (b2 * b2 * x) % P; + const b6 = (powMod(b3, 3, P) * b3) % P; + const b9 = (powMod(b6, 3, P) * b3) % P; + const b11 = (powMod(b9, 2, P) * b2) % P; + const b22 = (powMod(b11, 11, P) * b11) % P; + const b44 = (powMod(b22, 22, P) * b22) % P; + const b88 = (powMod(b44, 44, P) * b44) % P; + const b176 = (powMod(b88, 88, P) * b88) % P; + const b220 = (powMod(b176, 44, P) * b44) % P; + const b223 = (powMod(b220, 3, P) * b3) % P; + const t1 = (powMod(b223, 23, P) * b22) % P; + const t2 = (powMod(t1, 6, P) * b2) % P; + const root = powMod(t2, 2, P); + return root; +} + +const curveP = BigInt(`0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F`); + +/** + * This function tells whether the point given is a DER encoded point on the ECDSA curve. + * @param {string} pointHex The point as a hex string (*must not* include a '0x' prefix) + * @returns {boolean} true if the point is on the SECP256K1 curve + */ +export function isPoint(pointHex: string): boolean { + if ( + !( + // is uncompressed + ( + (pointHex.length === 130 && pointHex.startsWith('04')) || + // OR is compressed + (pointHex.length === 66 && + (pointHex.startsWith('02') || pointHex.startsWith('03'))) + ) + ) + ) { + return false; + } + + // Function modified slightly from noble-curves + + + // Now we know that pointHex is a 33 or 65 byte hex string. + const isCompressed = pointHex.length === 66; + + const x = BigInt(`0x${pointHex.slice(2, 66)}`); + if (x >= curveP) { + return false; + } + + if (!isCompressed) { + const y = BigInt(`0x${pointHex.slice(66, 130)}`); + if (y >= curveP) { + return false; + } + // Just check y^2 = x^3 + 7 (secp256k1 curve) + return (y * y) % curveP === (x * x * x + 7n) % curveP; + } else { + // Get unaltered y^2 (no mod p) + const ySquared = (x * x * x + 7n) % curveP; + // Try to sqrt it, it will round down if not perfect root + const y = sqrtMod(ySquared, curveP); + // If we square and it's equal, then it was a perfect root and valid point. + return (y * y) % curveP === ySquared; + } +} \ No newline at end of file diff --git a/frontend/src/app/shared/filters.utils.ts b/frontend/src/app/shared/filters.utils.ts index 4a8cb6a15..0b652a192 100644 --- a/frontend/src/app/shared/filters.utils.ts +++ b/frontend/src/app/shared/filters.utils.ts @@ -29,7 +29,7 @@ export const TransactionFlags = { replacement: 0b00000100_00000000_00000000n, // data op_return: 0b00000001_00000000_00000000_00000000n, - fake_multisig: 0b00000010_00000000_00000000_00000000n, + fake_pubkey: 0b00000010_00000000_00000000_00000000n, inscription: 0b00000100_00000000_00000000_00000000n, // heuristics coinjoin: 0b00000001_00000000_00000000_00000000_00000000n, @@ -64,7 +64,7 @@ export const TransactionFilters: { [key: string]: Filter } = { replacement: { key: 'replacement', label: 'Replacement', flag: TransactionFlags.replacement, important: true }, /* data */ op_return: { key: 'op_return', label: 'OP_RETURN', flag: TransactionFlags.op_return, important: true }, - // fake_multisig: { key: 'fake_multisig', label: 'Fake multisig', flag: TransactionFlags.fake_multisig }, + fake_pubkey: { key: 'fake_pubkey', label: 'Fake pubkey', flag: TransactionFlags.fake_pubkey }, inscription: { key: 'inscription', label: 'Inscription', flag: TransactionFlags.inscription, important: true }, /* heuristics */ coinjoin: { key: 'coinjoin', label: 'Coinjoin', flag: TransactionFlags.coinjoin, important: true }, @@ -82,7 +82,7 @@ export const FilterGroups: { label: string, filters: Filter[]}[] = [ { label: 'Features', filters: ['rbf', 'no_rbf', 'v1', 'v2', 'multisig'] }, { label: 'Address Types', filters: ['p2pk', 'p2ms', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh', 'p2tr'] }, { label: 'Behavior', filters: ['cpfp_parent', 'cpfp_child', 'replacement'] }, - { label: 'Data', filters: ['op_return', 'fake_multisig', 'inscription'] }, + { label: 'Data', filters: ['op_return', 'fake_pubkey', 'inscription'] }, { label: 'Heuristics', filters: ['coinjoin', 'consolidation', 'batch_payout'] }, { label: 'Sighash Flags', filters: ['sighash_all', 'sighash_none', 'sighash_single', 'sighash_default', 'sighash_acp'] }, ].map(group => ({ label: group.label, filters: group.filters.map(filter => TransactionFilters[filter] || null).filter(f => f != null) })); \ No newline at end of file From b0c02b16ff22058779a525ba9a02c0c34093a6ca Mon Sep 17 00:00:00 2001 From: natsee Date: Wed, 13 Dec 2023 19:22:46 +0100 Subject: [PATCH 19/69] Search result: do not offer Go to block --- .../app/components/search-form/search-form.component.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/components/search-form/search-form.component.ts b/frontend/src/app/components/search-form/search-form.component.ts index a99dc084e..446d8be5a 100644 --- a/frontend/src/app/components/search-form/search-form.component.ts +++ b/frontend/src/app/components/search-form/search-form.component.ts @@ -174,7 +174,7 @@ export class SearchFormComponent implements OnInit { const addressPrefixSearchResults = result[0]; const lightningResults = result[1]; - const matchesBlockHeight = this.regexBlockheight.test(searchText); + const matchesBlockHeight = this.regexBlockheight.test(searchText) && parseInt(searchText) <= this.stateService.latestBlockHeight; const matchesDateTime = this.regexDate.test(searchText) && new Date(searchText).toString() !== 'Invalid Date'; const matchesUnixTimestamp = this.regexUnixTimestamp.test(searchText); const matchesTxId = this.regexTransaction.test(searchText) && !this.regexBlockhash.test(searchText); @@ -217,7 +217,7 @@ export class SearchFormComponent implements OnInit { selectedResult(result: any): void { if (typeof result === 'string') { this.search(result); - } else if (typeof result === 'number') { + } else if (typeof result === 'number' && result <= this.stateService.latestBlockHeight) { this.navigate('/block/', result.toString()); } else if (result.alias) { this.navigate('/lightning/node/', result.public_key); @@ -232,8 +232,10 @@ export class SearchFormComponent implements OnInit { this.isSearching = true; if (!this.regexTransaction.test(searchText) && this.regexAddress.test(searchText)) { this.navigate('/address/', searchText); - } else if (this.regexBlockhash.test(searchText) || this.regexBlockheight.test(searchText)) { + } else if (this.regexBlockhash.test(searchText)) { this.navigate('/block/', searchText); + } else if (this.regexBlockheight.test(searchText)) { + parseInt(searchText) <= this.stateService.latestBlockHeight ? this.navigate('/block/', searchText) : this.isSearching = false; } else if (this.regexTransaction.test(searchText)) { const matches = this.regexTransaction.exec(searchText); if (this.network === 'liquid' || this.network === 'liquidtestnet') { From 16b9ca6c4096e76fbd73f3feba6b1ed662c0ab68 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Thu, 14 Dec 2023 13:30:19 +0000 Subject: [PATCH 20/69] Fix CI unit test circular dependency --- backend/src/api/fee-api.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/backend/src/api/fee-api.ts b/backend/src/api/fee-api.ts index 5260e959a..0cab5a295 100644 --- a/backend/src/api/fee-api.ts +++ b/backend/src/api/fee-api.ts @@ -1,8 +1,10 @@ import { MempoolBlock } from '../mempool.interfaces'; -import { Common } from './common'; +import config from '../config'; import mempool from './mempool'; import projectedBlocks from './mempool-blocks'; +const isLiquid = config.MEMPOOL.NETWORK === 'liquid' || config.MEMPOOL.NETWORK === 'liquidtestnet'; + interface RecommendedFees { fastestFee: number, halfHourFee: number, @@ -14,8 +16,8 @@ interface RecommendedFees { class FeeApi { constructor() { } - defaultFee = Common.isLiquid() ? 0.1 : 1; - minimumIncrement = Common.isLiquid() ? 0.1 : 1; + defaultFee = isLiquid ? 0.1 : 1; + minimumIncrement = isLiquid ? 0.1 : 1; public getRecommendedFee(): RecommendedFees { const pBlocks = projectedBlocks.getMempoolBlocks(); From 2e531413fa23b4ba93ff92877053bfb29b18aca3 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Thu, 14 Dec 2023 18:09:04 +0000 Subject: [PATCH 21/69] Disable filter UI on mined blocks --- .../block-overview-graph/block-overview-graph.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 251b84a73..9f5e7cb47 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 @@ -13,6 +13,6 @@ [auditEnabled]="auditHighlighting" [blockConversion]="blockConversion" > - + From 100936e551815894c76c3576308a3393bcb53f32 Mon Sep 17 00:00:00 2001 From: orangesurf Date: Fri, 15 Dec 2023 10:04:04 +0000 Subject: [PATCH 22/69] Changes following feedback --- LICENSE | 2 +- frontend/src/app/components/about/about.component.html | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/LICENSE b/LICENSE index f65965c7c..e5b707840 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ The Mempool Open Source Project® -Copyright (c) 2019-2023 The Mempool Space K.K. and other shadowy super-coders +Copyright (c) 2019-2023 Mempool Space K.K. and other shadowy super-coders This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html index 98365e91f..98dcb4c31 100644 --- a/frontend/src/app/components/about/about.component.html +++ b/frontend/src/app/components/about/about.component.html @@ -430,9 +430,7 @@
From eae044cf665ef3f5f3288298dbeae146b2a6506c Mon Sep 17 00:00:00 2001 From: natsee Date: Fri, 15 Dec 2023 15:38:02 +0100 Subject: [PATCH 23/69] Show blockhash copy button in block component --- 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 8bcf23ace..e908d5b24 100644 --- a/frontend/src/app/components/block/block.component.html +++ b/frontend/src/app/components/block/block.component.html @@ -42,7 +42,7 @@ Hash - ‎{{ block.id | shortenString : 13 }} + ‎{{ block.id | shortenString : 13 }} Timestamp From b3a68a0db2adde67a73e1fb79f407bffcf60bab4 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 15 Dec 2023 15:04:26 +0000 Subject: [PATCH 24/69] Smoother goggles color change transition --- .../block-overview-graph/block-overview-graph.component.ts | 1 + 1 file changed, 1 insertion(+) 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 716ba540e..8a449a121 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 @@ -121,6 +121,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On } else { this.scene.setColorFunction(this.overrideColors); } + this.start(); } ngOnDestroy(): void { From 60144695136e09620e31163d70cc7e5d2fd5641a Mon Sep 17 00:00:00 2001 From: softsimon Date: Fri, 15 Dec 2023 23:09:24 +0700 Subject: [PATCH 25/69] Acceleration sounds --- .../accelerate-preview.component.ts | 3 +++ .../transaction/transaction.component.ts | 6 +++++- frontend/src/app/services/audio.service.ts | 2 +- .../resources/sounds/ascend-chime-cartoon.mp3 | Bin 0 -> 17105 bytes .../sounds/wind-chimes-harp-ascend.mp3 | Bin 0 -> 63185 bytes 5 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 frontend/src/resources/sounds/ascend-chime-cartoon.mp3 create mode 100644 frontend/src/resources/sounds/wind-chimes-harp-ascend.mp3 diff --git a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts index f2224841a..d8ebb3830 100644 --- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts +++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts @@ -4,6 +4,7 @@ import { Subscription, catchError, of, tap } from 'rxjs'; import { StorageService } from '../../services/storage.service'; import { Transaction } from '../../interfaces/electrs.interface'; import { nextRoundNumber } from '../../shared/common.utils'; +import { AudioService } from '../../services/audio.service'; export type AccelerationEstimate = { txSummary: TxSummary; @@ -63,6 +64,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges constructor( private apiService: ApiService, private storageService: StorageService, + private audioService: AudioService, private cd: ChangeDetectorRef ) { } @@ -186,6 +188,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges this.userBid ).subscribe({ next: () => { + this.audioService.playSound('ascend-chime-cartoon'); this.showSuccess = true; this.scrollToPreviewWithTimeout('successAlert', 'center'); this.estimateSubscription.unsubscribe(); diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index ae3a950bd..f110c0435 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -439,7 +439,11 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { block_time: block.timestamp, }; this.stateService.markBlock$.next({ blockHeight: block.height }); - this.audioService.playSound('magic'); + if (this.accelerationInfo && ['accelerating', 'mined', 'completed'].includes(this.accelerationInfo.status)) { + this.audioService.playSound('wind-chimes-harp-ascend'); + } else { + this.audioService.playSound('magic'); + } this.fetchAcceleration$.next(block.id); } }); diff --git a/frontend/src/app/services/audio.service.ts b/frontend/src/app/services/audio.service.ts index a0b27d168..5b5ffbf07 100644 --- a/frontend/src/app/services/audio.service.ts +++ b/frontend/src/app/services/audio.service.ts @@ -13,7 +13,7 @@ export class AudioService { } catch (e) {} } - public playSound(name: 'magic' | 'chime' | 'cha-ching' | 'bright-harmony') { + public playSound(name: 'magic' | 'chime' | 'cha-ching' | 'bright-harmony' | 'wind-chimes-harp-ascend' | 'ascend-chime-cartoon') { if (this.isPlaying || !this.audio) { return; } diff --git a/frontend/src/resources/sounds/ascend-chime-cartoon.mp3 b/frontend/src/resources/sounds/ascend-chime-cartoon.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..614274324de204817ae044281e13bce0d748356c GIT binary patch literal 17105 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 vMeg4QasMR2|H8l(|Jy|FKhNs_jAf1dj<+^*+r literal 0 HcmV?d00001 From c2f52ac1f31a1674d7e6457c0c5bee9fd719a0d4 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 17 Dec 2023 09:57:15 +0000 Subject: [PATCH 26/69] Avoid repeated tx classification work --- backend/src/api/common.ts | 31 +++++++++++++++++++++---------- backend/src/mempool.interfaces.ts | 1 + 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index 751bab5a3..4cd2b0873 100644 --- a/backend/src/api/common.ts +++ b/backend/src/api/common.ts @@ -222,7 +222,25 @@ export class Common { } static getTransactionFlags(tx: TransactionExtended): number { - let flags = 0n; + let flags = tx.flags ? BigInt(tx.flags) : 0n; + + // Update variable flags (CPFP, RBF) + if (tx.ancestors?.length) { + flags |= TransactionFlags.cpfp_child; + } + if (tx.descendants?.length) { + flags |= TransactionFlags.cpfp_parent; + } + if (rbfCache.getRbfTree(tx.txid)) { + flags |= TransactionFlags.replacement; + } + + // Already processed static flags, no need to do it again + if (tx.flags) { + return Number(flags); + } + + // Process static flags if (tx.version === 1) { flags |= TransactionFlags.v1; } else if (tx.version === 2) { @@ -306,15 +324,7 @@ export class Common { if (hasFakePubkey) { flags |= TransactionFlags.fake_pubkey; } - if (tx.ancestors?.length) { - flags |= TransactionFlags.cpfp_child; - } - if (tx.descendants?.length) { - flags |= TransactionFlags.cpfp_parent; - } - if (rbfCache.getRbfTree(tx.txid)) { - flags |= TransactionFlags.replacement; - } + // fast but bad heuristic to detect possible coinjoins // (at least 5 inputs and 5 outputs, less than half of which are unique amounts, with no address reuse) const addressReuse = Object.values(reusedAddresses).reduce((acc, count) => Math.max(acc, count), 0) > 1; @@ -335,6 +345,7 @@ export class Common { static classifyTransaction(tx: TransactionExtended): TransactionClassified { const flags = this.getTransactionFlags(tx); + tx.flags = flags; return { ...this.stripTransaction(tx), flags, diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index 4a630f1e4..d5dfcbf14 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -95,6 +95,7 @@ export interface TransactionExtended extends IEsploraApi.Transaction { }; acceleration?: boolean; uid?: number; + flags?: number; } export interface MempoolTransactionExtended extends TransactionExtended { From a8fd2ac9dc2cb46cc8e7d99a767cffa824b89175 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 17 Dec 2023 10:45:26 +0000 Subject: [PATCH 27/69] Preserve tx replacement flag --- backend/src/api/common.ts | 2 +- backend/src/api/disk-cache.ts | 1 + backend/src/api/rbf-cache.ts | 13 +++++++++---- backend/src/api/redis-cache.ts | 1 + backend/src/mempool.interfaces.ts | 1 + 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index 4cd2b0873..358a98c98 100644 --- a/backend/src/api/common.ts +++ b/backend/src/api/common.ts @@ -231,7 +231,7 @@ export class Common { if (tx.descendants?.length) { flags |= TransactionFlags.cpfp_parent; } - if (rbfCache.getRbfTree(tx.txid)) { + if (tx.replacement) { flags |= TransactionFlags.replacement; } diff --git a/backend/src/api/disk-cache.ts b/backend/src/api/disk-cache.ts index 093f07f0d..202f8f4cb 100644 --- a/backend/src/api/disk-cache.ts +++ b/backend/src/api/disk-cache.ts @@ -256,6 +256,7 @@ class DiskCache { txs: rbfData.rbf.txs.map(([txid, entry]) => ({ value: entry })), trees: rbfData.rbf.trees, expiring: rbfData.rbf.expiring.map(([txid, value]) => ({ key: txid, value })), + mempool: memPool.getMempool(), }); } } catch (e) { diff --git a/backend/src/api/rbf-cache.ts b/backend/src/api/rbf-cache.ts index ad1762485..a087abbe0 100644 --- a/backend/src/api/rbf-cache.ts +++ b/backend/src/api/rbf-cache.ts @@ -97,6 +97,8 @@ class RbfCache { return; } + newTxExtended.replacement = true; + const newTx = Common.stripTransaction(newTxExtended) as RbfTransaction; const newTime = newTxExtended.firstSeen || (Date.now() / 1000); newTx.rbf = newTxExtended.vin.some((v) => v.sequence < 0xfffffffe); @@ -368,14 +370,14 @@ class RbfCache { }; } - public async load({ txs, trees, expiring }): Promise { + public async load({ txs, trees, expiring, mempool }): Promise { try { txs.forEach(txEntry => { this.txs.set(txEntry.value.txid, txEntry.value); }); this.staleCount = 0; for (const deflatedTree of trees) { - await this.importTree(deflatedTree.root, deflatedTree.root, deflatedTree, this.txs); + await this.importTree(mempool, deflatedTree.root, deflatedTree.root, deflatedTree, this.txs); } expiring.forEach(expiringEntry => { if (this.txs.has(expiringEntry.key)) { @@ -413,7 +415,7 @@ class RbfCache { return deflated; } - async importTree(root, txid, deflated, txs: Map, mined: boolean = false): Promise { + async importTree(mempool, root, txid, deflated, txs: Map, mined: boolean = false): Promise { const treeInfo = deflated[txid]; const replaces: RbfTree[] = []; @@ -426,9 +428,12 @@ class RbfCache { // recursively reconstruct child trees for (const childId of treeInfo.replaces) { - const replaced = await this.importTree(root, childId, deflated, txs, mined); + const replaced = await this.importTree(mempool, root, childId, deflated, txs, mined); if (replaced) { this.replacedBy.set(replaced.tx.txid, txid); + if (mempool[replaced.tx.txid]) { + mempool[replaced.tx.txid].replacement = true; + } replaces.push(replaced); if (replaced.mined) { mined = true; diff --git a/backend/src/api/redis-cache.ts b/backend/src/api/redis-cache.ts index 82ce34ad1..edfd2142b 100644 --- a/backend/src/api/redis-cache.ts +++ b/backend/src/api/redis-cache.ts @@ -222,6 +222,7 @@ class RedisCache { txs: rbfTxs, trees: rbfTrees.map(loadedTree => { loadedTree.value.key = loadedTree.key; return loadedTree.value; }), expiring: rbfExpirations, + mempool: memPool.getMempool(), }); } diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index d5dfcbf14..c93372ded 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -94,6 +94,7 @@ export interface TransactionExtended extends IEsploraApi.Transaction { vsize: number, }; acceleration?: boolean; + replacement?: boolean; uid?: number; flags?: number; } From bc89fd5b7c87ca8a1e5d05ce2d2ed8b87c5d7b95 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 15 Dec 2023 13:28:55 +0000 Subject: [PATCH 28/69] Goggles icon, beta tag and info tooltip --- .../block-filters.component.html | 8 +++-- .../block-filters.component.scss | 14 ++++++++ .../components/svg-icons/goggles.component.ts | 34 +++++++++++++++++++ frontend/src/app/shared/shared.module.ts | 6 +++- 4 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 frontend/src/app/shared/components/svg-icons/goggles.component.ts diff --git a/frontend/src/app/components/block-filters/block-filters.component.html b/frontend/src/app/components/block-filters/block-filters.component.html index 90b66ddc3..876a72ad1 100644 --- a/frontend/src/app/components/block-filters/block-filters.component.html +++ b/frontend/src/app/components/block-filters/block-filters.component.html @@ -1,7 +1,11 @@
+ + beta + +
-
diff --git a/frontend/src/app/components/block-filters/block-filters.component.scss b/frontend/src/app/components/block-filters/block-filters.component.scss index 20b565293..6406a1d93 100644 --- a/frontend/src/app/components/block-filters/block-filters.component.scss +++ b/frontend/src/app/components/block-filters/block-filters.component.scss @@ -20,7 +20,21 @@ margin-left: 0.5em; } + .info-badges { + display: flex; + flex-direction: row; + align-items: center; + float: right; + + &:hover, &:active { + text-decoration: none; + } + } + .menu-toggle { + width: 2em; + height: 2em; + padding: 0px 1px; opacity: 0; cursor: pointer; color: white; diff --git a/frontend/src/app/shared/components/svg-icons/goggles.component.ts b/frontend/src/app/shared/components/svg-icons/goggles.component.ts new file mode 100644 index 000000000..b045e6acb --- /dev/null +++ b/frontend/src/app/shared/components/svg-icons/goggles.component.ts @@ -0,0 +1,34 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'app-goggles-icon', + template: ` + + + + + + + `, +}) +export class GogglesIconComponent { + @Input() width: string = '100%'; + @Input() height: string = '100%'; +} diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 9bcfb932c..f772c3fd3 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -5,6 +5,7 @@ import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontaweso import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faHammer, faDatabase, faExchangeAlt, faInfoCircle, faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faClock, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft, faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck, faCircleCheck, faUserCircle, faCheck } from '@fortawesome/free-solid-svg-icons'; +import { GogglesIconComponent } from './components/svg-icons/goggles.component'; import { InfiniteScrollModule } from 'ngx-infinite-scroll'; import { MenuComponent } from '../components/menu/menu.component'; import { PreviewTitleComponent } from '../components/master-page-preview/preview-title.component'; @@ -200,6 +201,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir AccelerationsListComponent, AccelerationStatsComponent, PendingStatsComponent, + GogglesIconComponent, ], imports: [ CommonModule, @@ -322,7 +324,9 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir ClockFaceComponent, OnlyVsizeDirective, - OnlyWeightDirective + OnlyWeightDirective, + + GogglesIconComponent, ] }) export class SharedModule { From 8dbf879c37fc593531b2aa6d198fb611ebe6ba45 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 15 Dec 2023 13:29:29 +0000 Subject: [PATCH 29/69] Add Goggles FAQ entry --- .../src/app/docs/api-docs/api-docs-data.ts | 7 ++ .../app/docs/api-docs/api-docs.component.html | 89 +++++++++++++++++++ .../app/docs/api-docs/api-docs.component.scss | 41 +++++++++ 3 files changed, 137 insertions(+) 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 ce0b7eaef..97be0c2b1 100644 --- a/frontend/src/app/docs/api-docs/api-docs-data.ts +++ b/frontend/src/app/docs/api-docs/api-docs-data.ts @@ -8911,6 +8911,13 @@ export const faqData = [ fragment: "what-is-block-health", title: "What is block health?", }, + { + type: "endpoint", + category: "advanced", + showConditions: bitcoinNetworks, + fragment: "how-do-mempool-goggles-work", + title: "How do Mempool Goggles work?", + }, { type: "category", category: "self-hosting", diff --git a/frontend/src/app/docs/api-docs/api-docs.component.html b/frontend/src/app/docs/api-docs/api-docs.component.html index 49493d210..49b11ad7b 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.html +++ b/frontend/src/app/docs/api-docs/api-docs.component.html @@ -279,6 +279,95 @@

Because of this feature's resource usage and availability requirements, it is only supported on official mempool.space instances.

+ +

Mempool Goggles are a set of filters that can be applied to the mempool block visualizations to highlight different types of transactions.

+

There are currently 25 different Mempool Goggles filters, grouped into six categories:

+
+
Features
+
+
+
RBF enabled
+
The transaction opts-in to BIP-125 replaceability.
+
RBF disabled
+
The transaction does not opt-in to BIP-125 replaceability.
+
Version 1
+
The default version for most transactions.
+
Version 2
+
Required for transactions which use OP_CHECKSEQUENCEVERIFY relative timelocks.
+
+
+ +
Address Types
+
+
+
P2PK
+
Pay-to-public-key. A legacy output format most commonly found in old coinbase transactions.
+
Bare multisig
+
A legacy form of multisig, most commonly used for data embedding schemes (see also "Fake pubkey").
+
P2PKH
+
Pay-to-public-key-hash. A legacy address type that locks outputs to a public key.
+
P2SH
+
Pay-to-script-hash. A legacy address type that locks outputs to a redeem script.
+
P2WPKH
+
Pay-to-witness-public-key-hash. The SegWit version of P2PKH.
+
P2WSH
+
Pay-to-witness-script-hash. The SegWit version of P2SH.
+
Taproot
+
Addresses using the SegWit V1 format added in the Taproot upgrade.
+
+
+ +
Behavior
+
+
+
Paid for by child
+
The transaction's effective fee rate has been increased by a higher rate CPFP child.
+
Pays for parent
+
The transaction bumps the effective fee rate of a lower rate CPFP ancestor.
+
Replacement
+
The transaction replaced a prior version via RBF.
+
+
+ +
Data
+
+ Different methods of embedding arbitrary data in a Bitcoin transaction. +
+
OP_RETURN
+
Fake pubkey
+
Data may be embedded in an invalid public key in a P2PK or Bare multisig output. This is a heuristic filter and can be prone to false positives and false negatives.
+
Inscription
+
Data is embedded in the witness script of a taproot input.
+
+
+ +
Heuristics
+
+ These filters match common types of transactions according to subjective criteria. +
+
Coinjoin
+
A type of collaborative privacy-improving transaction.
+
Consolidation
+
The transaction condenses many inputs into a few outputs.
+
Batch payment
+
The transaction sends coins from a few inputs to many outputs.
+
+
+ +
Sighash Flags
+
+ Different ways of signing inputs to Bitcoin transactions. Note that selecting multiple sighash filters will highlight transactions in which each sighash flag is used, but not necessarily in the same input. +
+
sighash_all
+
sighash_none
+
sighash_single
+
sighash_default
+
sighash_anyonecanpay
+
+
+
+
+ The official mempool.space website is operated by The Mempool Open Source Project. See more information on our About page. There are also many unofficial instances of this website operated by individual members of the Bitcoin community. diff --git a/frontend/src/app/docs/api-docs/api-docs.component.scss b/frontend/src/app/docs/api-docs/api-docs.component.scss index f90274046..8a4150262 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.scss +++ b/frontend/src/app/docs/api-docs/api-docs.component.scss @@ -389,3 +389,44 @@ h3 { margin-bottom: 4rem; } } + +/* styles for nested definition lists */ +dl { + margin: 0; + padding: 0; +} + +dt { + font-weight: bold; + color: #4a68b9; + padding: 5px 0; +} + +dd { + padding: 2px 0; + + & > dl { + padding-left: 1em; + border-left: 2px solid #4a68b9; + margin-left: 1em; + margin-top: 5px; + } + + & > dl > dt { + display: inline; + font-weight: normal; + color: #e83e8c; + font-family: Consolas, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New; + text-transform: uppercase; + + &:before { + content: ""; + display: block; + } + } + + & > dl > dd { + display: inline; + margin-left: 1em; + } +} From 78857102f8cdaea05411e5cdfc38d8a7aeb7588e Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 17 Dec 2023 10:56:11 +0000 Subject: [PATCH 30/69] Move goggles icon inside generic svg-images component --- .../block-filters.component.html | 2 +- .../svg-images/svg-images.component.html | 8 +++++ .../components/svg-icons/goggles.component.ts | 34 ------------------- frontend/src/app/shared/shared.module.ts | 4 --- 4 files changed, 9 insertions(+), 39 deletions(-) delete mode 100644 frontend/src/app/shared/components/svg-icons/goggles.component.ts diff --git a/frontend/src/app/components/block-filters/block-filters.component.html b/frontend/src/app/components/block-filters/block-filters.component.html index 876a72ad1..7b1c2f9e5 100644 --- a/frontend/src/app/components/block-filters/block-filters.component.html +++ b/frontend/src/app/components/block-filters/block-filters.component.html @@ -5,7 +5,7 @@
diff --git a/frontend/src/app/components/svg-images/svg-images.component.html b/frontend/src/app/components/svg-images/svg-images.component.html index 5e8d7d29f..11dfe1d79 100644 --- a/frontend/src/app/components/svg-images/svg-images.component.html +++ b/frontend/src/app/components/svg-images/svg-images.component.html @@ -84,6 +84,14 @@ + + + + + + + + diff --git a/frontend/src/app/shared/components/svg-icons/goggles.component.ts b/frontend/src/app/shared/components/svg-icons/goggles.component.ts deleted file mode 100644 index b045e6acb..000000000 --- a/frontend/src/app/shared/components/svg-icons/goggles.component.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Component, Input } from '@angular/core'; - -@Component({ - selector: 'app-goggles-icon', - template: ` - - - - - - - `, -}) -export class GogglesIconComponent { - @Input() width: string = '100%'; - @Input() height: string = '100%'; -} diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index f772c3fd3..f092e81bc 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -5,7 +5,6 @@ import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontaweso import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faHammer, faDatabase, faExchangeAlt, faInfoCircle, faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faClock, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft, faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck, faCircleCheck, faUserCircle, faCheck } from '@fortawesome/free-solid-svg-icons'; -import { GogglesIconComponent } from './components/svg-icons/goggles.component'; import { InfiniteScrollModule } from 'ngx-infinite-scroll'; import { MenuComponent } from '../components/menu/menu.component'; import { PreviewTitleComponent } from '../components/master-page-preview/preview-title.component'; @@ -201,7 +200,6 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir AccelerationsListComponent, AccelerationStatsComponent, PendingStatsComponent, - GogglesIconComponent, ], imports: [ CommonModule, @@ -325,8 +323,6 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir OnlyVsizeDirective, OnlyWeightDirective, - - GogglesIconComponent, ] }) export class SharedModule { From 75b3bc62494b702f8101c6dacee4c432f921e69d Mon Sep 17 00:00:00 2001 From: natsee Date: Sun, 17 Dec 2023 19:15:23 +0100 Subject: [PATCH 31/69] Expand toggle for RBF history view --- .../rbf-timeline/rbf-timeline.component.html | 17 ++++++++++++++--- .../rbf-timeline/rbf-timeline.component.scss | 6 ++++++ .../rbf-timeline/rbf-timeline.component.ts | 6 ++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/components/rbf-timeline/rbf-timeline.component.html b/frontend/src/app/components/rbf-timeline/rbf-timeline.component.html index a2012d45f..8a750e624 100644 --- a/frontend/src/app/components/rbf-timeline/rbf-timeline.component.html +++ b/frontend/src/app/components/rbf-timeline/rbf-timeline.component.html @@ -1,7 +1,7 @@
-
-
+
+
@@ -13,7 +13,7 @@
-
+
+
+ + + + +
@@ -72,3 +81,5 @@ [isConnector]="hoverConnector" > -->
+ +{{ x }} remaining \ No newline at end of file diff --git a/frontend/src/app/components/rbf-timeline/rbf-timeline.component.scss b/frontend/src/app/components/rbf-timeline/rbf-timeline.component.scss index be7aef2d6..8afc3f026 100644 --- a/frontend/src/app/components/rbf-timeline/rbf-timeline.component.scss +++ b/frontend/src/app/components/rbf-timeline/rbf-timeline.component.scss @@ -34,6 +34,12 @@ &::-webkit-scrollbar { display: none; } + + .toggle-wrapper { + width: 100%; + text-align: center; + margin: 1.25em 0 0; + } } .intervals, .nodes { diff --git a/frontend/src/app/components/rbf-timeline/rbf-timeline.component.ts b/frontend/src/app/components/rbf-timeline/rbf-timeline.component.ts index 474da7326..687a7dbfd 100644 --- a/frontend/src/app/components/rbf-timeline/rbf-timeline.component.ts +++ b/frontend/src/app/components/rbf-timeline/rbf-timeline.component.ts @@ -25,7 +25,9 @@ function isTimelineCell(val: RbfTree | TimelineCell): boolean { export class RbfTimelineComponent implements OnInit, OnChanges { @Input() replacements: RbfTree; @Input() txid: string; + @Input() rowLimit: number = 5; // If explicitly set to 0, all timelines rows will be displayed by default rows: TimelineCell[][] = []; + timelineExpanded: boolean = this.rowLimit === 0; hoverInfo: RbfTree | null = null; tooltipPosition = null; @@ -191,6 +193,10 @@ export class RbfTimelineComponent implements OnInit, OnChanges { return rows; } + toggleTimeline(expand: boolean): void { + this.timelineExpanded = expand; + } + scrollToSelected() { const node = document.getElementById('node-' + this.txid); if (node) { From f72683dc197f34ce5a46b4e60bd1b9c51c909024 Mon Sep 17 00:00:00 2001 From: orangesurf Date: Mon, 18 Dec 2023 09:18:55 +0000 Subject: [PATCH 32/69] Add Mempool Goggles TM --- LICENSE | 8 ++++---- frontend/src/app/components/about/about.component.html | 2 +- .../trademark-policy/trademark-policy.component.html | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/LICENSE b/LICENSE index e5b707840..a5267e2e0 100644 --- a/LICENSE +++ b/LICENSE @@ -12,10 +12,10 @@ or any other contributor to The Mempool Open Source Project. The Mempool Open Source Project®, Mempool Accelerator™, Mempool Enterprise®, Mempool Liquidity™, mempool.space®, Be your own explorer™, Explore the full -Bitcoin ecosystem™, the mempool Logo, the mempool Square logo, the mempool Blocks -logo, the mempool Blocks 3 | 2 logo, the mempool.space Vertical Logo, and the -mempool.space Horizontal logo are registered trademarks or trademarks of Mempool -Space K.K in Japan, the United States, and/or other countries. +Bitcoin ecosystem™, Mempool Goggles™, the mempool Logo, the mempool Square logo, +the mempool Blocks logo, the mempool Blocks 3 | 2 logo, the mempool.space Vertical +Logo, and the mempool.space Horizontal logo are registered trademarks or trademarks +of MempoolSpace K.K in Japan, the United States, and/or other countries. See our full Trademark Policy and Guidelines for more details, published on . diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html index 98dcb4c31..b1a3069a6 100644 --- a/frontend/src/app/components/about/about.component.html +++ b/frontend/src/app/components/about/about.component.html @@ -422,7 +422,7 @@ Trademark Notice

- The Mempool Open Source Project®, Mempool Accelerator™, Mempool Enterprise®, Mempool Liquidity™, mempool.space®, Be your own explorer™, Explore the full Bitcoin ecosystem™, the mempool logo;, the mempool Square logo;, the mempool Blocks logo;, the mempool Blocks 3 | 2 logo;, the mempool.space Vertical Logo;, and the mempool.space Horizontal logo are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries. + The Mempool Open Source Project®, Mempool Accelerator™, Mempool Enterprise®, Mempool Liquidity™, mempool.space®, Be your own explorer™, Explore the full Bitcoin ecosystem™, Mempool Goggles™, the mempool logo;, the mempool Square logo;, the mempool Blocks logo;, the mempool Blocks 3 | 2 logo;, the mempool.space Vertical Logo;, and the mempool.space Horizontal logo are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries.

While our software is available under an open source software license, the copyright license does not include an implied right or license to use our trademarks. See our Trademark Policy and Guidelines for more details, published on <https://mempool.space/trademark-policy>. diff --git a/frontend/src/app/components/trademark-policy/trademark-policy.component.html b/frontend/src/app/components/trademark-policy/trademark-policy.component.html index 9d08c634d..b60e10f7b 100644 --- a/frontend/src/app/components/trademark-policy/trademark-policy.component.html +++ b/frontend/src/app/components/trademark-policy/trademark-policy.component.html @@ -62,6 +62,7 @@ mempool.space Be your own explorer Explore the full Bitcoin ecosystem + Mempool Goggles

@@ -314,7 +315,7 @@

Also, if you are using our Marks in a way described in the sections "Uses for Which We Are Granting a License," you must include the following trademark attribution at the foot of the webpage where you have used the Mark (or, if in a book, on the credits page), on any packaging or labeling, and on advertising or marketing materials:

-

"The Mempool Open Source Project®, Mempool Accelerator™, Mempool Enterprise®, Mempool Liquidity™, mempool.space®, Be your own explorer™, Explore the full Bitcoin ecosystem™, the mempool logo;, the mempool Square logo;, the mempool Blocks logo;, the mempool Blocks 3 | 2 logo;, the mempool.space Vertical Logo;, and the mempool.space Horizontal logo are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries, and are used with permission. Mempool Space K.K. has no affiliation with and does not sponsor or endorse the information provided herein."

+

"The Mempool Open Source Project®, Mempool Accelerator™, Mempool Enterprise®, Mempool Liquidity™, mempool.space®, Be your own explorer™, Explore the full Bitcoin ecosystem™, Mempool Goggles™, the mempool logo;, the mempool Square logo;, the mempool Blocks logo;, the mempool Blocks 3 | 2 logo;, the mempool.space Vertical Logo;, and the mempool.space Horizontal logo are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries, and are used with permission. Mempool Space K.K. has no affiliation with and does not sponsor or endorse the information provided herein."

  • What to Do When You See Abuse

  • From 03b78015ba012201929609c3ba51876d590c122e Mon Sep 17 00:00:00 2001 From: orangesurf Date: Mon, 18 Dec 2023 09:22:28 +0000 Subject: [PATCH 33/69] Remove redundant semicolons --- frontend/src/app/components/about/about.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html index b1a3069a6..dd2a4ead2 100644 --- a/frontend/src/app/components/about/about.component.html +++ b/frontend/src/app/components/about/about.component.html @@ -422,7 +422,7 @@ Trademark Notice

    - The Mempool Open Source Project®, Mempool Accelerator™, Mempool Enterprise®, Mempool Liquidity™, mempool.space®, Be your own explorer™, Explore the full Bitcoin ecosystem™, Mempool Goggles™, the mempool logo;, the mempool Square logo;, the mempool Blocks logo;, the mempool Blocks 3 | 2 logo;, the mempool.space Vertical Logo;, and the mempool.space Horizontal logo are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries. + The Mempool Open Source Project®, Mempool Accelerator™, Mempool Enterprise®, Mempool Liquidity™, mempool.space®, Be your own explorer™, Explore the full Bitcoin ecosystem™, Mempool Goggles™, the mempool logo, the mempool Square logo, the mempool Blocks logo, the mempool Blocks 3 | 2 logo, the mempool.space Vertical Logo, and the mempool.space Horizontal logo are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries.

    While our software is available under an open source software license, the copyright license does not include an implied right or license to use our trademarks. See our Trademark Policy and Guidelines for more details, published on <https://mempool.space/trademark-policy>. From 59f8a7199c45665107c5ff4fed441d0d67211348 Mon Sep 17 00:00:00 2001 From: orangesurf Date: Mon, 18 Dec 2023 09:35:14 +0000 Subject: [PATCH 34/69] Fix space --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index a5267e2e0..b6a09390a 100644 --- a/LICENSE +++ b/LICENSE @@ -15,7 +15,7 @@ Mempool Liquidity™, mempool.space®, Be your own explorer™, Explore the full Bitcoin ecosystem™, Mempool Goggles™, the mempool Logo, the mempool Square logo, the mempool Blocks logo, the mempool Blocks 3 | 2 logo, the mempool.space Vertical Logo, and the mempool.space Horizontal logo are registered trademarks or trademarks -of MempoolSpace K.K in Japan, the United States, and/or other countries. +of Mempool Space K.K in Japan, the United States, and/or other countries. See our full Trademark Policy and Guidelines for more details, published on . From bd07773b8d928b0715a7c45cde510ef019f5ca5d Mon Sep 17 00:00:00 2001 From: Pedro Date: Mon, 18 Dec 2023 12:05:05 +0000 Subject: [PATCH 35/69] Dot the i's on liquid logo --- .../liquid/liquid-network-preview.png | Bin 98482 -> 98357 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/frontend/src/resources/liquid/liquid-network-preview.png b/frontend/src/resources/liquid/liquid-network-preview.png index 72942110cd0e3dd4cf040d41d1456d9bde5ca25c..a66e9bb7aeb83f8394d2dadff914abfa499f293e 100644 GIT binary patch literal 98357 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%0AcU;~|o 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 Tk*a}-)-d;b-NA;5i%wKQX1)QuN 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 Date: Tue, 19 Dec 2023 13:17:26 +0700 Subject: [PATCH 36/69] Update frontend/src/app/components/transaction/transaction.component.ts Co-authored-by: mononaut <83316221+mononaut@users.noreply.github.com> --- .../src/app/components/transaction/transaction.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index f110c0435..1ee8a3ad6 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -439,7 +439,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { block_time: block.timestamp, }; this.stateService.markBlock$.next({ blockHeight: block.height }); - if (this.accelerationInfo && ['accelerating', 'mined', 'completed'].includes(this.accelerationInfo.status)) { + if (this.tx.acceleration || (this.accelerationInfo && ['accelerating', 'mined', 'completed'].includes(this.accelerationInfo.status))) { this.audioService.playSound('wind-chimes-harp-ascend'); } else { this.audioService.playSound('magic'); From 7f488f5b01048dce6552a69c72daa42e3147dbe9 Mon Sep 17 00:00:00 2001 From: natsee Date: Tue, 19 Dec 2023 14:40:37 +0100 Subject: [PATCH 37/69] Fade out the bottom of collapsed rbf history --- .../rbf-timeline/rbf-timeline.component.html | 4 ++-- .../rbf-timeline/rbf-timeline.component.scss | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/rbf-timeline/rbf-timeline.component.html b/frontend/src/app/components/rbf-timeline/rbf-timeline.component.html index 8a750e624..9ff35d669 100644 --- a/frontend/src/app/components/rbf-timeline/rbf-timeline.component.html +++ b/frontend/src/app/components/rbf-timeline/rbf-timeline.component.html @@ -1,6 +1,6 @@
    -
    +
    @@ -37,7 +37,7 @@
    -
    +
    diff --git a/frontend/src/app/components/rbf-timeline/rbf-timeline.component.scss b/frontend/src/app/components/rbf-timeline/rbf-timeline.component.scss index 8afc3f026..c0b38b59d 100644 --- a/frontend/src/app/components/rbf-timeline/rbf-timeline.component.scss +++ b/frontend/src/app/components/rbf-timeline/rbf-timeline.component.scss @@ -31,6 +31,19 @@ -ms-overflow-style: none; scrollbar-width: none; + .fade-out { + position: relative; + + &::before { + content: ''; + position: absolute; + width: 100%; + height: 100%; + background: linear-gradient(to bottom, rgba(36, 39, 62, 0) 0%, rgba(36, 39, 62, 1) 100%); + z-index: 1; + } + } + &::-webkit-scrollbar { display: none; } @@ -197,6 +210,10 @@ &.fullrbf { border-right: solid 10px #1bd8f4; } + &.last-pipe { + height: 150px; + bottom: -42px; + } } .corner { From fc36e04dc0500c0132c2dd90de65a7c7cc124a23 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Tue, 19 Dec 2023 08:20:21 -0700 Subject: [PATCH 38/69] update start9 os and icon --- README.md | 2 +- .../app/docs/api-docs/api-docs.component.html | 2 +- frontend/src/resources/profile/start9.png | Bin 14008 -> 49886 bytes 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e5779416d..e272c6b39 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Mempool can be conveniently installed on the following full-node distros: - [RaspiBlitz](https://github.com/rootzoll/raspiblitz) - [RoninDojo](https://code.samourai.io/ronindojo/RoninDojo) - [myNode](https://github.com/mynodebtc/mynode) -- [Start9](https://github.com/Start9Labs/embassy-os) +- [Start9](https://github.com/Start9Labs/start-os) - [nix-bitcoin](https://github.com/fort-nix/nix-bitcoin/blob/a1eacce6768ca4894f365af8f79be5bbd594e1c3/examples/configuration.nix#L129) **We highly recommend you deploy your own Mempool instance this way.** No matter which option you pick, you'll be able to get your own fully-sovereign instance of Mempool up quickly without needing to fiddle with any settings. diff --git a/frontend/src/app/docs/api-docs/api-docs.component.html b/frontend/src/app/docs/api-docs/api-docs.component.html index 49b11ad7b..77cf01326 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.html +++ b/frontend/src/app/docs/api-docs/api-docs.component.html @@ -373,7 +373,7 @@
    - We support one-click installation on a number of Raspberry Pi full-node distros including Umbrel, RaspiBlitz, MyNode, RoninDojo, and Start9's Embassy. + We support one-click installation on a number of Raspberry Pi full-node distros including Umbrel, RaspiBlitz, MyNode, RoninDojo, and StartOS. diff --git a/frontend/src/resources/profile/start9.png b/frontend/src/resources/profile/start9.png index 2cfe19ded9b1372494b8b11836ac62e5e9c90ad4..345a9869cf355fe25f27fa4d52132458a0d3f3aa 100644 GIT binary patch literal 49886 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
    -
    +
    @@ -50,15 +50,16 @@
    -
    - - - - -
    +
    +
    +
    + + + +
    diff --git a/frontend/src/app/components/rbf-timeline/rbf-timeline.component.scss b/frontend/src/app/components/rbf-timeline/rbf-timeline.component.scss index c0b38b59d..8962be63c 100644 --- a/frontend/src/app/components/rbf-timeline/rbf-timeline.component.scss +++ b/frontend/src/app/components/rbf-timeline/rbf-timeline.component.scss @@ -30,31 +30,32 @@ overflow-x: auto; -ms-overflow-style: none; scrollbar-width: none; - - .fade-out { - position: relative; - - &::before { - content: ''; - position: absolute; - width: 100%; - height: 100%; - background: linear-gradient(to bottom, rgba(36, 39, 62, 0) 0%, rgba(36, 39, 62, 1) 100%); - z-index: 1; - } - } - + &::-webkit-scrollbar { display: none; } + } - .toggle-wrapper { + .fade-out { + position: relative; + + &::before { + content: ''; + position: absolute; width: 100%; - text-align: center; - margin: 1.25em 0 0; + height: 70px; + top: -70px; + background: linear-gradient(to bottom, rgba(36, 39, 62, 0) 0%, rgba(36, 39, 62, 1) 100%); + z-index: 1; } } + .toggle-wrapper { + width: 100%; + text-align: center; + margin: 1.25em 0 0; + } + .intervals, .nodes { min-width: 100%; display: flex; From c3675d5b1c4d347a38204051302064d1b5f9e320 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 20 Dec 2023 18:30:19 +0000 Subject: [PATCH 41/69] Fix widget responsiveness on acceleration dashboard --- .../accelerations-list.component.html | 4 +- .../accelerations-list.component.scss | 67 ++++++++++++++++--- .../accelerator-dashboard.component.scss | 3 + 3 files changed, 61 insertions(+), 13 deletions(-) diff --git a/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.html b/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.html index 32012d363..f2265282f 100644 --- a/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.html +++ b/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.html @@ -1,10 +1,10 @@ -
    +

    Accelerations

    -
    +
    diff --git a/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.scss b/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.scss index 69aae18cc..4182abb68 100644 --- a/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.scss +++ b/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.scss @@ -14,11 +14,24 @@ .container-xl.legacy { max-width: 1140px; } +.container-xl.widget-container { + min-height: 335px; + @media (max-width: 767px) { + min-height: auto; + } +} .container { max-width: 100%; } +.acceleration-list { + min-height: 295px; + @media (max-width: 767px) { + min-height: auto; + } +} + tr, td, th { border: 0px; padding-top: 0.65rem !important; @@ -51,34 +64,63 @@ tr, td, th { .txid { width: 25%; - @media (max-width: 1100px) { - padding-right: 10px; - } - @media (max-width: 875px) { - display: none; - } overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 30%; + @media (max-width: 1060px) and (min-width: 768px) { + display: none; + } + @media (max-width: 500px) { + display: none; + } } -.fee { - width: 35%; -} - -.block { +.fee-rate { width: 20%; + @media (max-width: 1060px) and (min-width: 768px) { + text-align: start !important; + } + @media (max-width: 500px) { + text-align: start !important; + } + @media (max-width: 840px) and (min-width: 768px) { + display: none; + } + @media (max-width: 410px) { + display: none; + } } .bid { width: 30%; + min-width: 150px; + @media (max-width: 840px) and (min-width: 768px) { + text-align: start !important; + } + @media (max-width: 410px) { + text-align: start !important; + } } .time { width: 25%; } +.fee { + width: 35%; + @media (max-width: 1060px) and (min-width: 768px) { + text-align: start !important; + } + @media (max-width: 500px) { + text-align: start !important; + } +} + +.block { + width: 20%; +} + .status { width: 20% } @@ -122,4 +164,7 @@ tr, td, th { flex-direction: row; align-items: center; justify-content: center; + @media (max-width: 767px) { + height: 100px; + } } diff --git a/frontend/src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.scss b/frontend/src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.scss index 0d1c3b1c0..145569342 100644 --- a/frontend/src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.scss +++ b/frontend/src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.scss @@ -139,6 +139,9 @@ } .list-card { height: 410px; + @media (max-width: 767px) { + height: auto; + } } .mempool-block-wrapper { From 07f9410d457ce2273259e2e5c6243750c7d4381d Mon Sep 17 00:00:00 2001 From: wiz Date: Thu, 21 Dec 2023 05:05:59 +0900 Subject: [PATCH 42/69] ops: Fix elements unix socket path --- production/nginx/upstream-esplora.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/production/nginx/upstream-esplora.conf b/production/nginx/upstream-esplora.conf index 941f43566..b7fbbf163 100644 --- a/production/nginx/upstream-esplora.conf +++ b/production/nginx/upstream-esplora.conf @@ -2,7 +2,7 @@ upstream esplora-bitcoin-mainnet { server unix:/bitcoin/socket/esplora-bitcoin-mainnet fail_timeout=10s max_fails=10 weight=99999; } upstream esplora-liquid-mainnet { - server unix:/elements/socket/esplora-liquid-mainnet fail_timeout=10s max_fails=10 weight=99999; + server unix:/elements/socket/esplora-elements-liquid fail_timeout=10s max_fails=10 weight=99999; } upstream esplora-bitcoin-testnet { server unix:/bitcoin/socket/esplora-bitcoin-testnet fail_timeout=10s max_fails=10 weight=99999; @@ -11,5 +11,5 @@ upstream esplora-bitcoin-signet { server unix:/bitcoin/socket/esplora-bitcoin-signet fail_timeout=10s max_fails=10 weight=99999; } upstream esplora-liquid-testnet { - server unix:/elements/socket/esplora-liquid-testnet fail_timeout=10s max_fails=10 weight=99999; + server unix:/elements/socket/esplora-elements-liquidtestnet fail_timeout=10s max_fails=10 weight=99999; } From 0370cc896c50e2236e4b8abc815fb4a3d1e6e653 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Thu, 21 Dec 2023 13:06:52 +0000 Subject: [PATCH 43/69] Fix missing mempool fee chart when webgl disabled --- .../mempool-block/mempool-block.component.ts | 1 + frontend/src/app/services/websocket.service.ts | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) 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 bb6e7791f..197b07247 100644 --- a/frontend/src/app/components/mempool-block/mempool-block.component.ts +++ b/frontend/src/app/components/mempool-block/mempool-block.component.ts @@ -64,6 +64,7 @@ export class MempoolBlockComponent implements OnInit, OnDestroy { }), tap(() => { this.stateService.markBlock$.next({ mempoolBlockIndex: this.mempoolBlockIndex }); + this.websocketService.startTrackMempoolBlock(this.mempoolBlockIndex); }) ); diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts index 1e7f528eb..3c72252db 100644 --- a/frontend/src/app/services/websocket.service.ts +++ b/frontend/src/app/services/websocket.service.ts @@ -183,14 +183,18 @@ export class WebsocketService { } startTrackMempoolBlock(block: number) { - this.websocketSubject.next({ 'track-mempool-block': block }); - this.isTrackingMempoolBlock = true - this.trackingMempoolBlock = block + // skip duplicate tracking requests + if (this.trackingMempoolBlock !== block) { + this.websocketSubject.next({ 'track-mempool-block': block }); + this.isTrackingMempoolBlock = true; + this.trackingMempoolBlock = block; + } } stopTrackMempoolBlock() { this.websocketSubject.next({ 'track-mempool-block': -1 }); - this.isTrackingMempoolBlock = false + this.isTrackingMempoolBlock = false; + this.trackingMempoolBlock = null; } startTrackRbf(mode: 'all' | 'fullRbf') { From 40f2d553e20db8f8054f7ad909b830a1c47c7459 Mon Sep 17 00:00:00 2001 From: natsee Date: Fri, 22 Dec 2023 10:39:40 +0100 Subject: [PATCH 44/69] Fix faq nav bar overflow --- frontend/src/app/docs/api-docs/api-docs.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/docs/api-docs/api-docs.component.scss b/frontend/src/app/docs/api-docs/api-docs.component.scss index 8a4150262..27dfec61d 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.scss +++ b/frontend/src/app/docs/api-docs/api-docs.component.scss @@ -157,7 +157,7 @@ ul.no-bull.block-audit code{ position: fixed; top: 80px; overflow-y: auto; - height: calc(100vh - 50px); + height: calc(100vh - 75px); scrollbar-color: #2d3348 #11131f; scrollbar-width: thin; } From 93b074edf09b44c56c0b30cc21bc6d338d7f1c0b Mon Sep 17 00:00:00 2001 From: natsee Date: Wed, 27 Dec 2023 18:50:41 +0100 Subject: [PATCH 45/69] Add endpoints to docs api --- .../src/app/docs/api-docs/api-docs-data.ts | 1181 ++++++++++++++++- 1 file changed, 1172 insertions(+), 9 deletions(-) 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 97be0c2b1..c5319bf2a 100644 --- a/frontend/src/app/docs/api-docs/api-docs-data.ts +++ b/frontend/src/app/docs/api-docs/api-docs-data.ts @@ -209,6 +209,114 @@ export const restApiDocsData = [ } } }, + { + type: "endpoint", + category: "general", + httpRequestMethod: "GET", + fragment: "get-price", + title: "GET Price", + description: { + default: "Returns bitcoin latest price denominated in main currencies." + }, + urlString: "/v1/prices", + showConditions: [""], + showJsExamples: showJsExamplesDefaultFalse, + codeExample: { + default: { + codeTemplate: { + commonJS: ``, + esModule: ``, + curl: `/api/v1/prices`, + }, + codeSampleMainnet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + time: 1703252411, + USD: 43753, + EUR: 40545, + GBP: 37528, + CAD: 58123, + CHF: 37438, + AUD: 64499, + JPY: 6218915 +}` + }, + codeSampleTestnet: emptyCodeSample, + codeSampleSignet: emptyCodeSample, + codeSampleLiquid: emptyCodeSample, + codeSampleLiquidTestnet: emptyCodeSample, + codeSampleBisq: emptyCodeSample, + } + } + }, + { + type: "endpoint", + category: "general", + httpRequestMethod: "GET", + fragment: "get-historical-price", + title: "GET Historical Price", + description: { + default: "Returns bitcoin historical price denominated in main currencies." + }, + urlString: "/v1/historical-price", + showConditions: [""], + showJsExamples: showJsExamplesDefaultFalse, + codeExample: { + default: { + codeTemplate: { + commonJS: ``, + esModule: ``, + curl: `/api/v1/historical-price`, + }, + codeSampleMainnet: { + esModule: [], + commonJS: [], + curl: [], + response: `{ + prices: [ + { + time: 1703692800, + USD: 42972, + EUR: 39590, + GBP: 36803, + CAD: 56883, + CHF: 36486, + AUD: 63006, + JPY: 6124530 + }, + ... + { + time: 1279497600, + USD: 0.08584, + EUR: -1, + GBP: -1, + CAD: -1, + CHF: -1, + AUD: -1, + JPY: -1 + } + ], + exchangeRates: { + USDEUR: 0.92, + USDGBP: 0.86, + USDCAD: 1.32, + USDCHF: 0.85, + USDAUD: 1.47, + USDJPY: 142.52 + } +} +` + }, + codeSampleTestnet: emptyCodeSample, + codeSampleSignet: emptyCodeSample, + codeSampleLiquid: emptyCodeSample, + codeSampleLiquidTestnet: emptyCodeSample, + codeSampleBisq: emptyCodeSample, + } + } + }, { type: "endpoint", category: "general", @@ -1409,20 +1517,20 @@ export const restApiDocsData = [ ]` }, codeSampleSignet: { - esModule: [`1wiz18xYmhRX6xStj2b9t1rwWX4GKUgpv`], - commonJS: [`1wiz18xYmhRX6xStj2b9t1rwWX4GKUgpv`], - curl: [`1wiz18xYmhRX6xStj2b9t1rwWX4GKUgpv`], + esModule: [`tb1pu8ysre22dcl6qy5m5w7mjwutw73w4u24slcdh4myq06uhr6q29dqwc3ckt`], + commonJS: [`tb1pu8ysre22dcl6qy5m5w7mjwutw73w4u24slcdh4myq06uhr6q29dqwc3ckt`], + curl: [`tb1pu8ysre22dcl6qy5m5w7mjwutw73w4u24slcdh4myq06uhr6q29dqwc3ckt`], response: `[ { - txid: "e58b47f657b496a083ad9a4fb10c744d5e993028efd9cfba149885334d98bdf5", + txid: "c56a054302df8f8f80c5ac6b86b24ed52bf41d64de640659837c56bc33d10c9e", vout: 0, status: { confirmed: true, - block_height: 698571, - block_hash: "00000000000000000007536c0a664a7d2a01c31569623183eba0768d9a0c163d", - block_time: 1630520708 + block_height: 174923, + block_hash: "000000750e335ff355be2e3754fdada30d107d7d916aef07e2f5d014bec845e5", + block_time: 1703321003 }, - value: 642070789 + value: 546 }, ... ]` @@ -1470,6 +1578,65 @@ export const restApiDocsData = [ } } }, + { + type: "endpoint", + category: "addresses", + httpRequestMethod: "GET", + fragment: "get-address-validate", + title: "GET Address Validation", + description: { + default: "Returns whether an address is valid or not. Available fields: isvalid (boolean), address (string), scriptPubKey (string), isscript (boolean), iswitness (boolean), witness_version (numeric, optional), and witness_program (string, optional).", + }, + urlString: "/v1/validate-address/:address", + showConditions: bitcoinNetworks, + showJsExamples: showJsExamplesDefaultFalse, + codeExample: { + default: { + codeTemplate: { + curl: `/api/v1/validate-address/%{1}`, + commonJS: ``, + esModule: ``, + }, + codeSampleMainnet: { + curl: [`1KFHE7w8BhaENAswwryaoccDb6qcT6DbYY`], + response: `{ + isvalid: true, + address: "1KFHE7w8BhaENAswwryaoccDb6qcT6DbYY", + scriptPubKey: "76a914c825a1ecf2a6830c4401620c3a16f1995057c2ab88ac", + isscript: false, + iswitness: false +}` + }, + codeSampleTestnet: { + curl: [`tb1q4kgratttzjvkxfmgd95z54qcq7y6hekdm3w56u`], + response: `{ + isvalid: true, + address: "tb1q4kgratttzjvkxfmgd95z54qcq7y6hekdm3w56u", + scriptPubKey: "0014ad903ead6b149963276869682a54180789abe6cd", + isscript: false, + iswitness: true, + witness_version: 0, + witness_program: "ad903ead6b149963276869682a54180789abe6cd" +}` + }, + codeSampleSignet: { + curl: [`tb1pu8ysre22dcl6qy5m5w7mjwutw73w4u24slcdh4myq06uhr6q29dqwc3ckt`], + response: `{ + isvalid: true, + address: "tb1pu8ysre22dcl6qy5m5w7mjwutw73w4u24slcdh4myq06uhr6q29dqwc3ckt", + scriptPubKey: "5120e1c901e54a6e3fa0129ba3bdb93b8b77a2eaf15587f0dbd76403f5cb8f40515a", + isscript: true, + iswitness: true, + witness_version: 1, + witness_program: "e1c901e54a6e3fa0129ba3bdb93b8b77a2eaf15587f0dbd76403f5cb8f40515a" +}` + }, + codeSampleLiquid: emptyCodeSample, + codeSampleLiquidTestnet: emptyCodeSample, + codeSampleBisq: emptyCodeSample, + } + } + }, { type: "category", category: "assets", @@ -2109,6 +2276,61 @@ export const restApiDocsData = [ } } }, + { + type: "endpoint", + category: "blocks", + httpRequestMethod: "GET", + fragment: "get-block-timestamp", + title: "GET Block Timestamp", + description: { + default: "Returns the height and the hash of the block closest to the given :timestamp." + }, + urlString: "/v1/mining/blocks/timestamp/:timestamp", + showConditions: bitcoinNetworks, + showJsExamples: showJsExamplesDefaultFalse, + codeExample: { + default: { + codeTemplate: { + curl: `/api/v1/mining/blocks/timestamp/%{1}`, + commonJS: ``, + esModule: `` + }, + codeSampleMainnet: { + esModule: [], + commonJS: [], + curl: ['1672531200'], + response: `{ + height: 769786, + hash: "000000000000000000017f6405c2382de84944eb21be9cec0379a735813f137b", + timestamp: "2022-12-31T23:30:31.000Z" +}` + }, + codeSampleTestnet: { + esModule: [], + commonJS: [], + curl: ['1672531200'], + response: `{ + height: 2413838, + hash: "00000000000000082888e2353ea4baaea04d2e0e88f2ee054ad2bbcc1d6a5469", + timestamp: "2022-12-31T23:57:26.000Z" +}` + }, + codeSampleSignet: { + esModule: [], + commonJS: [], + curl: ['1672531200'], + response: `{ + height: 123713, + hash: "0000010c6df8ffe1684ab9d7cfac69836a4538c057fab4571b809120fe486c96", + timestamp: "2022-12-31T23:55:56.000Z" +}` + }, + codeSampleLiquid: emptyCodeSample, + codeSampleLiquidTestnet: emptyCodeSample, + codeSampleBisq: emptyCodeSample, + } + } + }, { type: "endpoint", category: "blocks", @@ -4042,6 +4264,101 @@ export const restApiDocsData = [ } } }, + { + type: "endpoint", + category: "mining", + httpRequestMethod: "GET", + fragment: "get-difficulty-adjustments", + title: "GET Difficulty Adjustments", + description: { + default: "

    Returns the record of difficulty adjustments over the specified trailing :interval:

    • Block timestamp
    • Block height
    • Difficulty
    • Difficulty change

    If no time interval is specified, all available data is returned." + }, + urlString: "/v1/mining/difficulty-adjustments/[:interval]", + showConditions: bitcoinNetworks, + showJsExamples: showJsExamplesDefaultFalse, + codeExample: { + default: { + codeTemplate: { + curl: `/api/v1/mining/difficulty-adjustments/1m`, + commonJS: ``, + esModule: `` + }, + codeSampleMainnet: { + esModule: [], + commonJS: [], + curl: [], + response: `[ + [ + 1703311464, + 822528, + 72006146478567.1, + 1.06983 + ], + [ + 1702180644, + 820512, + 67305906902031.39, + 0.990408 + ], + [ + 1700957763, + 818496, + 67957790298897.88, + 1.0507 + ] +]` + }, + codeSampleTestnet: { + esModule: [], + commonJS: [], + curl: [], + response: `[ + [ + 1703429523, + 2544008, + 105074715.9955905, + 105075000 + ], + [ + 1703426009, + 2544005, + 1, + 0 + ], + [ + 1703422944, + 2544000, + 105074715.9955905, + 105075000 + ], + ... +]` + }, + codeSampleSignet: { + esModule: [], + commonJS: [], + curl: [], + response: `[ + [ + 1702402252, + 173376, + 0.002967416960321784, + 1.01893 + ], + [ + 1701214807, + 171360, + 0.002912289751655253, + 0.9652 + ] +]` + }, + codeSampleLiquid: emptyCodeSample, + codeSampleLiquidTestnet: emptyCodeSample, + codeSampleBisq: emptyCodeSample, + } + } + }, { type: "endpoint", category: "mining", @@ -4559,6 +4876,407 @@ export const restApiDocsData = [ }, ... ] +}` + }, + codeSampleLiquid: emptyCodeSample, + codeSampleLiquidTestnet: emptyCodeSample, + codeSampleBisq: emptyCodeSample, + } + } + }, + { + type: "endpoint", + category: "mining", + httpRequestMethod: "GET", + fragment: "get-block-predictions", + title: "GET Block Predictions", + description: { + default: "

    Returns average block health in the specified :timePeriod, ordered oldest to newest. :timePeriod can be any of the following: " + miningTimeIntervals + ".

    For 24h and 3d time periods, every block is included and figures are exact (not averages). For the 1w time period, figures may be averages depending on how fast blocks were found around a particular timestamp. For other time periods, figures are averages.

    " + }, + urlString: ["/v1/mining/blocks/predictions/:timePeriod"], + showConditions: bitcoinNetworks, + showJsExamples: showJsExamplesDefaultFalse, + codeExample: { + default: { + codeTemplate: { + curl: `/api/v1/mining/blocks/predictions/%{1}`, + commonJS: ``, + esModule: `` + }, + codeSampleMainnet: { + esModule: [], + commonJS: [], + curl: [`3y`], + response: `[ + [ + 1687247274, + 777625, + 100 + ], + [ + 1687066238, + 788621, + 99.85 + ], + [ + 1687263518, + 795182, + 99.46 + ], + [ + 1687312271, + 795260, + 100 + ], + ... +]` + }, + codeSampleTestnet: { + esModule: [], + commonJS: [], + curl: [`3y`], + response: `[ + [ + 1687246773, + 2429248, + 100 + ], + [ + 1687285500, + 2438380, + 100 + ], + [ + 1687342820, + 2438467, + 100 + ], + [ + 1687372143, + 2438522, + 100 + ], + ... +]` + }, + codeSampleSignet: { + esModule: [], + commonJS: [], + curl: [`3y`], + response: `[ + [ + 1687246696, + 129639, + 0 + ], + [ + 1687303289, + 148191, + 0 + ], + [ + 1687315093, + 148218, + 0 + ], + [ + 1687368211, + 148312, + 0 + ], + ... +]` + }, + codeSampleLiquid: emptyCodeSample, + codeSampleLiquidTestnet: emptyCodeSample, + codeSampleBisq: emptyCodeSample, + } + } + }, + { + type: "endpoint", + category: "mining", + httpRequestMethod: "GET", + fragment: "get-block-audit-score", + title: "GET Block Audit Score", + description: { + default: "Returns the block audit score for the specified :blockHash. Available fields: hash, matchRate, expectedFees, and expectedWeight." + }, + urlString: ["/v1/mining/blocks/audit/score/:blockHash"], + showConditions: bitcoinNetworks, + showJsExamples: showJsExamplesDefaultFalse, + codeExample: { + default: { + codeTemplate: { + curl: `/api/v1/mining/blocks/audit/score/%{1}`, + commonJS: ``, + esModule: `` + }, + codeSampleMainnet: { + esModule: [], + commonJS: [], + curl: [`000000000000000000032535698c5b0c48283b792cf86c1c6e36ff84464de785`], + response: `{ + hash: "000000000000000000032535698c5b0c48283b792cf86c1c6e36ff84464de785", + matchRate: 99.66, + expectedFees: 12090955, + expectedWeight: 3991988 +}` + }, + codeSampleTestnet: { + esModule: [], + commonJS: [], + curl: [`000000000000025a66f30a181e438b9f65ef33cec3014b7a4ff4c7578289cd6e`], + response: `{ + hash: "000000000000025a66f30a181e438b9f65ef33cec3014b7a4ff4c7578289cd6e", + matchRate: 100, + expectedFees: 579169, + expectedWeight: 12997 +}` + }, + codeSampleSignet: { + esModule: [], + commonJS: [], + curl: [`000000c1491d7d4229d4bf07e0dcaa7e396767b45be388e1174c7439a9490121`], + response: `{ + hash: "000000c1491d7d4229d4bf07e0dcaa7e396767b45be388e1174c7439a9490121", + matchRate: 100, + expectedFees: 80520, + expectedWeight: 16487 +}` + }, + codeSampleLiquid: emptyCodeSample, + codeSampleLiquidTestnet: emptyCodeSample, + codeSampleBisq: emptyCodeSample, + } + } + }, + { + type: "endpoint", + category: "mining", + httpRequestMethod: "GET", + fragment: "get-blocks-audit-scores", + title: "GET Blocks Audit Scores", + description: { + default: "Returns blocks audit score for the past 16 blocks. If :startHeight is specified, the past 15 blocks before (and including) :startHeight are returned. Available fields: hash, matchRate, expectedFees, and expectedWeight." + }, + urlString: ["/v1/mining/blocks/audit/scores/:startHeight"], + showConditions: bitcoinNetworks, + showJsExamples: showJsExamplesDefaultFalse, + codeExample: { + default: { + codeTemplate: { + curl: `/api/v1/mining/blocks/audit/scores/%{1}`, + commonJS: ``, + esModule: `` + }, + codeSampleMainnet: { + esModule: [], + commonJS: [], + curl: [`820000`], + response: `[ + { + hash: "000000000000000000034cd3689507da0386d3d1790dd56f2e6945e650e02c74", + matchRate: 100, + expectedFees: 225828975, + expectedWeight: 3991756 + }, + { + hash: "00000000000000000000b3ad97907e99c54e6b9145a8f77842e59d9c0c8377cf", + matchRate: 100, + expectedFees: 295107022, + expectedWeight: 3991752 + }, + ... +]` + }, + codeSampleTestnet: { + esModule: [], + commonJS: [], + curl: [`2566570`], + response: `[ + { + hash: "00000000000002e7e96e7b5ee04a5fbb3ef9575a9f4a99effb32a8a89d9d2f19", + matchRate: 100, + expectedFees: 964677, + expectedWeight: 24959 + }, + { + hash: "00000000000003bd3962806d0e06d9982eb2e06aeba912687b2bac3668db32aa", + matchRate: 100, + expectedFees: 631200, + expectedWeight: 15516 + }, + ... +]` + }, + codeSampleSignet: { + esModule: [], + commonJS: [], + curl: [`175504`], + response: `[ + { + hash: "00000012d54289925efc151f2e111e0775e80c3b6bb4b0dcd3ff01dec4bbc5d0", + matchRate: 100, + expectedFees: 4767, + expectedWeight: 2524 + }, + { + hash: "00000031e269cf0b567260b01ae11453175f4598fdb4f1908c5e2f4265b9d93a", + matchRate: 100, + expectedFees: 9090, + expectedWeight: 1851 + }, + ... +]` + }, + codeSampleLiquid: emptyCodeSample, + codeSampleLiquidTestnet: emptyCodeSample, + codeSampleBisq: emptyCodeSample, + } + } + }, + { + type: "endpoint", + category: "mining", + httpRequestMethod: "GET", + fragment: "get-block-audit-summary", + title: "GET Block Audit Summary", + description: { + default: "Returns the block audit summary for the specified :blockHash. Available fields: height, id, timestamp, template, missingTxs, addedTxs, freshTxs, sigopTxs, fullrbfTxs, acceleratedTxs, matchRate, expectedFees, and expectedWeight." + }, + urlString: ["/v1/block/:blockHash/audit-summary"], + showConditions: bitcoinNetworks, + showJsExamples: showJsExamplesDefaultFalse, + codeExample: { + default: { + codeTemplate: { + curl: `/api/v1/block/%{1}/audit-summary`, + commonJS: ``, + esModule: `` + }, + codeSampleMainnet: { + esModule: [], + commonJS: [], + curl: [`00000000000000000000f218ceda7a5d9c289040b9c3f05ef9f7c2f4930e0123`], + response: `{ + height: 822418, + id: "00000000000000000000f218ceda7a5d9c289040b9c3f05ef9f7c2f4930e0123", + timestamp: 1703262962, + template: [ + { + txid: "1de119e4fe0fb92378de74a59fec337c39d505bbc0d04d20d151cc3fb7a91bf0", + fee: 92000, + vsize: 140.25, + value: 354245800, + rate: 655.9714795008913, + flags: 1099511631881 + }, + ... + ], + missingTxs: [], + addedTxs: [ + "3036565d1af6c5b14876a255cdf06214aa350e62154d1ce8619c8e933d0526f8", + "aaa9d8e8f1de712574182a618b4d608f96f39bfc55e296d2e5904561cdef2e77", + ... + ], + freshTxs: [ + "8ede292d8f0319cbe79fff9fd47564cd7f78fad74d7c506d2b157399ff41d904" + ], + sigopTxs: [], + fullrbfTxs: [ + "271e7792910a4ea134c02c03c9d7477b32a8531a5dd92fbc4dbf3ca70614fcce", + "634a5b2de393f0f5b4eeb335bee75c1779b1f2308a07e86cafb95894aa4734d0", + ... + ], + acceleratedTxs: [], + matchRate: 100, + expectedFees: 169464627, + expectedWeight: 3991702 +}` + }, + codeSampleTestnet: { + esModule: [], + commonJS: [], + curl: [`000000000000007cfba94e051326b3546c968a188a7e12e340a78cefc586bfe3`], + response: `{ + height: 2566708, + id: "000000000000007cfba94e051326b3546c968a188a7e12e340a78cefc586bfe3", + timestamp: 1703684826, + template: [ + { + txid: "6556caa3c6bff537f04837a6f7182dd7a253f31a46de4f21dec9584720156d35", + fee: 109707, + vsize: 264.75, + value: 456855, + rate: 414.37960339943345, + flags: 9895621445642 + }, + { + txid: "53b7743b8cfa0108dbcdc7c2f5e661b9d8f56216845a439449d7f9dfc466b147", + fee: 74640, + vsize: 215.5, + value: 19063915, + rate: 348.5338491295938, + flags: 1099528491017 + }, + ... + ], + missingTxs: [ + "8f2eae756119e43054ce1014a06e81d612113794d8b519e6ff393d7e0023396a", + "012b44b0fc0fddc549a056c85850f03a83446c843504c588cd5829873b30f5a9", + ... + ], + addedTxs: [], + freshTxs: [ + "af36a8b88f6c19f997614dfc8a41395190eaf496a49e8db393dacb770999abd5", + "fdfa272c8fe069573b964ddad605d748d8c737e94dfcd09bddaae0ee0a2445df", + ... + ], + sigopTxs: [], + fullrbfTxs: [], + acceleratedTxs: [], + matchRate: 86.96, + expectedFees: 1541639, + expectedWeight: 26425 +}` + }, + codeSampleSignet: { + esModule: [], + commonJS: [], + curl: [`0000008acf5177d07f1d648f4d54f26095936a5d29a0a6145dd75a0415e63c0f`], + response: `{ + height: 175519, + id: "0000008acf5177d07f1d648f4d54f26095936a5d29a0a6145dd75a0415e63c0f", + timestamp: 1703682844, + template: [ + { + txid: "f95b38742c483b81dc4ff49a803bae7625f1596ec5756c944d7586dfe8b38250", + fee: 3766, + vsize: 172.25, + value: 115117171776, + rate: 21.86357039187228, + flags: 1099528425481 + }, + { + txid: "8665c4d05732c930c2037bc0220e4ab9b1b64ce3302363ff7d118827c7347b52", + fee: 3766, + vsize: 172.25, + value: 115116509429, + rate: 21.86357039187228, + flags: 1099528425481 + }, + ... + ], + missingTxs: [], + addedTxs: [], + freshTxs: [], + sigopTxs: [], + fullrbfTxs: [], + acceleratedTxs: [], + matchRate: 100, + expectedFees: 10494, + expectedWeight: 6582 }` }, codeSampleLiquid: emptyCodeSample, @@ -5127,6 +5845,267 @@ export const restApiDocsData = [ } } }, + { + type: "endpoint", + category: "mempool", + httpRequestMethod: "GET", + fragment: "get-mempool-rbf", + title: "GET Mempool RBF Transactions", + description: { + default: "Returns the list of mempool transactions that are part of a RBF chain." + }, + urlString: "/v1/replacements", + showConditions: bitcoinNetworks, + showJsExamples: showJsExamplesDefaultFalse, + codeExample: { + default: { + codeTemplate: { + curl: `/api/v1/replacements`, + commonJS: ``, + esModule: ``, + }, + codeSampleMainnet: { + curl: [], + response: `[ + { + tx: { + txid: "1ca4b22006e57b1b13f5cc89a41cf7c9e99fe225aabf407251e4fe0268f22d93", + fee: 14983, + vsize: 141.5, + value: 343934, + rate: 105.886925795053, + rbf: true, + fullRbf: false + }, + time: 1703331467, + fullRbf: false, + replaces: [ + { + tx: { + txid: "9f8e30674af641bb153a35254d539468e1d847b16bbdc13ce23b5a970b0b11cf", + fee: 13664, + vsize: 141.25, + value: 345253, + rate: 96.7362831858407, + rbf: true + }, + time: 1703331398, + interval: 69, + fullRbf: false, + replaces: [] + } + ] + }, + ... +]` + }, + codeSampleTestnet: { + curl: [], + response: `[ + { + tx: { + txid: "7766e3f008011b776905f96fcad9d4a7b75d1b368d1e77db2901254f1fa8357d", + fee: 9101, + vsize: 317, + value: 147706698, + rate: 28.709779179810724, + rbf: true, + fullRbf: false + }, + time: 1703331325, + fullRbf: false, + replaces: [ + { + tx: { + txid: "43055f6e5750c6aa0c2214e59e99f367398d96bde935e7666c3e648d249a4e40", + fee: 7000, + vsize: 317, + value: 147708799, + rate: 22.082018927444796, + rbf: true + }, + time: 1703331154, + interval: 171, + fullRbf: false, + replaces: [] + } + ] + }, + ... +]` + }, + codeSampleSignet: { + curl: [], + response: `[ + { + tx: { + txid: "13985a5717a1ea54ce720cd6b70421b1667061be491a6799acf6dea01c551248", + fee: 5040, + vsize: 215.5, + value: 762745, + rate: 23.387470997679813, + rbf: true, + fullRbf: false, + mined: true + }, + time: 1703316271, + fullRbf: false, + replaces: [ + { + tx: { + txid: "eac5ec8487414c955f4a5d3b2e516c351aec5299f1335f9019a00907962386ce", + fee: 4560, + vsize: 215.25, + value: 763225, + rate: 21.18466898954704, + rbf: true + }, + time: 1703316270, + interval: 1, + fullRbf: false, + replaces: [] + } + ], + mined: true + } +]` + }, + codeSampleLiquid: emptyCodeSample, + codeSampleLiquidTestnet: emptyCodeSample, + codeSampleBisq: emptyCodeSample, + } + } + }, + { + type: "endpoint", + category: "mempool", + httpRequestMethod: "GET", + fragment: "get-mempool-fullrbf", + title: "GET Mempool Full RBF Transactions", + description: { + default: "Returns the list of mempool transactions that are part of a Full-RBF chain." + }, + urlString: "/v1/fullrbf/replacements", + showConditions: bitcoinNetworks, + showJsExamples: showJsExamplesDefaultFalse, + codeExample: { + default: { + codeTemplate: { + curl: `/api/v1/fullrbf/replacements`, + commonJS: ``, + esModule: ``, + }, + codeSampleMainnet: { + curl: [], + response: `[ + { + tx: { + txid: "25e2bfaf0e0821e5cb71f11e460b2f71e1d5a3755015de42544afa5fbad6d443", + fee: 24436, + vsize: 297.75, + value: 273418, + rate: 82.0688497061293, + rbf: false, + fullRbf: true + }, + time: 1703409882, + fullRbf: true, + replaces: [ + { + tx: { + txid: "07d501e8ad4a25f07f3ced0a6102741720f710765e6fdb2eb966ba0df657997a", + fee: 24138, + vsize: 297.75, + value: 273716, + rate: 81.06801007556675, + rbf: false + }, + time: 1703409853, + interval: 29, + fullRbf: true, + replaces: [] + } + ] + }, + ... +]` + }, + codeSampleTestnet: { + curl: [], + response: `[ + { + tx: { + txid: "25e2bfaf0e0821e5cb71f11e460b2f71e1d5a3755015de42544afa5fbad6d443", + fee: 24436, + vsize: 297.75, + value: 273418, + rate: 82.0688497061293, + rbf: false, + fullRbf: true + }, + time: 1703409882, + fullRbf: true, + replaces: [ + { + tx: { + txid: "07d501e8ad4a25f07f3ced0a6102741720f710765e6fdb2eb966ba0df657997a", + fee: 24138, + vsize: 297.75, + value: 273716, + rate: 81.06801007556675, + rbf: false + }, + time: 1703409853, + interval: 29, + fullRbf: true, + replaces: [] + } + ] + }, + ... +]` + }, + codeSampleSignet: { + curl: [], + response: `[ + { + tx: { + txid: "25e2bfaf0e0821e5cb71f11e460b2f71e1d5a3755015de42544afa5fbad6d443", + fee: 24436, + vsize: 297.75, + value: 273418, + rate: 82.0688497061293, + rbf: false, + fullRbf: true + }, + time: 1703409882, + fullRbf: true, + replaces: [ + { + tx: { + txid: "07d501e8ad4a25f07f3ced0a6102741720f710765e6fdb2eb966ba0df657997a", + fee: 24138, + vsize: 297.75, + value: 273716, + rate: 81.06801007556675, + rbf: false + }, + time: 1703409853, + interval: 29, + fullRbf: true, + replaces: [] + } + ] + }, + ... +]` + }, + codeSampleLiquid: emptyCodeSample, + codeSampleLiquidTestnet: emptyCodeSample, + codeSampleBisq: emptyCodeSample, + } + } + }, { type: "category", category: "transactions", @@ -5143,7 +6122,7 @@ export const restApiDocsData = [ description: { default: "Returns the ancestors and the best descendant fees for a transaction." }, - urlString: "/v1/fees/cpfp", + urlString: "/v1/cpfp", showConditions: bitcoinNetworks.concat(liquidNetworks), showJsExamples: showJsExamplesDefault, codeExample: { @@ -6000,6 +6979,148 @@ export const restApiDocsData = [ } } }, + { + type: "endpoint", + category: "transactions", + httpRequestMethod: "GET", + fragment: "get-transaction-rbf-history", + title: "GET Transaction RBF History", + description: { + default: "Returns the RBF tree history of a transaction." + }, + urlString: "v1/tx/:txId/rbf", + showConditions: bitcoinNetworks, + showJsExamples: showJsExamplesDefaultFalse, + codeExample: { + default: { + codeTemplate: { + curl: `/api/v1/tx/%{1}/rbf`, + commonJS: ``, + esModule: ``, + }, + codeSampleMainnet: { + curl: [`2e95ff9094df9f3650e3f2abc189250760162be89a88f9f2f23301c7cb14b8b4`], + response: `{ + replacements: { + tx: { + txid: "2e95ff9094df9f3650e3f2abc189250760162be89a88f9f2f23301c7cb14b8b4", + fee: 1668, + vsize: 276.75, + value: 14849, + rate: 4.824207492795389, + rbf: false, + fullRbf: true + }, + time: 1703240261, + fullRbf: true, + replaces: [ + { + tx: { + txid: "3f4670463daadffed07d7a1060071b07f7e81a2566eca21d78bb513cbf21c82a", + fee: 420, + vsize: 208.25, + value: 4856, + rate: 2.0168067226890756, + rbf: false + }, + time: 1702870898, + interval: 369363, + fullRbf: true, + replaces: [] + } + ... + ] + }, + replaces: [ + "3f4670463daadffed07d7a1060071b07f7e81a2566eca21d78bb513cbf21c82a", + "92f9b4f719d0ffc9035d3a9767d80c940cecbc656df2243bafd33f52b583ee92" + ] +}` + }, + codeSampleTestnet: { + curl: [`5faaa30530bee55de8cc896bdf48f803c2274a94bffc2842386bec2a8bf7a813`], + response: `{ + replacements: { + tx: { + txid: "5faaa30530bee55de8cc896bdf48f803c2274a94bffc2842386bec2a8bf7a813", + fee: 9101, + vsize: 318, + value: 148022607, + rate: 28.61949685534591, + rbf: true, + fullRbf: false, + mined: true + }, + time: 1703322610, + fullRbf: false, + replaces: [ + { + tx: { + txid: "06e69641fa889fe9148669ac2904929004e7140087bedaec8c8e4e05aabded52", + fee: 7000, + vsize: 318, + value: 148024708, + rate: 22.0125786163522, + rbf: true + }, + time: 1703322602, + interval: 8, + fullRbf: false, + replaces: [] + } + ], + mined: true + }, + replaces: [ + "06e69641fa889fe9148669ac2904929004e7140087bedaec8c8e4e05aabded52" + ] +}` + }, + codeSampleSignet: { + curl: [`13985a5717a1ea54ce720cd6b70421b1667061be491a6799acf6dea01c551248`], + response: `{ + replacements: { + tx: { + txid: "13985a5717a1ea54ce720cd6b70421b1667061be491a6799acf6dea01c551248", + fee: 5040, + vsize: 215.5, + value: 762745, + rate: 23.387470997679813, + rbf: true, + fullRbf: false, + mined: true + }, + time: 1703316272, + fullRbf: false, + replaces: [ + { + tx: { + txid: "eac5ec8487414c955f4a5d3b2e516c351aec5299f1335f9019a00907962386ce", + fee: 4560, + vsize: 215.25, + value: 763225, + rate: 21.18466898954704, + rbf: true + }, + time: 1703316270, + interval: 2, + fullRbf: false, + replaces: [] + } + ], + mined: true + }, + replaces: [ + "eac5ec8487414c955f4a5d3b2e516c351aec5299f1335f9019a00907962386ce" + ] +}` + }, + codeSampleLiquid: emptyCodeSample, + codeSampleLiquidTestnet: emptyCodeSample, + codeSampleBisq: emptyCodeSample, + } + } + }, { type: "endpoint", category: "transactions", @@ -6098,6 +7219,48 @@ export const restApiDocsData = [ } } }, + { + type: "endpoint", + category: "transactions", + httpRequestMethod: "GET", + fragment: "get-transaction-times", + title: "GET Transaction Times", + description: { + default: "Returns the timestamps when a list of unconfirmed transactions was initially observed in the mempool. If a transaction is not found in the mempool or has been mined, the timestamp will be 0." + }, + urlString: "/v1/transaction-times", + showConditions: bitcoinNetworks.concat(liquidNetworks), + showJsExamples: showJsExamplesDefaultFalse, + codeExample: { + default: { + codeTemplate: { + curl: `/api/v1/transaction-times?txId[]=%{1}&txId[]=%{2}`, + commonJS: ``, + esModule: ``, + }, + codeSampleMainnet: { + curl: ['51545ef0ec7f09196e60693b59369a134870985c8a90e5d42655b191de06285e', '6086089bd1c56a9c42a39d470cdfa7c12d4b52bf209608b390dfc4943f2d3851'], + response: `[1703082129,1702325558]` + }, + codeSampleTestnet: { + curl: ['25e7a95ebf10ed192ee91741653d8d970ac88f8e0cd6fb14cc6c7145116d3964', '1e158327e52acae35de94962e60e53fc70f6b175b0cfc3e2058bed4b895203b4'], + response: `[1703267563,1703267322]` + }, + codeSampleSignet: { + curl: ['8af0c5199acd89621244f2f61107fe5a9c7c7aad54928e8400651d03ca949aeb', '08f840f7b0c33c5b0fdadf1666e8a8c206836993d95fc1eeeef39b5ef9de03d0'], + response: `[1703267652,1703267696]` + }, + codeSampleLiquid: { + curl: ['6091498f06a3054f82a0c3e5be0a23030185c658dc3568684b0bccc4e759be11', '631212a073aa4ca392e3aeb469d1366ec2ee288988b106e4a6fc8dae8c4d7a9a'], + response: `[1703267652,1703267696]`, + }, + codeSampleLiquidTestnet: { + curl: ['fa8d43e47b2c4bbee12fd8bc1c7440028be2da6ac0f1df6ac77c983938c503fb', '26b12cd450f8fa8b6a527578db218bf212a60b2d5eb65c168f8eb3be6f5fd991'], + response: `[1703268185,1703268209]`, + }, + } + } + }, { type: "endpoint", category: "transactions", From d2a5adbd9d234aa5023cd43d7c73eb8b35902944 Mon Sep 17 00:00:00 2001 From: natsee Date: Thu, 28 Dec 2023 15:34:36 +0100 Subject: [PATCH 46/69] Fix duplicate address field in search results --- .../search-form/search-results/search-results.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/components/search-form/search-results/search-results.component.html b/frontend/src/app/components/search-form/search-results/search-results.component.html index d4f68edbd..6c096fc4e 100644 --- a/frontend/src/app/components/search-form/search-results/search-results.component.html +++ b/frontend/src/app/components/search-form/search-results/search-results.component.html @@ -35,7 +35,7 @@ - +
    Bitcoin Addresses
    + + + +
    TXIDAccelerated
    Accelerated
    diff --git a/frontend/src/app/components/block/block-preview.component.ts b/frontend/src/app/components/block/block-preview.component.ts index c4dfe40df..49417508f 100644 --- a/frontend/src/app/components/block/block-preview.component.ts +++ b/frontend/src/app/components/block/block-preview.component.ts @@ -2,7 +2,7 @@ import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/co import { ActivatedRoute, ParamMap } from '@angular/router'; import { ElectrsApiService } from '../../services/electrs-api.service'; import { switchMap, tap, throttleTime, catchError, shareReplay, startWith, pairwise, filter } from 'rxjs/operators'; -import { of, Subscription, asyncScheduler } from 'rxjs'; +import { of, Subscription, asyncScheduler, forkJoin } from 'rxjs'; import { StateService } from '../../services/state.service'; import { SeoService } from '../../services/seo.service'; import { OpenGraphService } from '../../services/opengraph.service'; @@ -121,21 +121,37 @@ export class BlockPreviewComponent implements OnInit, OnDestroy { this.overviewSubscription = block$.pipe( startWith(null), pairwise(), - switchMap(([prevBlock, block]) => this.apiService.getStrippedBlockTransactions$(block.id) - .pipe( - catchError((err) => { - this.overviewError = err; - this.openGraphService.fail('block-viz-' + this.rawId); - return of([]); - }), - switchMap((transactions) => { - return of({ transactions, direction: 'down' }); - }) - ) + switchMap(([prevBlock, block]) => { + return forkJoin([ + this.apiService.getStrippedBlockTransactions$(block.id) + .pipe( + catchError((err) => { + this.overviewError = err; + this.openGraphService.fail('block-viz-' + this.rawId); + return of([]); + }), + switchMap((transactions) => { + return of(transactions); + }) + ), + block.height > 819500 ? this.apiService.getAccelerationHistory$({ blockHash: block.id }) : of([]) + ]); + } ), ) - .subscribe(({transactions, direction}: {transactions: TransactionStripped[], direction: string}) => { + .subscribe(([transactions, accelerations]) => { this.strippedTransactions = transactions; + + const acceleratedInBlock = {}; + for (const acc of accelerations) { + acceleratedInBlock[acc.txid] = acc; + } + for (const tx of transactions) { + if (acceleratedInBlock[tx.txid]) { + tx.acc = true; + } + } + this.isLoadingOverview = false; if (this.blockGraph) { this.blockGraph.destroy(); diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts index bb83494c5..d3fef00a6 100644 --- a/frontend/src/app/components/block/block.component.ts +++ b/frontend/src/app/components/block/block.component.ts @@ -328,17 +328,28 @@ export class BlockComponent implements OnInit, OnDestroy { this.overviewError = err; return of(null); }) - ) + ), + block.height > 819500 ? this.apiService.getAccelerationHistory$({ blockHash: block.id }) : of([]) ]); }) ) - .subscribe(([transactions, blockAudit]) => { + .subscribe(([transactions, blockAudit, accelerations]) => { if (transactions) { this.strippedTransactions = transactions; } else { this.strippedTransactions = []; } + const acceleratedInBlock = {}; + for (const acc of accelerations) { + acceleratedInBlock[acc.txid] = acc; + } + for (const tx of transactions) { + if (acceleratedInBlock[tx.txid]) { + tx.acc = true; + } + } + this.blockAudit = null; if (transactions && blockAudit) { const inTemplate = {}; From fab82551cfa6e6a16c3d5d24e9fc753385d47ce3 Mon Sep 17 00:00:00 2001 From: wiz Date: Sun, 31 Dec 2023 03:22:14 +0900 Subject: [PATCH 48/69] Remove old GPLv3 license file, rename AGPLv3 license to COPYING --- LICENSE.AGPL-3.md => COPYING.md | 0 LICENSE.GPL-3.md | 675 -------------------------------- 2 files changed, 675 deletions(-) rename LICENSE.AGPL-3.md => COPYING.md (100%) delete mode 100644 LICENSE.GPL-3.md diff --git a/LICENSE.AGPL-3.md b/COPYING.md similarity index 100% rename from LICENSE.AGPL-3.md rename to COPYING.md diff --git a/LICENSE.GPL-3.md b/LICENSE.GPL-3.md deleted file mode 100644 index 2fb2e74d8..000000000 --- a/LICENSE.GPL-3.md +++ /dev/null @@ -1,675 +0,0 @@ -### GNU GENERAL PUBLIC LICENSE - -Version 3, 29 June 2007 - -Copyright (C) 2007 Free Software Foundation, Inc. - - -Everyone is permitted to copy and distribute verbatim copies of this -license document, but changing it is not allowed. - -### Preamble - -The GNU General Public License is a free, copyleft license for -software and other kinds of works. - -The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom -to share and change all versions of a program--to make sure it remains -free software for all its users. We, the Free Software Foundation, use -the GNU General Public License for most of our software; it applies -also to any other work released this way by its authors. You can apply -it to your programs, too. - -When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - -To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you -have certain responsibilities if you distribute copies of the -software, or if you modify it: responsibilities to respect the freedom -of others. - -For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - -Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - -For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - -Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the -manufacturer can do so. This is fundamentally incompatible with the -aim of protecting users' freedom to change the software. The -systematic pattern of such abuse occurs in the area of products for -individuals to use, which is precisely where it is most unacceptable. -Therefore, we have designed this version of the GPL to prohibit the -practice for those products. If such problems arise substantially in -other domains, we stand ready to extend this provision to those -domains in future versions of the GPL, as needed to protect the -freedom of users. - -Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish -to avoid the special danger that patents applied to a free program -could make it effectively proprietary. To prevent this, the GPL -assures that patents cannot be used to render the program non-free. - -The precise terms and conditions for copying, distribution and -modification follow. - -### TERMS AND CONDITIONS - -#### 0. Definitions. - -"This License" refers to version 3 of the GNU General Public License. - -"Copyright" also means copyright-like laws that apply to other kinds -of works, such as semiconductor masks. - -"The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - -To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of -an exact copy. The resulting work is called a "modified version" of -the earlier work or a work "based on" the earlier work. - -A "covered work" means either the unmodified Program or a work based -on the Program. - -To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - -To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user -through a computer network, with no transfer of a copy, is not -conveying. - -An interactive user interface displays "Appropriate Legal Notices" to -the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - -#### 1. Source Code. - -The "source code" for a work means the preferred form of the work for -making modifications to it. "Object code" means any non-source form of -a work. - -A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - -The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - -The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - -The Corresponding Source need not include anything that users can -regenerate automatically from other parts of the Corresponding Source. - -The Corresponding Source for a work in source code form is that same -work. - -#### 2. Basic Permissions. - -All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - -You may make, run and propagate covered works that you do not convey, -without conditions so long as your license otherwise remains in force. -You may convey covered works to others for the sole purpose of having -them make modifications exclusively for you, or provide you with -facilities for running those works, provided that you comply with the -terms of this License in conveying all material for which you do not -control copyright. Those thus making or running the covered works for -you must do so exclusively on your behalf, under your direction and -control, on terms that prohibit them from making any copies of your -copyrighted material outside their relationship with you. - -Conveying under any other circumstances is permitted solely under the -conditions stated below. Sublicensing is not allowed; section 10 makes -it unnecessary. - -#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - -No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - -When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such -circumvention is effected by exercising rights under this License with -respect to the covered work, and you disclaim any intention to limit -operation or modification of the work as a means of enforcing, against -the work's users, your or third parties' legal rights to forbid -circumvention of technological measures. - -#### 4. Conveying Verbatim Copies. - -You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - -You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - -#### 5. Conveying Modified Source Versions. - -You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these -conditions: - -- a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. -- b) The work must carry prominent notices stating that it is - released under this License and any conditions added under - section 7. This requirement modifies the requirement in section 4 - to "keep intact all notices". -- c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. -- d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - -A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - -#### 6. Conveying Non-Source Forms. - -You may convey a covered work in object code form under the terms of -sections 4 and 5, provided that you also convey the machine-readable -Corresponding Source under the terms of this License, in one of these -ways: - -- a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. -- b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the Corresponding - Source from a network server at no charge. -- c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. -- d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. -- e) Convey the object code using peer-to-peer transmission, - provided you inform other peers where the object code and - Corresponding Source of the work are being offered to the general - public at no charge under subsection 6d. - -A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - -A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, -family, or household purposes, or (2) anything designed or sold for -incorporation into a dwelling. In determining whether a product is a -consumer product, doubtful cases shall be resolved in favor of -coverage. For a particular product received by a particular user, -"normally used" refers to a typical or common use of that class of -product, regardless of the status of the particular user or of the way -in which the particular user actually uses, or expects or is expected -to use, the product. A product is a consumer product regardless of -whether the product has substantial commercial, industrial or -non-consumer uses, unless such uses represent the only significant -mode of use of the product. - -"Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to -install and execute modified versions of a covered work in that User -Product from a modified version of its Corresponding Source. The -information must suffice to ensure that the continued functioning of -the modified object code is in no case prevented or interfered with -solely because modification has been made. - -If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - -The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or -updates for a work that has been modified or installed by the -recipient, or for the User Product in which it has been modified or -installed. Access to a network may be denied when the modification -itself materially and adversely affects the operation of the network -or violates the rules and protocols for communication across the -network. - -Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - -#### 7. Additional Terms. - -"Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - -When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - -Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders -of that material) supplement the terms of this License with terms: - -- a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or -- b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or -- c) Prohibiting misrepresentation of the origin of that material, - or requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or -- d) Limiting the use for publicity purposes of names of licensors - or authors of the material; or -- e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or -- f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions - of it) with contractual assumptions of liability to the recipient, - for any liability that these contractual assumptions directly - impose on those licensors and authors. - -All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - -If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - -Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; the -above requirements apply either way. - -#### 8. Termination. - -You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - -However, if you cease all violation of this License, then your license -from a particular copyright holder is reinstated (a) provisionally, -unless and until the copyright holder explicitly and finally -terminates your license, and (b) permanently, if the copyright holder -fails to notify you of the violation by some reasonable means prior to -60 days after the cessation. - -Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - -Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - -#### 9. Acceptance Not Required for Having Copies. - -You are not required to accept this License in order to receive or run -a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - -#### 10. Automatic Licensing of Downstream Recipients. - -Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - -An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - -You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - -#### 11. Patents. - -A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - -A contributor's "essential patent claims" are all patent claims owned -or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - -Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - -In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - -If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - -If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - -A patent license is "discriminatory" if it does not include within the -scope of its coverage, prohibits the exercise of, or is conditioned on -the non-exercise of one or more of the rights that are specifically -granted under this License. You may not convey a covered work if you -are a party to an arrangement with a third party that is in the -business of distributing software, under which you make payment to the -third party based on the extent of your activity of conveying the -work, and under which the third party grants, to any of the parties -who would receive the covered work from you, a discriminatory patent -license (a) in connection with copies of the covered work conveyed by -you (or copies made from those copies), or (b) primarily for and in -connection with specific products or compilations that contain the -covered work, unless you entered into that arrangement, or that patent -license was granted, prior to 28 March 2007. - -Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - -#### 12. No Surrender of Others' Freedom. - -If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under -this License and any other pertinent obligations, then as a -consequence you may not convey it at all. For example, if you agree to -terms that obligate you to collect a royalty for further conveying -from those to whom you convey the Program, the only way you could -satisfy both those terms and this License would be to refrain entirely -from conveying the Program. - -#### 13. Use with the GNU Affero General Public License. - -Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - -#### 14. Revised Versions of this License. - -The Free Software Foundation may publish revised and/or new versions -of the GNU General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in -detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies that a certain numbered version of the GNU General Public -License "or any later version" applies to it, you have the option of -following the terms and conditions either of that numbered version or -of any later version published by the Free Software Foundation. If the -Program does not specify a version number of the GNU General Public -License, you may choose any version ever published by the Free -Software Foundation. - -If the Program specifies that a proxy can decide which future versions -of the GNU General Public License can be used, that proxy's public -statement of acceptance of a version permanently authorizes you to -choose that version for the Program. - -Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - -#### 15. Disclaimer of Warranty. - -THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT -WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND -PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE -DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR -CORRECTION. - -#### 16. Limitation of Liability. - -IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR -CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES -ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT -NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR -LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM -TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER -PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -#### 17. Interpretation of Sections 15 and 16. - -If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - -END OF TERMS AND CONDITIONS - -### How to Apply These Terms to Your New Programs - -If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these -terms. - -To do so, attach the following notices to the program. It is safest to -attach them to the start of each source file to most effectively state -the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper -mail. - -If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands \`show w' and \`show c' should show the -appropriate parts of the General Public License. Of course, your -program's commands might be different; for a GUI interface, you would -use an "about box". - -You should also get your employer (if you work as a programmer) or -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. For more information on this, and how to apply and follow -the GNU GPL, see . - -The GNU General Public License does not permit incorporating your -program into proprietary programs. If your program is a subroutine -library, you may consider it more useful to permit linking proprietary -applications with the library. If this is what you want to do, use the -GNU Lesser General Public License instead of this License. But first, -please read . From 681f9a1c694cf70900da60bbedc62e2224445fb3 Mon Sep 17 00:00:00 2001 From: wiz Date: Sun, 31 Dec 2023 03:39:23 +0900 Subject: [PATCH 49/69] ops: Enable redis for production mainnet --- production/mempool-config.mainnet.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/production/mempool-config.mainnet.json b/production/mempool-config.mainnet.json index 0aa90aa9c..36310e59d 100644 --- a/production/mempool-config.mainnet.json +++ b/production/mempool-config.mainnet.json @@ -106,7 +106,12 @@ "node206.tk7.mempool.space" ] }, - "MEMPOOL_SERVICES": { + "REDIS": { + "ENABLED": true, + "UNIX_SOCKET_PATH": "/tmp/redis.sock", + "BATCH_QUERY_BASE_SIZE": 5000 + }, + "MEMPOOL_SERVICES": { "API": "https://mempool.space/api/v1/services", "ACCELERATIONS": true } From 23ececca956beec76540d49fca782150b0933739 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 31 Dec 2023 18:05:25 +0000 Subject: [PATCH 50/69] Optimize websocket updates on new block --- backend/src/api/websocket-handler.ts | 54 ++++++++++++++----- .../mempool-block-overview.component.ts | 38 ++++++++++++- 2 files changed, 77 insertions(+), 15 deletions(-) diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index b082573f1..34d4682d2 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -282,8 +282,7 @@ class WebsocketHandler { } if (Object.keys(response).length) { - const serializedResponse = this.serializeResponse(response); - client.send(serializedResponse); + client.send(this.serializeResponse(response)); } } catch (e) { logger.debug(`Error parsing websocket message from ${client['remoteAddress']}: ` + (e instanceof Error ? e.message : e)); @@ -392,8 +391,7 @@ class WebsocketHandler { } if (Object.keys(response).length) { - const serializedResponse = this.serializeResponse(response); - client.send(serializedResponse); + client.send(this.serializeResponse(response)); } }); } @@ -639,8 +637,7 @@ class WebsocketHandler { } if (Object.keys(response).length) { - const serializedResponse = this.serializeResponse(response); - client.send(serializedResponse); + client.send(this.serializeResponse(response)); } }); } @@ -738,10 +735,13 @@ class WebsocketHandler { } } + const confirmedTxids: { [txid: string]: boolean } = {}; + // Update mempool to remove transactions included in the new block for (const txId of txIds) { delete _memPool[txId]; rbfCache.mined(txId); + confirmedTxids[txId] = true; } if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) { @@ -773,6 +773,8 @@ class WebsocketHandler { 'fees': fees, }); + const mBlocksWithTransactions = mempoolBlocks.getMempoolBlocksWithTransactions(); + const responseCache = { ...this.socketData }; function getCachedResponse(key, data): string { if (!responseCache[key]) { @@ -808,7 +810,7 @@ class WebsocketHandler { if (client['track-tx']) { const trackTxid = client['track-tx']; - if (trackTxid && txIds.indexOf(trackTxid) > -1) { + if (trackTxid && confirmedTxids[trackTxid]) { response['txConfirmed'] = JSON.stringify(trackTxid); } else { const mempoolTx = _memPool[trackTxid]; @@ -880,17 +882,24 @@ class WebsocketHandler { if (client['track-mempool-block'] >= 0 && memPool.isInSync()) { const index = client['track-mempool-block']; - if (mBlockDeltas && mBlockDeltas[index]) { - response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-${index}`, { - index: index, - delta: mBlockDeltas[index], - }); + + if (mBlockDeltas && mBlockDeltas[index] && mBlocksWithTransactions[index]?.transactions?.length) { + if (mBlockDeltas[index].added.length > (mBlocksWithTransactions[index]?.transactions.length / 2)) { + response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-full-${index}`, { + index: index, + blockTransactions: mBlocksWithTransactions[index].transactions, + }); + } else { + response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-delta-${index}`, { + index: index, + delta: mBlockDeltas[index], + }); + } } } if (Object.keys(response).length) { - const serializedResponse = this.serializeResponse(response); - client.send(serializedResponse); + client.send(this.serializeResponse(response)); } }); } @@ -951,10 +960,27 @@ class WebsocketHandler { private printLogs(): void { if (this.wss) { + let numTxSubs = 0; + let numProjectedSubs = 0; + let numRbfSubs = 0; + + this.wss.clients.forEach((client) => { + if (client['track-tx']) { + numTxSubs++; + } + if (client['track-mempool-block'] >= 0) { + numProjectedSubs++; + } + if (client['track-rbf']) { + numRbfSubs++; + } + }) + const count = this.wss?.clients?.size || 0; const diff = count - this.numClients; this.numClients = count; logger.debug(`${count} websocket clients | ${this.numConnected} connected | ${this.numDisconnected} disconnected | (${diff >= 0 ? '+' : ''}${diff})`); + logger.debug(`websocket subscriptions: track-tx: ${numTxSubs}, track-mempool-block: ${numProjectedSubs} track-rbf: ${numRbfSubs}`); this.numConnected = 0; this.numDisconnected = 0; } 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 4beda043a..f09b8f5ea 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 @@ -35,6 +35,8 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang blockSub: Subscription; deltaSub: Subscription; + firstLoad: boolean = true; + constructor( public stateService: StateService, private websocketService: WebsocketService, @@ -58,7 +60,40 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang ) .pipe(switchMap(() => this.stateService.mempoolBlockTransactions$)) .subscribe((transactionsStripped) => { - this.replaceBlock(transactionsStripped); + if (this.firstLoad) { + this.replaceBlock(transactionsStripped); + } else { + const inOldBlock = {}; + const inNewBlock = {}; + const added: TransactionStripped[] = []; + const changed: { txid: string, rate: number | undefined, acc: boolean | undefined }[] = []; + const removed: string[] = []; + for (const tx of transactionsStripped) { + inNewBlock[tx.txid] = true; + } + for (const txid of Object.keys(this.blockGraph?.scene?.txs || {})) { + inOldBlock[txid] = true; + if (!inNewBlock[txid]) { + removed.push(txid); + } + } + for (const tx of transactionsStripped) { + if (!inOldBlock[tx.txid]) { + added.push(tx); + } else { + changed.push({ + txid: tx.txid, + rate: tx.rate, + acc: tx.acc + }); + } + } + this.updateBlock({ + removed, + changed, + added + }); + } }); this.deltaSub = this.stateService.mempoolBlockDelta$.subscribe((delta) => { this.updateBlock(delta); @@ -67,6 +102,7 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang ngOnChanges(changes): void { if (changes.index) { + this.firstLoad = true; if (this.blockGraph) { this.blockGraph.clear(changes.index.currentValue > changes.index.previousValue ? this.chainDirection : this.poolDirection); } From 9b6e60bfbaccc489e44f01a5c661d6e380641a8e Mon Sep 17 00:00:00 2001 From: wiz Date: Mon, 1 Jan 2024 08:01:06 +0900 Subject: [PATCH 51/69] ops: Add production/redis.conf --- production/redis.conf | 79 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 production/redis.conf diff --git a/production/redis.conf b/production/redis.conf new file mode 100644 index 000000000..f1ed13a68 --- /dev/null +++ b/production/redis.conf @@ -0,0 +1,79 @@ +save 3600 1 300 100 60 10000 +unixsocket /tmp/redis.sock +unixsocketperm 666 + +bind 127.0.0.1 -::1 +protected-mode yes +port 6379 +tcp-backlog 511 +timeout 0 +tcp-keepalive 300 +daemonize yes +pidfile /var/run/redis/redis.pid +loglevel notice +logfile /var/log/redis/redis.log +databases 16 +always-show-logo no +set-proc-title yes +proc-title-template "{title} {listen-addr} {server-mode}" +locale-collate "" +stop-writes-on-bgsave-error yes +rdbcompression yes +rdbchecksum yes +dbfilename dump.rdb +rdb-del-sync-files no +dir /var/db/redis/ +replica-serve-stale-data yes +replica-read-only yes +repl-diskless-sync yes +repl-diskless-sync-delay 5 +repl-diskless-sync-max-replicas 0 +repl-diskless-load disabled +repl-disable-tcp-nodelay no +replica-priority 100 +acllog-max-len 128 +lazyfree-lazy-eviction no +lazyfree-lazy-expire no +lazyfree-lazy-server-del no +replica-lazy-flush no +lazyfree-lazy-user-del no +lazyfree-lazy-user-flush no +oom-score-adj no +oom-score-adj-values 0 200 800 +disable-thp yes +appendonly no +appendfilename "appendonly.aof" +appenddirname "appendonlydir" +appendfsync everysec +no-appendfsync-on-rewrite no +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb +aof-load-truncated yes +aof-use-rdb-preamble yes +aof-timestamp-enabled no + +slowlog-log-slower-than 10000 +slowlog-max-len 128 +latency-monitor-threshold 0 +notify-keyspace-events "" +hash-max-listpack-entries 512 +hash-max-listpack-value 64 +list-max-listpack-size -2 +list-compress-depth 0 +set-max-intset-entries 512 +set-max-listpack-entries 128 +set-max-listpack-value 64 +zset-max-listpack-entries 128 +zset-max-listpack-value 64 +hll-sparse-max-bytes 3000 +stream-node-max-bytes 4096 +stream-node-max-entries 100 +activerehashing yes +client-output-buffer-limit normal 0 0 0 +client-output-buffer-limit replica 256mb 64mb 60 +client-output-buffer-limit pubsub 32mb 8mb 60 +hz 10 +dynamic-hz yes +aof-rewrite-incremental-fsync yes +rdb-save-incremental-fsync yes +jemalloc-bg-thread yes From 51bfffbf0ee60930efa7204d8e0276c887b6d440 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 1 Jan 2024 12:01:53 +0000 Subject: [PATCH 52/69] unsubscribe from mempool block tracking --- .../src/app/components/mempool-block/mempool-block.component.ts | 1 + 1 file changed, 1 insertion(+) 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 197b07247..89fb97dad 100644 --- a/frontend/src/app/components/mempool-block/mempool-block.component.ts +++ b/frontend/src/app/components/mempool-block/mempool-block.component.ts @@ -75,6 +75,7 @@ export class MempoolBlockComponent implements OnInit, OnDestroy { ngOnDestroy(): void { this.stateService.markBlock$.next({}); + this.websocketService.stopTrackMempoolBlock(); } getOrdinal(mempoolBlock: MempoolBlock): string { From 09041c50efdf8ea5df2bbc0b3e3dfe5dfb6a98cf Mon Sep 17 00:00:00 2001 From: softsimon Date: Tue, 2 Jan 2024 10:35:52 +0700 Subject: [PATCH 53/69] Acceleration flag check on block view --- frontend/src/app/components/block/block-preview.component.ts | 2 +- frontend/src/app/components/block/block.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/block/block-preview.component.ts b/frontend/src/app/components/block/block-preview.component.ts index 49417508f..e634ae11f 100644 --- a/frontend/src/app/components/block/block-preview.component.ts +++ b/frontend/src/app/components/block/block-preview.component.ts @@ -134,7 +134,7 @@ export class BlockPreviewComponent implements OnInit, OnDestroy { return of(transactions); }) ), - block.height > 819500 ? this.apiService.getAccelerationHistory$({ blockHash: block.id }) : of([]) + this.stateService.env.ACCELERATOR === true && block.height > 819500 ? this.apiService.getAccelerationHistory$({ blockHash: block.id }) : of([]) ]); } ), diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts index d3fef00a6..6a995127b 100644 --- a/frontend/src/app/components/block/block.component.ts +++ b/frontend/src/app/components/block/block.component.ts @@ -329,7 +329,7 @@ export class BlockComponent implements OnInit, OnDestroy { return of(null); }) ), - block.height > 819500 ? this.apiService.getAccelerationHistory$({ blockHash: block.id }) : of([]) + this.stateService.env.ACCELERATOR === true && block.height > 819500 ? this.apiService.getAccelerationHistory$({ blockHash: block.id }) : of([]) ]); }) ) From a498f9205bd4407dd8a93efbf91c12e1ba52dfec Mon Sep 17 00:00:00 2001 From: softsimon Date: Tue, 2 Jan 2024 11:25:50 +0700 Subject: [PATCH 54/69] Fix error on transaction page when acceleration is disabled --- frontend/src/app/components/transaction/transaction.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index 1ee8a3ad6..409ba33ff 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -242,6 +242,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { }); this.fetchAccelerationSubscription = this.fetchAcceleration$.pipe( + filter(() => this.stateService.env.ACCELERATOR === true), tap(() => { this.accelerationInfo = null; }), From c1785993a8e364756f2e8cdbc7c2a2ca2d099589 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 2 Jan 2024 16:26:57 +0000 Subject: [PATCH 55/69] Fix websocket subscription logging --- 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 34d4682d2..937d4a7c5 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -968,7 +968,7 @@ class WebsocketHandler { if (client['track-tx']) { numTxSubs++; } - if (client['track-mempool-block'] >= 0) { + if (client['track-mempool-block'] != null && client['track-mempool-block'] >= 0) { numProjectedSubs++; } if (client['track-rbf']) { From f73350f7d641505597ce53b060e3cc359ccec278 Mon Sep 17 00:00:00 2001 From: natsee Date: Wed, 3 Jan 2024 14:21:11 +0100 Subject: [PATCH 56/69] [Liquid] Fix missing assets/featured route --- frontend/src/app/liquid/liquid-master-page.module.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/src/app/liquid/liquid-master-page.module.ts b/frontend/src/app/liquid/liquid-master-page.module.ts index 10d87bc4b..17fa6b8e5 100644 --- a/frontend/src/app/liquid/liquid-master-page.module.ts +++ b/frontend/src/app/liquid/liquid-master-page.module.ts @@ -10,6 +10,7 @@ import { PushTransactionComponent } from '../components/push-transaction/push-tr import { BlocksList } from '../components/blocks-list/blocks-list.component'; import { AssetGroupComponent } from '../components/assets/asset-group/asset-group.component'; import { AssetsComponent } from '../components/assets/assets.component'; +import { AssetsFeaturedComponent } from '../components/assets/assets-featured/assets-featured.component' import { AssetComponent } from '../components/asset/asset.component'; import { AssetsNavComponent } from '../components/assets/assets-nav/assets-nav.component'; @@ -73,6 +74,11 @@ const routes: Routes = [ data: { networks: ['liquid'] }, component: AssetsComponent, }, + { + path: 'featured', + data: { networks: ['liquid'] }, + component: AssetsFeaturedComponent, + }, { path: 'asset/:id', data: { networkSpecific: true }, From d665d2a12cb5bfc48c20a78e162b3e1365d74210 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sat, 6 Jan 2024 03:42:45 +0000 Subject: [PATCH 57/69] Handle unmineable transactions in GBT implementations --- backend/rust-gbt/src/gbt.rs | 11 +++++++---- backend/src/api/tx-selection-worker.ts | 3 +++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/backend/rust-gbt/src/gbt.rs b/backend/rust-gbt/src/gbt.rs index fb28dc299..412d4e5e9 100644 --- a/backend/rust-gbt/src/gbt.rs +++ b/backend/rust-gbt/src/gbt.rs @@ -127,7 +127,7 @@ pub fn gbt(mempool: &mut ThreadTransactionsMap, accelerations: &[ThreadAccelerat let next_from_stack = next_valid_from_stack(&mut mempool_stack, &audit_pool); let next_from_queue = next_valid_from_queue(&mut modified, &audit_pool); if next_from_stack.is_none() && next_from_queue.is_none() { - continue; + break; } let (next_tx, from_stack) = match (next_from_stack, next_from_queue) { (Some(stack_tx), Some(queue_tx)) => match queue_tx.cmp(stack_tx) { @@ -203,10 +203,13 @@ pub fn gbt(mempool: &mut ThreadTransactionsMap, accelerations: &[ThreadAccelerat 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 - if !transactions.is_empty() { - blocks.push(transactions); - block_weights.push(block_weight); + if transactions.is_empty() { + break; } + + blocks.push(transactions); + block_weights.push(block_weight); + // reset for the next block transactions = Vec::with_capacity(initial_txes_per_block); block_weight = BLOCK_RESERVED_WEIGHT; diff --git a/backend/src/api/tx-selection-worker.ts b/backend/src/api/tx-selection-worker.ts index 0acc2f65e..8ac7328fe 100644 --- a/backend/src/api/tx-selection-worker.ts +++ b/backend/src/api/tx-selection-worker.ts @@ -173,10 +173,13 @@ function makeBlockTemplates(mempool: Map) // this block is full const exceededPackageTries = failures > 1000 && blockWeight > (config.MEMPOOL.BLOCK_WEIGHT_UNITS - 4000); const queueEmpty = top >= mempoolArray.length && modified.isEmpty(); + if ((exceededPackageTries || queueEmpty) && blocks.length < 7) { // construct this block if (transactions.length) { blocks.push(transactions.map(t => t.uid)); + } else { + break; } // reset for the next block transactions = []; From 5f66a954023eddf805dd0022d53ee2763d2dafb1 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 7 Jan 2024 18:02:11 +0000 Subject: [PATCH 58/69] Smooth out irregular mempool block updates --- .../block-overview-graph/block-scene.ts | 2 +- .../mempool-block-overview.component.ts | 43 +++++++++++++------ 2 files changed, 32 insertions(+), 13 deletions(-) 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 cb589527d..adcf736fc 100644 --- a/frontend/src/app/components/block-overview-graph/block-scene.ts +++ b/frontend/src/app/components/block-overview-graph/block-scene.ts @@ -11,7 +11,7 @@ export default class BlockScene { getColor: ((tx: TxView) => Color) = defaultColorFunction; orientation: string; flip: boolean; - animationDuration: number = 1000; + animationDuration: number = 900; configAnimationOffset: number | null; animationOffset: number; highlightingEnabled: boolean; 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 f09b8f5ea..84c9a3416 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 @@ -3,8 +3,8 @@ import { Component, ComponentRef, ViewChild, HostListener, Input, Output, EventE import { StateService } from '../../services/state.service'; import { MempoolBlockDelta, TransactionStripped } from '../../interfaces/websocket.interface'; import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component'; -import { Subscription, BehaviorSubject, merge, of } from 'rxjs'; -import { switchMap, filter } from 'rxjs/operators'; +import { Subscription, BehaviorSubject, merge, of, timer } from 'rxjs'; +import { switchMap, filter, concatMap, map } from 'rxjs/operators'; import { WebsocketService } from '../../services/websocket.service'; import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe'; import { Router } from '@angular/router'; @@ -33,7 +33,8 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang poolDirection: string = 'left'; blockSub: Subscription; - deltaSub: Subscription; + rateLimit = 1000; + private lastEventTime = Date.now() - this.rateLimit; firstLoad: boolean = true; @@ -55,11 +56,32 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang ngAfterViewInit(): void { this.blockSub = merge( - of(true), - this.stateService.connectionState$.pipe(filter((state) => state === 2)) - ) - .pipe(switchMap(() => this.stateService.mempoolBlockTransactions$)) - .subscribe((transactionsStripped) => { + this.stateService.mempoolBlockTransactions$, + this.stateService.mempoolBlockDelta$, + ).pipe( + concatMap(event => { + const now = Date.now(); + const timeSinceLastEvent = now - this.lastEventTime; + this.lastEventTime = Math.max(now, this.lastEventTime + this.rateLimit); + + // If time since last event is less than X seconds, delay this event + if (timeSinceLastEvent < this.rateLimit) { + return timer(this.rateLimit - timeSinceLastEvent).pipe( + // Emit the event after the timer + map(() => event) + ); + } else { + // If enough time has passed, emit the event immediately + return of(event); + } + }) + ).subscribe((update) => { + if (update['added']) { + // delta + this.updateBlock(update as MempoolBlockDelta); + } else { + const transactionsStripped = update as TransactionStripped[]; + // new transactions if (this.firstLoad) { this.replaceBlock(transactionsStripped); } else { @@ -94,9 +116,7 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang added }); } - }); - this.deltaSub = this.stateService.mempoolBlockDelta$.subscribe((delta) => { - this.updateBlock(delta); + } }); } @@ -113,7 +133,6 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang ngOnDestroy(): void { this.blockSub.unsubscribe(); - this.deltaSub.unsubscribe(); this.timeLtrSubscription.unsubscribe(); this.websocketService.stopTrackMempoolBlock(); } From 9485dfe2ce7d69d6375ddba1d2b1e9b7e6b667de Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 02:48:43 +0000 Subject: [PATCH 59/69] Bump cypress-fail-on-console-error from 5.0.0 to 5.1.0 in /frontend Bumps [cypress-fail-on-console-error](https://github.com/nils-hoyer/cypress-fail-on-console-error) from 5.0.0 to 5.1.0. - [Release notes](https://github.com/nils-hoyer/cypress-fail-on-console-error/releases) - [Commits](https://github.com/nils-hoyer/cypress-fail-on-console-error/compare/5.0.0...5.1.0) --- updated-dependencies: - dependency-name: cypress-fail-on-console-error dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 187 +++++++++++++++++++++++++------------ frontend/package.json | 2 +- 2 files changed, 127 insertions(+), 62 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b02ee1c50..dfd5b1f72 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -31,6 +31,7 @@ "bootstrap": "~4.6.2", "browserify": "^17.0.0", "clipboard": "^2.0.11", + "cypress-fail-on-console-error": "~5.1.0", "domino": "^2.1.6", "echarts": "~5.4.3", "lightweight-charts": "~3.8.0", @@ -59,7 +60,7 @@ "@cypress/schematic": "^2.5.0", "@types/cypress": "^1.1.3", "cypress": "^13.6.0", - "cypress-fail-on-console-error": "~5.0.0", + "cypress-fail-on-console-error": "~5.1.0", "cypress-wait-until": "^2.0.1", "mock-socket": "~9.3.1", "start-server-and-test": "~2.0.0" @@ -4068,9 +4069,9 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", "optional": true, "dependencies": { "@sinonjs/commons": "^3.0.0" @@ -6242,17 +6243,18 @@ "optional": true }, "node_modules/chai": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", - "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.0.tgz", + "integrity": "sha512-x9cHNq1uvkCdU+5xTkNh5WtgD4e4yDFCsp9jVc7N7qVeKeftv3gO/ZrviX5d+3ZfxdYnZXZYujjRInu1RogU6A==", "optional": true, "dependencies": { "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", "pathval": "^1.1.1", - "type-detect": "^4.0.5" + "type-detect": "^4.0.8" }, "engines": { "node": ">=4" @@ -6277,10 +6279,13 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", "optional": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, "engines": { "node": "*" } @@ -7137,13 +7142,13 @@ } }, "node_modules/cypress-fail-on-console-error": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cypress-fail-on-console-error/-/cypress-fail-on-console-error-5.0.0.tgz", - "integrity": "sha512-xui/aSu8rmExZjZNgId3iX0MsGZih6ZoFH+54vNHrK3HaqIZZX5hUuNhAcmfSoM1rIDc2DeITeVaMn/hiQ9IWQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cypress-fail-on-console-error/-/cypress-fail-on-console-error-5.1.0.tgz", + "integrity": "sha512-u/AXLE9obLd9KcGHkGJluJVZeOj1EEOFOs0URxxca4FrftUDJQ3u+IoNfjRUjsrBKmJxgM4vKd0G10D+ZT1uIA==", "optional": true, "dependencies": { - "chai": "^4.3.4", - "sinon": "^15.0.0", + "chai": "^4.3.10", + "sinon": "^17.0.0", "sinon-chai": "^3.7.0", "type-detect": "^4.0.8" } @@ -7403,15 +7408,15 @@ "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=" }, "node_modules/deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", "optional": true, "dependencies": { "type-detect": "^4.0.0" }, "engines": { - "node": ">=0.12" + "node": ">=6" } }, "node_modules/deep-equal": { @@ -11759,6 +11764,15 @@ "node": ">=8.0" } }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "optional": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -12537,9 +12551,9 @@ } }, "node_modules/nise": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.4.tgz", - "integrity": "sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.5.tgz", + "integrity": "sha512-VJuPIfUFaXNRzETTQEEItTOP8Y171ijr+JLq42wHes3DiryR8vT+1TXQW/Rx8JNUhyYYWyIvjXTU6dOhJcs9Nw==", "optional": true, "dependencies": { "@sinonjs/commons": "^2.0.0", @@ -12558,6 +12572,24 @@ "type-detect": "4.0.8" } }, + "node_modules/nise/node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "optional": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/nise/node_modules/@sinonjs/fake-timers/node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "optional": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, "node_modules/nise/node_modules/isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -14842,16 +14874,16 @@ ] }, "node_modules/sinon": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.2.0.tgz", - "integrity": "sha512-nPS85arNqwBXaIsFCkolHjGIkFo+Oxu9vbgmBJizLAhqe6P2o3Qmj3KCUoRkfhHtvgDhZdWD3risLHAUJ8npjw==", + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", "optional": true, "dependencies": { "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^10.3.0", + "@sinonjs/fake-timers": "^11.2.2", "@sinonjs/samsam": "^8.0.0", "diff": "^5.1.0", - "nise": "^5.1.4", + "nise": "^5.1.5", "supports-color": "^7.2.0" }, "funding": { @@ -19882,9 +19914,9 @@ } }, "@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", "optional": true, "requires": { "@sinonjs/commons": "^3.0.0" @@ -21594,17 +21626,18 @@ "optional": true }, "chai": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", - "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.0.tgz", + "integrity": "sha512-x9cHNq1uvkCdU+5xTkNh5WtgD4e4yDFCsp9jVc7N7qVeKeftv3gO/ZrviX5d+3ZfxdYnZXZYujjRInu1RogU6A==", "optional": true, "requires": { "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", "pathval": "^1.1.1", - "type-detect": "^4.0.5" + "type-detect": "^4.0.8" } }, "chalk": { @@ -21623,10 +21656,13 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "optional": true + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "optional": true, + "requires": { + "get-func-name": "^2.0.2" + } }, "check-more-types": { "version": "2.24.0", @@ -22403,13 +22439,13 @@ } }, "cypress-fail-on-console-error": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cypress-fail-on-console-error/-/cypress-fail-on-console-error-5.0.0.tgz", - "integrity": "sha512-xui/aSu8rmExZjZNgId3iX0MsGZih6ZoFH+54vNHrK3HaqIZZX5hUuNhAcmfSoM1rIDc2DeITeVaMn/hiQ9IWQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cypress-fail-on-console-error/-/cypress-fail-on-console-error-5.1.0.tgz", + "integrity": "sha512-u/AXLE9obLd9KcGHkGJluJVZeOj1EEOFOs0URxxca4FrftUDJQ3u+IoNfjRUjsrBKmJxgM4vKd0G10D+ZT1uIA==", "optional": true, "requires": { - "chai": "^4.3.4", - "sinon": "^15.0.0", + "chai": "^4.3.10", + "sinon": "^17.0.0", "sinon-chai": "^3.7.0", "type-detect": "^4.0.8" } @@ -22490,9 +22526,9 @@ "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=" }, "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", "optional": true, "requires": { "type-detect": "^4.0.0" @@ -25754,6 +25790,15 @@ "streamroller": "^3.0.2" } }, + "loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "optional": true, + "requires": { + "get-func-name": "^2.0.1" + } + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -26361,9 +26406,9 @@ } }, "nise": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.4.tgz", - "integrity": "sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.5.tgz", + "integrity": "sha512-VJuPIfUFaXNRzETTQEEItTOP8Y171ijr+JLq42wHes3DiryR8vT+1TXQW/Rx8JNUhyYYWyIvjXTU6dOhJcs9Nw==", "optional": true, "requires": { "@sinonjs/commons": "^2.0.0", @@ -26382,6 +26427,26 @@ "type-detect": "4.0.8" } }, + "@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "optional": true, + "requires": { + "@sinonjs/commons": "^3.0.0" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "optional": true, + "requires": { + "type-detect": "4.0.8" + } + } + } + }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -28036,16 +28101,16 @@ "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" }, "sinon": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.2.0.tgz", - "integrity": "sha512-nPS85arNqwBXaIsFCkolHjGIkFo+Oxu9vbgmBJizLAhqe6P2o3Qmj3KCUoRkfhHtvgDhZdWD3risLHAUJ8npjw==", + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", "optional": true, "requires": { "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^10.3.0", + "@sinonjs/fake-timers": "^11.2.2", "@sinonjs/samsam": "^8.0.0", "diff": "^5.1.0", - "nise": "^5.1.4", + "nise": "^5.1.5", "supports-color": "^7.2.0" }, "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index a4a4ac462..8dbcbcf3e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -111,7 +111,7 @@ "@cypress/schematic": "^2.5.0", "@types/cypress": "^1.1.3", "cypress": "^13.6.0", - "cypress-fail-on-console-error": "~5.0.0", + "cypress-fail-on-console-error": "~5.1.0", "cypress-wait-until": "^2.0.1", "mock-socket": "~9.3.1", "start-server-and-test": "~2.0.0" From fc8eca4c26c38b2e57ce86c62f854f44e072e71d Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 8 Jan 2024 14:42:00 +0000 Subject: [PATCH 60/69] Handle stale smoothed mempool block updates --- .../mempool-block-overview.component.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) 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 84c9a3416..8dad6a9c1 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 @@ -35,6 +35,7 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang blockSub: Subscription; rateLimit = 1000; private lastEventTime = Date.now() - this.rateLimit; + private subId = 0; firstLoad: boolean = true; @@ -59,23 +60,30 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang this.stateService.mempoolBlockTransactions$, this.stateService.mempoolBlockDelta$, ).pipe( - concatMap(event => { + concatMap(update => { const now = Date.now(); const timeSinceLastEvent = now - this.lastEventTime; this.lastEventTime = Math.max(now, this.lastEventTime + this.rateLimit); + const subId = this.subId; + // If time since last event is less than X seconds, delay this event if (timeSinceLastEvent < this.rateLimit) { return timer(this.rateLimit - timeSinceLastEvent).pipe( // Emit the event after the timer - map(() => event) + map(() => ({ update, subId })) ); } else { // If enough time has passed, emit the event immediately - return of(event); + return of({ update, subId }); } }) - ).subscribe((update) => { + ).subscribe(({ update, subId }) => { + // discard stale updates after a block transition + if (subId !== this.subId) { + return; + } + // process update if (update['added']) { // delta this.updateBlock(update as MempoolBlockDelta); @@ -122,6 +130,7 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang ngOnChanges(changes): void { if (changes.index) { + this.subId++; this.firstLoad = true; if (this.blockGraph) { this.blockGraph.clear(changes.index.currentValue > changes.index.previousValue ? this.chainDirection : this.poolDirection); From d5c5ae0e090adda89872f2e21f75dbecab937da0 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 8 Jan 2024 16:33:19 +0000 Subject: [PATCH 61/69] Additional fee recommendation sanity checks --- backend/src/api/fee-api.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/backend/src/api/fee-api.ts b/backend/src/api/fee-api.ts index 0cab5a295..24fd25a4b 100644 --- a/backend/src/api/fee-api.ts +++ b/backend/src/api/fee-api.ts @@ -39,15 +39,25 @@ class FeeApi { const secondMedianFee = pBlocks[1] ? this.optimizeMedianFee(pBlocks[1], pBlocks[2], firstMedianFee) : this.defaultFee; const thirdMedianFee = pBlocks[2] ? this.optimizeMedianFee(pBlocks[2], pBlocks[3], secondMedianFee) : this.defaultFee; + let fastestFee = Math.max(minimumFee, firstMedianFee); + let halfHourFee = Math.max(minimumFee, secondMedianFee); + let hourFee = Math.max(minimumFee, thirdMedianFee); + const economyFee = Math.max(minimumFee, Math.min(2 * minimumFee, thirdMedianFee)); + + // ensure recommendations always increase w/ priority + fastestFee = Math.max(fastestFee, halfHourFee, hourFee, economyFee); + halfHourFee = Math.max(halfHourFee, hourFee, economyFee); + hourFee = Math.max(hourFee, economyFee); + // explicitly enforce a minimum of ceil(mempoolminfee) on all recommendations. // simply rounding up recommended rates is insufficient, as the purging rate // can exceed the median rate of projected blocks in some extreme scenarios // (see https://bitcoin.stackexchange.com/a/120024) return { - 'fastestFee': Math.max(minimumFee, firstMedianFee), - 'halfHourFee': Math.max(minimumFee, secondMedianFee), - 'hourFee': Math.max(minimumFee, thirdMedianFee), - 'economyFee': Math.max(minimumFee, Math.min(2 * minimumFee, thirdMedianFee)), + 'fastestFee': fastestFee, + 'halfHourFee': halfHourFee, + 'hourFee': hourFee, + 'economyFee': economyFee, 'minimumFee': minimumFee, }; } From 0230f95001875039995c6f68a3adc80f85ce0eea Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 8 Jan 2024 22:33:11 +0000 Subject: [PATCH 62/69] Fix bad coinbase price url, switch to median prices --- backend/src/tasks/price-feeds/coinbase-api.ts | 4 ++-- backend/src/tasks/price-updater.ts | 14 ++++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/backend/src/tasks/price-feeds/coinbase-api.ts b/backend/src/tasks/price-feeds/coinbase-api.ts index 424ac8867..d2c6d063a 100644 --- a/backend/src/tasks/price-feeds/coinbase-api.ts +++ b/backend/src/tasks/price-feeds/coinbase-api.ts @@ -5,14 +5,14 @@ class CoinbaseApi implements PriceFeed { public name: string = 'Coinbase'; public currencies: string[] = ['USD', 'EUR', 'GBP']; - public url: string = 'https://api.coinbase.com/v2/prices/spot?currency='; + public url: string = 'https://api.coinbase.com/v2/prices/BTC-{CURRENCY}/spot'; public urlHist: string = 'https://api.exchange.coinbase.com/products/BTC-{CURRENCY}/candles?granularity={GRANULARITY}'; constructor() { } public async $fetchPrice(currency): Promise { - const response = await query(this.url + currency); + const response = await query(this.url.replace('{CURRENCY}', currency)); if (response && response['data'] && response['data']['amount']) { return parseInt(response['data']['amount'], 10); } else { diff --git a/backend/src/tasks/price-updater.ts b/backend/src/tasks/price-updater.ts index fd799fb87..0d5ca5958 100644 --- a/backend/src/tasks/price-updater.ts +++ b/backend/src/tasks/price-updater.ts @@ -23,6 +23,14 @@ export interface PriceHistory { [timestamp: number]: ApiPrice; } +function getMedian(arr: number[]): number { + const sortedArr = arr.slice().sort((a, b) => a - b); + const mid = Math.floor(sortedArr.length / 2); + return sortedArr.length % 2 !== 0 + ? sortedArr[mid] + : (sortedArr[mid - 1] + sortedArr[mid]) / 2; +} + class PriceUpdater { public historyInserted = false; private timeBetweenUpdatesMs = 360_0000 / config.MEMPOOL.PRICE_UPDATES_PER_HOUR; @@ -173,7 +181,7 @@ class PriceUpdater { if (prices.length === 0) { this.latestPrices[currency] = -1; } else { - this.latestPrices[currency] = Math.round((prices.reduce((partialSum, a) => partialSum + a, 0)) / prices.length); + this.latestPrices[currency] = Math.round(getMedian(prices)); } } @@ -300,9 +308,7 @@ class PriceUpdater { if (grouped[time][currency].length === 0) { continue; } - prices[currency] = Math.round((grouped[time][currency].reduce( - (partialSum, a) => partialSum + a, 0) - ) / grouped[time][currency].length); + prices[currency] = Math.round(getMedian(grouped[time][currency])); } await PricesRepository.$savePrices(parseInt(time, 10), prices); ++totalInserted; From 89d37f00580eb033dcca81dd27c33b12fa178da5 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 9 Jan 2024 16:18:04 +0000 Subject: [PATCH 63/69] Fix unmineable tx handling --- backend/rust-gbt/src/gbt.rs | 129 +++++++++++++++--------------- backend/src/api/mempool-blocks.ts | 11 +-- 2 files changed, 72 insertions(+), 68 deletions(-) diff --git a/backend/rust-gbt/src/gbt.rs b/backend/rust-gbt/src/gbt.rs index 412d4e5e9..e1ceeefb6 100644 --- a/backend/rust-gbt/src/gbt.rs +++ b/backend/rust-gbt/src/gbt.rs @@ -60,6 +60,7 @@ pub fn gbt(mempool: &mut ThreadTransactionsMap, accelerations: &[ThreadAccelerat indexed_accelerations[acceleration.uid as usize] = Some(acceleration); } + info!("Initializing working vecs with uid capacity for {}", max_uid + 1); let mempool_len = mempool.len(); let mut audit_pool: AuditPool = Vec::with_capacity(max_uid + 1); audit_pool.resize(max_uid + 1, None); @@ -127,74 +128,75 @@ pub fn gbt(mempool: &mut ThreadTransactionsMap, accelerations: &[ThreadAccelerat let next_from_stack = next_valid_from_stack(&mut mempool_stack, &audit_pool); let next_from_queue = next_valid_from_queue(&mut modified, &audit_pool); if next_from_stack.is_none() && next_from_queue.is_none() { - break; - } - let (next_tx, from_stack) = match (next_from_stack, next_from_queue) { - (Some(stack_tx), Some(queue_tx)) => match queue_tx.cmp(stack_tx) { - std::cmp::Ordering::Less => (stack_tx, true), - _ => (queue_tx, false), - }, - (Some(stack_tx), None) => (stack_tx, true), - (None, Some(queue_tx)) => (queue_tx, false), - (None, None) => unreachable!(), - }; - - if from_stack { - mempool_stack.pop(); + info!("No transactions left! {:#?} in overflow", overflow.len()); } else { - modified.pop(); - } + let (next_tx, from_stack) = match (next_from_stack, next_from_queue) { + (Some(stack_tx), Some(queue_tx)) => match queue_tx.cmp(stack_tx) { + std::cmp::Ordering::Less => (stack_tx, true), + _ => (queue_tx, false), + }, + (Some(stack_tx), None) => (stack_tx, true), + (None, Some(queue_tx)) => (queue_tx, false), + (None, None) => unreachable!(), + }; - if blocks.len() < (MAX_BLOCKS - 1) - && ((block_weight + (4 * next_tx.ancestor_sigop_adjusted_vsize()) - >= MAX_BLOCK_WEIGHT_UNITS) - || (block_sigops + next_tx.ancestor_sigops() > BLOCK_SIGOPS)) - { - // hold this package in an overflow list while we check for smaller options - overflow.push(next_tx.uid); - failures += 1; - } else { - let mut package: Vec<(u32, u32, usize)> = Vec::new(); - let mut cluster: Vec = Vec::new(); - let is_cluster: bool = !next_tx.ancestors.is_empty(); - for ancestor_id in &next_tx.ancestors { - if let Some(Some(ancestor)) = audit_pool.get(*ancestor_id as usize) { - package.push((*ancestor_id, ancestor.order(), ancestor.ancestors.len())); - } - } - package.sort_unstable_by(|a, b| -> Ordering { - if a.2 != b.2 { - // order by ascending ancestor count - a.2.cmp(&b.2) - } else if a.1 != b.1 { - // tie-break by ascending partial txid - a.1.cmp(&b.1) - } else { - // tie-break partial txid collisions by ascending uid - a.0.cmp(&b.0) - } - }); - package.push((next_tx.uid, next_tx.order(), next_tx.ancestors.len())); - - let cluster_rate = next_tx.cluster_rate(); - - for (txid, _, _) in &package { - cluster.push(*txid); - if let Some(Some(tx)) = audit_pool.get_mut(*txid as usize) { - tx.used = true; - tx.set_dirty_if_different(cluster_rate); - transactions.push(tx.uid); - block_weight += tx.weight; - block_sigops += tx.sigops; - } - update_descendants(*txid, &mut audit_pool, &mut modified, cluster_rate); + if from_stack { + mempool_stack.pop(); + } else { + modified.pop(); } - if is_cluster { - clusters.push(cluster); - } + if blocks.len() < (MAX_BLOCKS - 1) + && ((block_weight + (4 * next_tx.ancestor_sigop_adjusted_vsize()) + >= MAX_BLOCK_WEIGHT_UNITS) + || (block_sigops + next_tx.ancestor_sigops() > BLOCK_SIGOPS)) + { + // hold this package in an overflow list while we check for smaller options + overflow.push(next_tx.uid); + failures += 1; + } else { + let mut package: Vec<(u32, u32, usize)> = Vec::new(); + let mut cluster: Vec = Vec::new(); + let is_cluster: bool = !next_tx.ancestors.is_empty(); + for ancestor_id in &next_tx.ancestors { + if let Some(Some(ancestor)) = audit_pool.get(*ancestor_id as usize) { + package.push((*ancestor_id, ancestor.order(), ancestor.ancestors.len())); + } + } + package.sort_unstable_by(|a, b| -> Ordering { + if a.2 != b.2 { + // order by ascending ancestor count + a.2.cmp(&b.2) + } else if a.1 != b.1 { + // tie-break by ascending partial txid + a.1.cmp(&b.1) + } else { + // tie-break partial txid collisions by ascending uid + a.0.cmp(&b.0) + } + }); + package.push((next_tx.uid, next_tx.order(), next_tx.ancestors.len())); - failures = 0; + let cluster_rate = next_tx.cluster_rate(); + + for (txid, _, _) in &package { + cluster.push(*txid); + if let Some(Some(tx)) = audit_pool.get_mut(*txid as usize) { + tx.used = true; + tx.set_dirty_if_different(cluster_rate); + transactions.push(tx.uid); + block_weight += tx.weight; + block_sigops += tx.sigops; + } + update_descendants(*txid, &mut audit_pool, &mut modified, cluster_rate); + } + + if is_cluster { + clusters.push(cluster); + } + + failures = 0; + } } // this block is full @@ -204,6 +206,7 @@ pub fn gbt(mempool: &mut ThreadTransactionsMap, accelerations: &[ThreadAccelerat 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()); break; } diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index a7f00f6e8..2097acd4b 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -432,15 +432,16 @@ class MempoolBlocks { this.nextUid, ), ); - const resultMempoolSize = blocks.reduce((total, block) => total + block.length, 0); - if (mempoolSize !== resultMempoolSize) { - throw new Error('GBT returned wrong number of transactions, cache is probably out of sync'); - } else { + //// different number of transactions is now expected, if any were unmineable + // const resultMempoolSize = blocks.reduce((total, block) => total + block.length, 0); + // if (mempoolSize !== resultMempoolSize) { + // throw new Error('GBT returned wrong number of transactions, cache is probably out of sync'); + // } else { const processed = this.processBlockTemplates(newMempool, blocks, blockWeights, rates, clusters, accelerations, accelerationPool, true); this.removeUids(removedUids); logger.debug(`RUST updateBlockTemplates completed in ${(Date.now() - start)/1000} seconds`); return processed; - } + // } } catch (e) { logger.err('RUST updateBlockTemplates failed. ' + (e instanceof Error ? e.message : e)); this.resetRustGbt(); From 30d58d9971bd73c4ba19d016d4004653d3b40f1a Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 9 Jan 2024 17:08:25 +0000 Subject: [PATCH 64/69] Restore GBT result size sanity check --- backend/rust-gbt/index.d.ts | 3 ++- backend/rust-gbt/src/gbt.rs | 1 + backend/rust-gbt/src/lib.rs | 1 + backend/src/api/mempool-blocks.ts | 33 ++++++++++++++++++++----------- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/backend/rust-gbt/index.d.ts b/backend/rust-gbt/index.d.ts index 2bd8a620a..d1cb85b92 100644 --- a/backend/rust-gbt/index.d.ts +++ b/backend/rust-gbt/index.d.ts @@ -45,5 +45,6 @@ export class GbtResult { blockWeights: Array clusters: Array> rates: Array> - constructor(blocks: Array>, blockWeights: Array, clusters: Array>, rates: Array>) + overflow: Array + constructor(blocks: Array>, blockWeights: Array, clusters: Array>, rates: Array>, overflow: Array) } diff --git a/backend/rust-gbt/src/gbt.rs b/backend/rust-gbt/src/gbt.rs index e1ceeefb6..38bf826a6 100644 --- a/backend/rust-gbt/src/gbt.rs +++ b/backend/rust-gbt/src/gbt.rs @@ -271,6 +271,7 @@ pub fn gbt(mempool: &mut ThreadTransactionsMap, accelerations: &[ThreadAccelerat block_weights, clusters, rates, + overflow, } } diff --git a/backend/rust-gbt/src/lib.rs b/backend/rust-gbt/src/lib.rs index 53db0ba21..edc9714ee 100644 --- a/backend/rust-gbt/src/lib.rs +++ b/backend/rust-gbt/src/lib.rs @@ -133,6 +133,7 @@ pub struct GbtResult { pub block_weights: Vec, pub clusters: Vec>, pub rates: Vec>, // Tuples not supported. u32 fits inside f64 + pub overflow: Vec, } /// All on another thread, this runs an arbitrary task in between diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index 2097acd4b..0ca550f4c 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -368,12 +368,15 @@ class MempoolBlocks { // run the block construction algorithm in a separate thread, and wait for a result const rustGbt = saveResults ? this.rustGbtGenerator : new GbtGenerator(); try { - const { blocks, blockWeights, rates, clusters } = this.convertNapiResultTxids( + const { blocks, blockWeights, rates, clusters, overflow } = this.convertNapiResultTxids( await rustGbt.make(Object.values(newMempool) as RustThreadTransaction[], convertedAccelerations as RustThreadAcceleration[], this.nextUid), ); if (saveResults) { this.rustInitialized = true; } + const mempoolSize = Object.keys(newMempool).length; + const resultMempoolSize = blocks.reduce((total, block) => total + block.length, 0) + overflow.length; + logger.debug(`RUST updateBlockTemplates returned ${resultMempoolSize} txs out of ${mempoolSize} in the mempool, ${overflow.length} were unmineable`); const processed = this.processBlockTemplates(newMempool, blocks, blockWeights, rates, clusters, accelerations, accelerationPool, saveResults); logger.debug(`RUST makeBlockTemplates completed in ${(Date.now() - start)/1000} seconds`); return processed; @@ -424,7 +427,7 @@ class MempoolBlocks { // run the block construction algorithm in a separate thread, and wait for a result try { - const { blocks, blockWeights, rates, clusters } = this.convertNapiResultTxids( + const { blocks, blockWeights, rates, clusters, overflow } = this.convertNapiResultTxids( await this.rustGbtGenerator.update( added as RustThreadTransaction[], removedUids, @@ -432,16 +435,16 @@ class MempoolBlocks { this.nextUid, ), ); - //// different number of transactions is now expected, if any were unmineable - // const resultMempoolSize = blocks.reduce((total, block) => total + block.length, 0); - // if (mempoolSize !== resultMempoolSize) { - // throw new Error('GBT returned wrong number of transactions, cache is probably out of sync'); - // } else { + const resultMempoolSize = blocks.reduce((total, block) => total + block.length, 0) + overflow.length; + logger.debug(`RUST updateBlockTemplates returned ${resultMempoolSize} txs out of ${mempoolSize} in the mempool, ${overflow.length} were unmineable`); + if (mempoolSize !== resultMempoolSize) { + throw new Error('GBT returned wrong number of transactions , cache is probably out of sync'); + } else { const processed = this.processBlockTemplates(newMempool, blocks, blockWeights, rates, clusters, accelerations, accelerationPool, true); this.removeUids(removedUids); logger.debug(`RUST updateBlockTemplates completed in ${(Date.now() - start)/1000} seconds`); return processed; - // } + } } catch (e) { logger.err('RUST updateBlockTemplates failed. ' + (e instanceof Error ? e.message : e)); this.resetRustGbt(); @@ -659,8 +662,8 @@ class MempoolBlocks { return { blocks: convertedBlocks, rates: convertedRates, clusters: convertedClusters } as { blocks: string[][], rates: { [root: string]: number }, clusters: { [root: string]: string[] }}; } - private convertNapiResultTxids({ blocks, blockWeights, rates, clusters }: GbtResult) - : { blocks: string[][], blockWeights: number[], rates: [string, number][], clusters: string[][] } { + private convertNapiResultTxids({ blocks, blockWeights, rates, clusters, overflow }: GbtResult) + : { blocks: string[][], blockWeights: number[], rates: [string, number][], clusters: string[][], overflow: string[] } { const convertedBlocks: string[][] = blocks.map(block => block.map(uid => { const txid = this.uidMap.get(uid); if (txid !== undefined) { @@ -678,7 +681,15 @@ class MempoolBlocks { for (const cluster of clusters) { convertedClusters.push(cluster.map(uid => this.uidMap.get(uid)) as string[]); } - return { blocks: convertedBlocks, blockWeights, rates: convertedRates, clusters: convertedClusters }; + const convertedOverflow: string[] = overflow.map(uid => { + const txid = this.uidMap.get(uid); + if (txid !== undefined) { + return txid; + } else { + throw new Error('GBT returned an unmineable transaction with unknown uid'); + } + }); + return { blocks: convertedBlocks, blockWeights, rates: convertedRates, clusters: convertedClusters, overflow: convertedOverflow }; } } From 8336a00aead1ad32dbf3606374c4c1420dc1e712 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 10 Jan 2024 14:32:37 +0000 Subject: [PATCH 65/69] Clean rust-gbt directory before build --- backend/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/package.json b/backend/package.json index 0c1d3cc4a..e2417524b 100644 --- a/backend/package.json +++ b/backend/package.json @@ -35,7 +35,8 @@ "lint": "./node_modules/.bin/eslint . --ext .ts", "lint:fix": "./node_modules/.bin/eslint . --ext .ts --fix", "prettier": "./node_modules/.bin/prettier --write \"src/**/*.{js,ts}\"", - "rust-build": "cd rust-gbt && npm run build-release" + "rust-clean": "cd rust-gbt && rm -f *.node index.d.ts index.js && rm -rf target && cd ../", + "rust-build": "npm run rust-clean && cd rust-gbt && npm run build-release" }, "dependencies": { "@babel/core": "^7.23.2", From 367f70c3b3fb7a88546c6fce76e153ab8478046e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Jan 2024 14:40:20 +0000 Subject: [PATCH 66/69] Bump mysql2 from 3.6.0 to 3.7.0 in /backend Bumps [mysql2](https://github.com/sidorares/node-mysql2) from 3.6.0 to 3.7.0. - [Release notes](https://github.com/sidorares/node-mysql2/releases) - [Changelog](https://github.com/sidorares/node-mysql2/blob/master/Changelog.md) - [Commits](https://github.com/sidorares/node-mysql2/compare/v3.6.0...v3.7.0) --- updated-dependencies: - dependency-name: mysql2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- backend/package-lock.json | 14 +++++++------- backend/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 3e9e31988..b3b659459 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -17,7 +17,7 @@ "crypto-js": "~4.2.0", "express": "~4.18.2", "maxmind": "~4.3.11", - "mysql2": "~3.6.0", + "mysql2": "~3.7.0", "redis": "^4.6.6", "rust-gbt": "file:./rust-gbt", "socks-proxy-agent": "~7.0.0", @@ -6110,9 +6110,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/mysql2": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.6.0.tgz", - "integrity": "sha512-EWUGAhv6SphezurlfI2Fpt0uJEWLmirrtQR7SkbTHFC+4/mJBrPiSzHESHKAWKG7ALVD6xaG/NBjjd1DGJGQQQ==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.7.0.tgz", + "integrity": "sha512-c45jA3Jc1X8yJKzrWu1GpplBKGwv/wIV6ITZTlCSY7npF2YfJR+6nMP5e+NTQhUeJPSyOQAbGDCGEHbAl8HN9w==", "dependencies": { "denque": "^2.1.0", "generate-function": "^2.3.1", @@ -12230,9 +12230,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "mysql2": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.6.0.tgz", - "integrity": "sha512-EWUGAhv6SphezurlfI2Fpt0uJEWLmirrtQR7SkbTHFC+4/mJBrPiSzHESHKAWKG7ALVD6xaG/NBjjd1DGJGQQQ==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.7.0.tgz", + "integrity": "sha512-c45jA3Jc1X8yJKzrWu1GpplBKGwv/wIV6ITZTlCSY7npF2YfJR+6nMP5e+NTQhUeJPSyOQAbGDCGEHbAl8HN9w==", "requires": { "denque": "^2.1.0", "generate-function": "^2.3.1", diff --git a/backend/package.json b/backend/package.json index e2417524b..cd1255392 100644 --- a/backend/package.json +++ b/backend/package.json @@ -47,7 +47,7 @@ "crypto-js": "~4.2.0", "express": "~4.18.2", "maxmind": "~4.3.11", - "mysql2": "~3.6.0", + "mysql2": "~3.7.0", "rust-gbt": "file:./rust-gbt", "redis": "^4.6.6", "socks-proxy-agent": "~7.0.0", From 439177a78f1b7024531bb198bc9f547bbd7117dc Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 10 Jan 2024 22:07:01 +0000 Subject: [PATCH 67/69] Add sigop related FAQ entries --- .../src/app/docs/api-docs/api-docs-data.ts | 21 ++++++++++++++++++ .../app/docs/api-docs/api-docs.component.html | 22 +++++++++++++++++++ 2 files changed, 43 insertions(+) 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 c5319bf2a..86a63e513 100644 --- a/frontend/src/app/docs/api-docs/api-docs-data.ts +++ b/frontend/src/app/docs/api-docs/api-docs-data.ts @@ -10081,6 +10081,27 @@ export const faqData = [ fragment: "how-do-mempool-goggles-work", title: "How do Mempool Goggles work?", }, + { + type: "endpoint", + category: "advanced", + showConditions: bitcoinNetworks, + fragment: "what-are-sigops", + title: "What are sigops?", + }, + { + type: "endpoint", + category: "advanced", + showConditions: bitcoinNetworks, + fragment: "what-is-adjusted-vsize", + title: "What is adjusted vsize?", + }, + { + type: "endpoint", + category: "advanced", + showConditions: bitcoinNetworks, + fragment: "why-do-the-projected-block-fee-ranges-overlap", + title: "Why do the projected block fee ranges overlap?", + }, { type: "category", category: "self-hosting", diff --git a/frontend/src/app/docs/api-docs/api-docs.component.html b/frontend/src/app/docs/api-docs/api-docs.component.html index 77cf01326..c3a260995 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.html +++ b/frontend/src/app/docs/api-docs/api-docs.component.html @@ -368,6 +368,28 @@ + +

    A "sigop" is a way of accounting for the cost of "signature operations" in Bitcoin script, like OP_CHECKSIG, OP_CHECKSIGVERIFY, OP_CHECKMULTISIG and OP_CHECKMULTISIGVERIFY

    +

    These signature operations incur different costs depending on whether they are single or multi-sig operations, and on where they appear in a Bitcoin transaction.

    +

    By consensus, each Bitcoin block is permitted to include a maximum of 80,000 sigops.

    +
    + + +

    Bitcoin blocks have two independent consensus-enforced resource constraints - a 4MWU weight limit, and the 80,000 sigop limit.

    +

    Most transactions use a more of the weight limit than the sigop limit. However, some transactions use a disproportionate number of sigops compared to their weight.

    +

    To account for this, Bitcoin Core calculates and uses an "adjusted vsize" equal 5 times the number of sigops, or the unadjusted vsize, whichever is larger.

    +

    Then, during block template construction, Bitcoin Core selects transactions in descending order of fee rate measured in satoshis per adjusted vsize

    +

    On mempool.space, effective fee rates for unconfirmed transactions are also measured in terms of satoshis per adjusted vsize, after accounting for CPFP relationships and other dependencies.

    +
    + + +

    The projected mempool blocks represent what we expect the next blocks would look like if they were mined right now, and so each projected block follows all of the same rules and constraints as real mined blocks.

    +

    Those constraints can sometimes cause transactions with lower fee rates to be included "ahead" of transactions with higher rates.

    +

    For example, if one projected block has a very small amount of space left, it might be able to fit one more tiny low fee rate transaction, while larger higher fee rate transactions have to wait for the following block.

    +

    A similar effect can occur when there are a large number of transactions with very many sigops. In that scenario, each projected block can only include up to 80,000 sigops worth of transactions, after which the remaining space can only be filled by potentially much lower fee transactions with zero sigops.

    +

    In extreme cases this can produce several projected blocks in a row with overlapping fee ranges, as a result of each projected block containing both high-feerate high-sigop transactions and lower feerate zero-sigop transactions.

    +
    + The official mempool.space website is operated by The Mempool Open Source Project. See more information on our
    About page. There are also many unofficial instances of this website operated by individual members of the Bitcoin community. From e9a67adf4fb0648e5ccb01c2f20a63bbb4ae70f7 Mon Sep 17 00:00:00 2001 From: softsimon Date: Fri, 12 Jan 2024 09:50:33 +0700 Subject: [PATCH 68/69] Adding links to FAQ sections from TX page --- .../src/app/components/block/block.component.html | 2 +- .../src/app/components/block/block.component.scss | 5 ----- .../transaction/transaction.component.html | 12 ++++++++++-- frontend/src/styles.scss | 4 ++++ 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html index e908d5b24..b34b39c8c 100644 --- a/frontend/src/app/components/block/block.component.html +++ b/frontend/src/app/components/block/block.component.html @@ -59,7 +59,7 @@ - Health + Health  - Adjusted vsize + Adjusted vsize + + + + @@ -321,7 +325,11 @@ - Sigops + Sigops + + + + diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index be8cec328..8a4fe3c9a 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -1191,3 +1191,7 @@ app-global-footer { line-height: 0.5; border-radius: 0.2rem; } + +.info-link fa-icon { + color: rgba(255, 255, 255, 0.4); +} From a23088458bd638cdfe0cda5d73da0c4180653cbe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jan 2024 10:21:43 +0000 Subject: [PATCH 69/69] Bump follow-redirects from 1.15.3 to 1.15.5 in /frontend Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.5. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.5) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index dfd5b1f72..59bdac54a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -31,7 +31,6 @@ "bootstrap": "~4.6.2", "browserify": "^17.0.0", "clipboard": "^2.0.11", - "cypress-fail-on-console-error": "~5.1.0", "domino": "^2.1.6", "echarts": "~5.4.3", "lightweight-charts": "~3.8.0", @@ -9273,9 +9272,9 @@ "devOptional": true }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", "funding": [ { "type": "individual", @@ -23993,9 +23992,9 @@ "devOptional": true }, "follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==" + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==" }, "foreach": { "version": "2.0.5",

    Ovtf*nL@gSdACc7IN#w*q(qasboeh(OrTrAK3) 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 From 4f3852bbba88265b88ae0175febe5ce484e83bde Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Tue, 19 Dec 2023 21:49:52 -0700 Subject: [PATCH 39/69] One more --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e272c6b39..7e426f155 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Mempool can be conveniently installed on the following full-node distros: - [RaspiBlitz](https://github.com/rootzoll/raspiblitz) - [RoninDojo](https://code.samourai.io/ronindojo/RoninDojo) - [myNode](https://github.com/mynodebtc/mynode) -- [Start9](https://github.com/Start9Labs/start-os) +- [StartOS](https://github.com/Start9Labs/start-os) - [nix-bitcoin](https://github.com/fort-nix/nix-bitcoin/blob/a1eacce6768ca4894f365af8f79be5bbd594e1c3/examples/configuration.nix#L129) **We highly recommend you deploy your own Mempool instance this way.** No matter which option you pick, you'll be able to get your own fully-sovereign instance of Mempool up quickly without needing to fiddle with any settings. From f4715419073e98792b78e2735c4f72c2bed665e8 Mon Sep 17 00:00:00 2001 From: natsee Date: Wed, 20 Dec 2023 16:21:40 +0100 Subject: [PATCH 40/69] Fix fade out on RBF history --- .../rbf-timeline/rbf-timeline.component.html | 21 +++++------ .../rbf-timeline/rbf-timeline.component.scss | 35 ++++++++++--------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/frontend/src/app/components/rbf-timeline/rbf-timeline.component.html b/frontend/src/app/components/rbf-timeline/rbf-timeline.component.html index 9ff35d669..6f19537e1 100644 --- a/frontend/src/app/components/rbf-timeline/rbf-timeline.component.html +++ b/frontend/src/app/components/rbf-timeline/rbf-timeline.component.html @@ -1,6 +1,6 @@