Merge branch 'master' into mononaut/fiat-selector

This commit is contained in:
wiz 2023-02-15 12:10:07 +09:00 committed by GitHub
commit e009c78c3e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 257 additions and 120 deletions

View file

@ -33,7 +33,7 @@ class MempoolBlocks {
return this.mempoolBlockDeltas;
}
public updateMempoolBlocks(memPool: { [txid: string]: TransactionExtended }): void {
public updateMempoolBlocks(memPool: { [txid: string]: TransactionExtended }, saveResults: boolean = false): MempoolBlockWithTransactions[] {
const latestMempool = memPool;
const memPoolArray: TransactionExtended[] = [];
for (const i in latestMempool) {
@ -75,10 +75,14 @@ class MempoolBlocks {
logger.debug('Mempool blocks calculated in ' + time / 1000 + ' seconds');
const blocks = this.calculateMempoolBlocks(memPoolArray, this.mempoolBlocks);
const deltas = this.calculateMempoolDeltas(this.mempoolBlocks, blocks);
this.mempoolBlocks = blocks;
this.mempoolBlockDeltas = deltas;
if (saveResults) {
const deltas = this.calculateMempoolDeltas(this.mempoolBlocks, blocks);
this.mempoolBlocks = blocks;
this.mempoolBlockDeltas = deltas;
}
return blocks;
}
private calculateMempoolBlocks(transactionsSorted: TransactionExtended[], prevBlocks: MempoolBlockWithTransactions[]): MempoolBlockWithTransactions[] {
@ -143,7 +147,7 @@ class MempoolBlocks {
return mempoolBlockDeltas;
}
public async makeBlockTemplates(newMempool: { [txid: string]: TransactionExtended }): Promise<void> {
public async makeBlockTemplates(newMempool: { [txid: string]: TransactionExtended }, saveResults: boolean = false): Promise<MempoolBlockWithTransactions[]> {
// prepare a stripped down version of the mempool with only the minimum necessary data
// to reduce the overhead of passing this data to the worker thread
const strippedMempool: { [txid: string]: ThreadTransaction } = {};
@ -184,19 +188,21 @@ class MempoolBlocks {
this.txSelectionWorker.postMessage({ type: 'set', mempool: strippedMempool });
const { blocks, clusters } = await workerResultPromise;
this.processBlockTemplates(newMempool, blocks, clusters);
// clean up thread error listener
this.txSelectionWorker?.removeListener('error', threadErrorListener);
return this.processBlockTemplates(newMempool, blocks, clusters, saveResults);
} catch (e) {
logger.err('makeBlockTemplates failed. ' + (e instanceof Error ? e.message : e));
}
return this.mempoolBlocks;
}
public async updateBlockTemplates(newMempool: { [txid: string]: TransactionExtended }, added: TransactionExtended[], removed: string[]): Promise<void> {
public async updateBlockTemplates(newMempool: { [txid: string]: TransactionExtended }, added: TransactionExtended[], removed: string[], saveResults: boolean = false): Promise<void> {
if (!this.txSelectionWorker) {
// need to reset the worker
return this.makeBlockTemplates(newMempool);
this.makeBlockTemplates(newMempool, saveResults);
return;
}
// prepare a stripped down version of the mempool with only the minimum necessary data
// to reduce the overhead of passing this data to the worker thread
@ -224,16 +230,16 @@ class MempoolBlocks {
this.txSelectionWorker.postMessage({ type: 'update', added: addedStripped, removed });
const { blocks, clusters } = await workerResultPromise;
this.processBlockTemplates(newMempool, blocks, clusters);
// clean up thread error listener
this.txSelectionWorker?.removeListener('error', threadErrorListener);
this.processBlockTemplates(newMempool, blocks, clusters, saveResults);
} catch (e) {
logger.err('updateBlockTemplates failed. ' + (e instanceof Error ? e.message : e));
}
}
private processBlockTemplates(mempool, blocks, clusters): void {
private processBlockTemplates(mempool, blocks, clusters, saveResults): MempoolBlockWithTransactions[] {
// update this thread's mempool with the results
blocks.forEach(block => {
block.forEach(tx => {
@ -278,10 +284,13 @@ class MempoolBlocks {
}).filter(tx => !!tx), undefined, undefined, blockIndex);
});
const deltas = this.calculateMempoolDeltas(this.mempoolBlocks, mempoolBlocks);
if (saveResults) {
const deltas = this.calculateMempoolDeltas(this.mempoolBlocks, mempoolBlocks);
this.mempoolBlocks = mempoolBlocks;
this.mempoolBlockDeltas = deltas;
}
this.mempoolBlocks = mempoolBlocks;
this.mempoolBlockDeltas = deltas;
return mempoolBlocks;
}
private dataToMempoolBlocks(transactions: TransactionExtended[],

View file

@ -19,6 +19,7 @@ import feeApi from './fee-api';
import BlocksAuditsRepository from '../repositories/BlocksAuditsRepository';
import BlocksSummariesRepository from '../repositories/BlocksSummariesRepository';
import Audit from './audit';
import { deepClone } from '../utils/clone';
class WebsocketHandler {
private wss: WebSocket.Server | undefined;
@ -251,9 +252,9 @@ class WebsocketHandler {
}
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
await mempoolBlocks.updateBlockTemplates(newMempool, newTransactions, deletedTransactions.map(tx => tx.txid));
await mempoolBlocks.updateBlockTemplates(newMempool, newTransactions, deletedTransactions.map(tx => tx.txid), true);
} else {
mempoolBlocks.updateMempoolBlocks(newMempool);
mempoolBlocks.updateMempoolBlocks(newMempool, true);
}
const mBlocks = mempoolBlocks.getMempoolBlocks();
@ -418,16 +419,18 @@ class WebsocketHandler {
const _memPool = memPool.getMempool();
let projectedBlocks;
// template calculation functions have mempool side effects, so calculate audits using
// a cloned copy of the mempool if we're running a different algorithm for mempool updates
const auditMempool = (config.MEMPOOL.ADVANCED_GBT_AUDIT === config.MEMPOOL.ADVANCED_GBT_MEMPOOL) ? _memPool : deepClone(_memPool);
if (config.MEMPOOL.ADVANCED_GBT_AUDIT) {
await mempoolBlocks.makeBlockTemplates(_memPool);
projectedBlocks = await mempoolBlocks.makeBlockTemplates(auditMempool, false);
} else {
mempoolBlocks.updateMempoolBlocks(_memPool);
projectedBlocks = mempoolBlocks.updateMempoolBlocks(auditMempool, false);
}
if (Common.indexingEnabled() && memPool.isInSync()) {
const projectedBlocks = mempoolBlocks.getMempoolBlocksWithTransactions();
const { censored, added, fresh, score } = Audit.auditBlock(transactions, projectedBlocks, _memPool);
const { censored, added, fresh, score } = Audit.auditBlock(transactions, projectedBlocks, auditMempool);
const matchRate = Math.round(score * 100 * 100) / 100;
const stripped = projectedBlocks[0]?.transactions ? projectedBlocks[0].transactions.map((tx) => {
@ -471,9 +474,9 @@ class WebsocketHandler {
}
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
await mempoolBlocks.updateBlockTemplates(_memPool, [], removed);
await mempoolBlocks.updateBlockTemplates(_memPool, [], removed, true);
} else {
mempoolBlocks.updateMempoolBlocks(_memPool);
mempoolBlocks.updateMempoolBlocks(_memPool, true);
}
const mBlocks = mempoolBlocks.getMempoolBlocks();
const mBlockDeltas = mempoolBlocks.getMempoolBlockDeltas();

View file

@ -111,7 +111,7 @@ class CpfpRepository {
}
}
public async $getCluster(clusterRoot: string): Promise<Cluster> {
public async $getCluster(clusterRoot: string): Promise<Cluster | void> {
const [clusterRows]: any = await DB.query(
`
SELECT *
@ -121,8 +121,11 @@ class CpfpRepository {
[clusterRoot]
);
const cluster = clusterRows[0];
cluster.txs = this.unpack(cluster.txs);
return cluster;
if (cluster?.txs) {
cluster.txs = this.unpack(cluster.txs);
return cluster;
}
return;
}
public async $deleteClustersFrom(height: number): Promise<void> {
@ -136,9 +139,9 @@ class CpfpRepository {
[height]
) as RowDataPacket[][];
if (rows?.length) {
for (let clusterToDelete of rows) {
const txs = this.unpack(clusterToDelete.txs);
for (let tx of txs) {
for (const clusterToDelete of rows) {
const txs = this.unpack(clusterToDelete?.txs);
for (const tx of txs) {
await transactionRepository.$removeTransaction(tx.txid);
}
}
@ -204,20 +207,25 @@ class CpfpRepository {
return [];
}
const arrayBuffer = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
const txs: Ancestor[] = [];
const view = new DataView(arrayBuffer);
for (let offset = 0; offset < arrayBuffer.byteLength; offset += 44) {
const txid = Array.from(new Uint8Array(arrayBuffer, offset, 32)).reverse().map(b => b.toString(16).padStart(2, '0')).join('');
const weight = view.getUint32(offset + 32);
const fee = Number(view.getBigUint64(offset + 36));
txs.push({
txid,
weight,
fee
});
try {
const arrayBuffer = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
const txs: Ancestor[] = [];
const view = new DataView(arrayBuffer);
for (let offset = 0; offset < arrayBuffer.byteLength; offset += 44) {
const txid = Array.from(new Uint8Array(arrayBuffer, offset, 32)).reverse().map(b => b.toString(16).padStart(2, '0')).join('');
const weight = view.getUint32(offset + 32);
const fee = Number(view.getBigUint64(offset + 36));
txs.push({
txid,
weight,
fee
});
}
return txs;
} catch (e) {
logger.warn(`Failed to unpack CPFP cluster. Reason: ` + (e instanceof Error ? e.message : e));
return [];
}
return txs;
}
}

View file

@ -3,15 +3,6 @@ import logger from '../logger';
import { Ancestor, CpfpInfo } from '../mempool.interfaces';
import cpfpRepository from './CpfpRepository';
interface CpfpSummary {
txid: string;
cluster: string;
root: string;
txs: Ancestor[];
height: number;
fee_rate: number;
}
class TransactionRepository {
public async $setCluster(txid: string, clusterRoot: string): Promise<void> {
try {
@ -72,7 +63,9 @@ class TransactionRepository {
const txid = txRows[0].id.toLowerCase();
const clusterId = txRows[0].root.toLowerCase();
const cluster = await cpfpRepository.$getCluster(clusterId);
return this.convertCpfp(txid, cluster);
if (cluster) {
return this.convertCpfp(txid, cluster);
}
}
} catch (e) {
logger.err('Cannot get transaction cpfp info from db. Reason: ' + (e instanceof Error ? e.message : e));
@ -81,13 +74,18 @@ class TransactionRepository {
}
public async $removeTransaction(txid: string): Promise<void> {
await DB.query(
`
DELETE FROM compact_transactions
WHERE txid = UNHEX(?)
`,
[txid]
);
try {
await DB.query(
`
DELETE FROM compact_transactions
WHERE txid = UNHEX(?)
`,
[txid]
);
} catch (e) {
logger.warn('Cannot delete transaction cpfp info from db. Reason: ' + (e instanceof Error ? e.message : e));
throw e;
}
}
private convertCpfp(txid, cluster): CpfpInfo {
@ -95,7 +93,7 @@ class TransactionRepository {
const ancestors: Ancestor[] = [];
let matched = false;
for (const tx of cluster.txs) {
for (const tx of (cluster?.txs || [])) {
if (tx.txid === txid) {
matched = true;
} else if (!matched) {

View file

@ -0,0 +1,14 @@
// simple recursive deep clone for literal-type objects
// does not preserve Dates, Maps, Sets etc
// does not support recursive objects
// properties deeper than maxDepth will be shallow cloned
export function deepClone(obj: any, maxDepth: number = 50, depth: number = 0): any {
let cloned = obj;
if (depth < maxDepth && typeof obj === 'object') {
cloned = Array.isArray(obj) ? [] : {};
for (const key in obj) {
cloned[key] = deepClone(obj[key], maxDepth, depth + 1);
}
}
return cloned;
}

View file

@ -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 January 25, 2022.
Signed: AlexLloyd0

View file

@ -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 January 25, 2022.
Signed: Arooba-git

View file

@ -3,8 +3,8 @@ const fs = require('fs');
let PROXY_CONFIG = require('./proxy.conf');
PROXY_CONFIG.forEach(entry => {
entry.target = entry.target.replace("mempool.space", "mempool-staging.tk7.mempool.space");
entry.target = entry.target.replace("liquid.network", "liquid-staging.tk7.mempool.space");
entry.target = entry.target.replace("mempool.space", "mempool-staging.fra.mempool.space");
entry.target = entry.target.replace("liquid.network", "liquid-staging.fra.mempool.space");
entry.target = entry.target.replace("bisq.markets", "bisq-staging.fra.mempool.space");
});

View file

@ -53,7 +53,7 @@
<td [innerHTML]="'&lrm;' + (block.weight | wuBytes: 2)"></td>
</tr>
<tr *ngIf="auditAvailable">
<td i18n="latest-blocks.health">Health <a class="info-link" [routerLink]="['/docs/faq' | relativeUrl ]" fragment="what-is-block-health"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></a></td>
<td><ng-container i18n="latest-blocks.health">Health</ng-container> <a class="info-link" [routerLink]="['/docs/faq' | relativeUrl ]" fragment="what-is-block-health"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></a></td>
<td>
<span
class="health-badge badge"
@ -206,13 +206,13 @@
<div class="box" *ngIf="!error && webGlEnabled && showAudit">
<div class="nav nav-tabs" *ngIf="isMobile && showAudit">
<a class="nav-link" [class.active]="mode === 'projected'"
fragment="projected" (click)="changeMode('projected')"><ng-container i18n="block.projected">Projected</ng-container>&nbsp;&nbsp;<span class="badge badge-pill badge-warning" i18n="beta">beta</span></a>
fragment="projected" (click)="changeMode('projected')"><ng-container i18n="block.expected">Expected</ng-container>&nbsp;&nbsp;<span class="badge badge-pill badge-warning" i18n="beta">beta</span></a>
<a class="nav-link" [class.active]="mode === 'actual'" i18n="block.actual"
fragment="actual" (click)="changeMode('actual')">Actual</a>
</div>
<div class="row">
<div class="col-sm">
<h3 class="block-subtitle" *ngIf="!isMobile"><ng-container i18n="block.projected-block">Projected Block</ng-container> <span class="badge badge-pill badge-warning beta" i18n="beta">beta</span></h3>
<h3 class="block-subtitle" *ngIf="!isMobile"><ng-container i18n="block.expected-block">Expected Block</ng-container> <span class="badge badge-pill badge-warning beta" i18n="beta">beta</span></h3>
<div class="block-graph-wrapper">
<app-block-overview-graph #blockGraphProjected [isLoading]="isLoadingOverview" [resolution]="75"
[blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" [auditHighlighting]="showAudit"
@ -221,7 +221,7 @@
</div>
</div>
<div class="col-sm" *ngIf="!isMobile">
<h3 class="block-subtitle" *ngIf="!isMobile" i18n="block.actual-block">Actual Block</h3>
<h3 class="block-subtitle actual" *ngIf="!isMobile"><ng-container i18n="block.actual-block">Actual Block</ng-container> <a class="info-link" [routerLink]="['/docs/faq' | relativeUrl ]" fragment="how-do-block-audits-work"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></a></h3>
<div class="block-graph-wrapper">
<app-block-overview-graph #blockGraphActual [isLoading]="isLoadingOverview" [resolution]="75"
[blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" mode="mined" [auditHighlighting]="showAudit"

View file

@ -41,6 +41,17 @@
}
}
.block-subtitle.actual a {
position: absolute;
top: -3px;
}
.block-subtitle.actual fa-icon {
color: rgba(255, 255, 255, 0.4);
font-size: 18px;
margin-left: 8px;
}
h1 {
margin: 0px;
padding: 0px;

View file

@ -17,7 +17,7 @@
<app-blockchain [pageIndex]="pageIndex" [pages]="pages" [blocksPerPage]="blocksPerPage" [minScrollWidth]="minScrollWidth"></app-blockchain>
</div>
<div class="reset-scroll" [class.hidden]="pageIndex === 0" (click)="resetScroll()">
<fa-icon [icon]="['fas', 'circle-left']" [fixedWidth]="true" i18n-title="blocks.return-to-tip" title="Return to tip"></fa-icon>
<fa-icon [icon]="['fas', 'circle-left']" [fixedWidth]="true"></fa-icon>
</div>
</div>

View file

@ -298,6 +298,10 @@ export class StartComponent implements OnInit, OnDestroy {
}
ngOnDestroy() {
if (this.blockchainContainer?.nativeElement) {
// clean up scroll position to prevent caching wrong scroll in Firefox
this.blockchainContainer.nativeElement.scrollLeft = 0;
}
this.timeLtrSubscription.unsubscribe();
this.chainTipSubscription.unsubscribe();
this.markBlockSubscription.unsubscribe();

View file

@ -8671,6 +8671,15 @@ export const faqData = [
type: "endpoint",
category: "advanced",
showConditions: bitcoinNetworks,
options: { officialOnly: true },
fragment: "how-do-block-audits-work",
title: "How do block audits work?",
},
{
type: "endpoint",
category: "advanced",
showConditions: bitcoinNetworks,
options: { officialOnly: true },
fragment: "what-is-block-health",
title: "What is block health?",
},

View file

@ -1,4 +1,4 @@
<div *ngFor="let item of tabData">
<p *ngIf="( item.type === 'category' ) && ( item.showConditions.indexOf(network.val) > -1 )">{{ item.title }}</p>
<a *ngIf="( item.type !== 'category' ) && ( item.showConditions.indexOf(network.val) > -1 )" [routerLink]="['./']" fragment="{{ item.fragment }}" (click)="navLinkClick($event)">{{ item.title }}</a>
<a *ngIf="( item.type !== 'category' ) && ( item.showConditions.indexOf(network.val) > -1 ) && ( !item.hasOwnProperty('options') || ( item.hasOwnProperty('options') && item.options.hasOwnProperty('officialOnly') && item.options.officialOnly && officialMempoolInstance ) )" [routerLink]="['./']" fragment="{{ item.fragment }}" (click)="navLinkClick($event)">{{ item.title }}</a>
</div>

View file

@ -1,4 +1,5 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { Env, StateService } from '../../services/state.service';
import { restApiDocsData } from './api-docs-data';
import { faqData } from './api-docs-data';
@ -12,11 +13,17 @@ export class ApiDocsNavComponent implements OnInit {
@Input() network: any;
@Input() whichTab: string;
@Output() navLinkClickEvent: EventEmitter<any> = new EventEmitter();
env: Env;
tabData: any[];
officialMempoolInstance: boolean;
constructor() { }
constructor(
private stateService: StateService
) { }
ngOnInit(): void {
this.env = this.stateService.env;
this.officialMempoolInstance = this.env.OFFICIAL_MEMPOOL_SPACE;
if (this.whichTab === 'rest') {
this.tabData = restApiDocsData;
} else if (this.whichTab === 'faq') {

View file

@ -15,11 +15,13 @@
</div>
<div class="doc-item-container" *ngFor="let item of faq">
<h3 *ngIf="item.type === 'category'">{{ item.title }}</h3>
<div *ngIf="item.type !== 'category'" class="endpoint-container" id="{{ item.fragment }}">
<a id="{{ item.fragment + '-tab-header' }}" class="section-header" (click)="anchorLinkClick( $event )" [routerLink]="['./']" fragment="{{ item.fragment }}"><table><tr><td>{{ item.title }}</td><td><span>{{ item.category }}</span></td></tr></table></a>
<div class="endpoint-content">
<ng-container *ngTemplateOutlet="dict[item.fragment]" class="endpoint"></ng-container>
<div *ngIf="!item.hasOwnProperty('options') || ( item.hasOwnProperty('options') && item.options.hasOwnProperty('officialOnly') && item.options.officialOnly && officialMempoolInstance )">
<h3 *ngIf="item.type === 'category'">{{ item.title }}</h3>
<div *ngIf="item.type !== 'category'" class="endpoint-container" id="{{ item.fragment }}">
<a id="{{ item.fragment + '-tab-header' }}" class="section-header" (click)="anchorLinkClick( $event )" [routerLink]="['./']" fragment="{{ item.fragment }}"><table><tr><td>{{ item.title }}</td><td><span>{{ item.category }}</span></td></tr></table></a>
<div class="endpoint-content">
<ng-container *ngTemplateOutlet="dict[item.fragment]" class="endpoint"></ng-container>
</div>
</div>
</div>
</div>
@ -218,17 +220,42 @@
<p>For unconfirmed CPFP transactions, Mempool will show the effective feerate (along with descendent & ancestor transaction information) on the transaction page. For confirmed transactions, CPFP relationships are not stored, so this additional information is not shown.</p>
</ng-template>
<ng-template type="how-do-block-audits-work">
<p>A block audit visually compares Mempool's expected block to the actual block for a particular block height.</p>
<p>How is the expected block determined? Mempool monitors its view of the mempool and runs a re-implementation of Bitcoin Core's transaction selection algorithm to determine the transactions it expects to see in upcoming blocks (<a href="https://github.com/mempool/mempool/blob/master/backend/src/api/mempool-blocks.ts" target="_blank">source code here</a>). Since there is a continual flow of new transactions, this algorithm runs every 2 seconds, and as a result, you will see the transactions <a href="/mempool-block/0">projected to be in upcoming blocks</a> change in near real-time.</p>
<p>At the moment a new block is mined, Mempool saves a snapshot of its projected block template for the next block. We call this snapshot the <b>expected block</b> for the block height in question, and it serves as the basis for the block audit.</p>
<p>When details for an expected block and actual block are available, we can compare them. <b>The purpose of block audits is to deduce when miners intentionally include or exclude transactions from blocks they mine.</b> Since this information cannot be precisely known, Mempool uses a handful of heuristics to accomplish this.</p>
<p>Block audits highlight transactions in different colors to convey these heuristics:</p>
<ul class="no-bull block-audit">
<li><span class="block-audit-highlight-color added"></span><code>Added</code><p>A transaction is highlighted blue if it is not present in the expected block, present in the actual block, and also either:</p>
<ul>
<li>far out of the expected feerate range, meaning the miner may have intentionally prioritized the transaction</li>
<li>not in the mempool at all, meaning the miner may have accepted the transaction out-of-band</li>
</ul>
<p>Added transactions do not negatively affect <a [routerLink]="['/docs/faq' | relativeUrl]" fragment="what-is-block-health">block health</a>.</p></li>
<li><span class="block-audit-highlight-color recent"></span><code>Recently broadcasted</code><p>A transaction is highlighted dark pink if it is present in the expected block, not present in the actual block, and was first seen by Mempool's Bitcoin node within 3 minutes of the block being mined.</p><p>Due to network latency and other factors, it can take time for a miner's Bitcoin nodes to receive a transaction, so we do not assume a miner has intentionally excluded such a transaction from a block.</p><p>Recently-broadcasted transactions do not negatively affect <a [routerLink]="['/docs/faq' | relativeUrl]" fragment="what-is-block-health">block health</a>.</p></li>
<li><span class="block-audit-highlight-color marginal"></span><code>Marginal fee</code>
<p>A transaction is darkened if it is in the low end of the expected feerate range and missing in either the expected block or the actual block.</p><p>Such a transaction may have been displaced by an added transaction, or it may have been displaced by another transaction from the mempool that was also at the low end of the expected feerate range for the block. In either case, the deviation is not considered notable.</p>
<p>Marginal fee transactions do not negatively affect <a [routerLink]="['/docs/faq' | relativeUrl]" fragment="what-is-block-health">block health</a>.</p></li>
<li><span class="block-audit-highlight-color removed"></span><code>Removed</code><p>A transaction is highlighted bright pink if it is present in the expected block, not present in the actual block, and qualifies as neither recently-broadcasted nor marginal-fee. In other words, it has been in the mempool long enough to be widely propagated and has a feerate that is well within the range expected for the block. There is a chance such a transaction may have been intentionally excluded from the block.<p>Removed transactions do negatively affect <a [routerLink]="['/docs/faq' | relativeUrl]" fragment="what-is-block-health">block health</a>.</p></li>
</ul>
<p>See how results of the block audit are used to devise the block health score <a [routerLink]="['/docs/faq' | relativeUrl]" fragment="what-is-block-health">below</a>.</p>
<p class='note'>Because of this feature's resource usage and availability requirements, it is only supported on official mempool.space instances.</p>
</ng-template>
<ng-template type="what-is-block-health">
<p>Block health indicates the extent of <i>potential</i> censorship in a block. This is determined by counting how many expected transactions a block is missing—a block that is not missing any expected transactions will have 100% health, while a block missing 1 or more expected transactions will have sub-100% health.</p>
<p>How does this work? Let <span class='math'>s<sub>expected</sub></span> be the set of all transactions Mempool expected to be in a block and let <span class='math'>s<sub>actual</sub></span> be the set of all transactions actually in a block. Let <span class='math'>n</span> be the number of all transactions in both <span class='math'>s<sub>expected</sub></span> and <span class='math'>s<sub>actual</sub></span>.</p>
<p>Then let <span class='math'>r</span> be the number of removed transactions—all transactions expected to be in <span class='math'>s<sub>actual</sub></span> but not actually in it (excluding those that have been recently broadcast; see below).</p>
<p>Block health is a measure of how many transactions appear to be intentionally excluded from a block—a block without any transactions that appear intentionally excluded will have 100% health, while a block with 1 or more transactions that appear intentionally excluded will have sub-100% health.</p>
<p>How is it calculated? Let <span class='math'>s<sub>expected</sub></span> be the set of all transactions in Mempool's expected block and let <span class='math'>s<sub>actual</sub></span> be the set of all transactions in the actual block. Then let <span class='math'>n</span> be the number of all transactions in both <span class='math'>s<sub>expected</sub></span> and <span class='math'>s<sub>actual</sub></span>.</p>
<p>Furthermore, let <span class='math'>r</span> be the number of transactions Mempool deduces were <a [routerLink]="['/docs/faq' | relativeUrl]" fragment="how-do-block-audits-work">intentionally excluded</a> from <span class='math'>s<sub>actual</sub></span>.</p>
<p>Block health is calculated as <span class='math'>n / ( n + r</span> ).</p>
<p>Transactions appearing in both <span class='math'>s<sub>expected</sub></span> and <span class='math'>s<sub>actual</sub></span> are used (instead of a block's full transaction count) in order to minimize chances that block health is impacted by missing transactions that don't imply censorship:</p>
<p>The number of transactions appearing in both <span class='math'>s<sub>expected</sub></span> and <span class='math'>s<sub>actual</sub></span> is used (instead of a block's full transaction count) in order to minimize chances that block health is inadvertently impacted by transactions that were most likely not intentionally excluded:</p>
<ul>
<li>recently-broadcast transactions, since the miner may simply not have received them</li>
<li>certain low-feerate transactions, since the miner may have opted to replace them with more profitable out-of-band transactions</li>
</ul>
<p>Mempool uses a re-implementation of Bitcoin Core's transaction selection algorithm to determine the transactions it expects to see in the next block.</p>
<p>As a result, block health is <i>not</i> intended to be a measure of how closely an expected block resembles an actual block. The actual block can be vastly different from the expected block, but if no transactions appear to be intentionally excluded, it will have a high health rating (<a [routerLink]="['/block/0000000000000000000515e202c8ae73c8155fc472422d7593af87aa74f2cf3d']">extreme example</a>).</p>
<p>See more context in our FAQ on <a [routerLink]="['/docs/faq' | relativeUrl]" fragment="how-do-block-audits-work">block audits</a>.</p>
<p class='note'>Because of this feature's resource usage and availability requirements, it is only supported on official mempool.space instances.</p>
</ng-template>
<ng-template type="who-runs-this-website">

View file

@ -42,6 +42,48 @@ li.nav-item {
}
}
ul.no-bull {
list-style: none;
}
ul.no-bull.block-audit li code {
text-transform: uppercase;
}
ul.no-bull.block-audit li span {
margin-right: 10px;
}
ul.no-bull.block-audit li span.block-audit-highlight-color.added {
color: #0099ff;
}
ul.no-bull.block-audit li span.block-audit-highlight-color.removed {
color: #f344df;
}
ul.no-bull.block-audit li span.block-audit-highlight-color.recent {
color: #8a3480;
}
ul.no-bull.block-audit li span.block-audit-highlight-color.marginal {
color: #414127;
}
ul.no-bull.block-audit li p {
margin-left: 25px;
margin-top: 5px;
}
ul.no-bull.block-audit li ul {
margin-left: 15px;
margin-bottom: 15px;
}
ul.no-bull.block-audit code{
background-color: inherit;
}
.doc-welcome-note {
margin-bottom: 0;
}

View file

@ -1,7 +1,7 @@
import { Component, OnInit, Input, QueryList, AfterViewInit, ViewChildren } from '@angular/core';
import { Env, StateService } from '../../services/state.service';
import { Observable, merge, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import { Observable, merge, of, Subject } from 'rxjs';
import { tap, takeUntil } from 'rxjs/operators';
import { ActivatedRoute } from "@angular/router";
import { faqData, restApiDocsData, wsApiDocsData } from './api-docs-data';
import { FaqTemplateDirective } from '../faq-template/faq-template.component';
@ -12,6 +12,7 @@ import { FaqTemplateDirective } from '../faq-template/faq-template.component';
styleUrls: ['./api-docs.component.scss']
})
export class ApiDocsComponent implements OnInit, AfterViewInit {
private destroy$: Subject<any> = new Subject<any>();
plainHostname = document.location.hostname;
electrsPort = 0;
hostname = document.location.hostname;
@ -82,7 +83,7 @@ export class ApiDocsComponent implements OnInit, AfterViewInit {
this.restDocs = restApiDocsData;
this.wsDocs = wsApiDocsData;
this.network$.subscribe((network) => {
this.network$.pipe(takeUntil(this.destroy$)).subscribe((network) => {
this.active = (network === 'liquid' || network === 'liquidtestnet') ? 2 : 0;
switch( network ) {
case "":
@ -102,6 +103,8 @@ export class ApiDocsComponent implements OnInit, AfterViewInit {
}
ngOnDestroy(): void {
this.destroy$.next(true);
this.destroy$.complete();
window.removeEventListener('scroll', this.onDocScroll);
}

View file

@ -546,7 +546,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/master-page/master-page.component.html</context>
<context context-type="linenumber">49,51</context>
<context context-type="linenumber">48,50</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/pool-ranking/pool-ranking.component.html</context>
@ -1420,7 +1420,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/master-page/master-page.component.html</context>
<context context-type="linenumber">58,61</context>
<context context-type="linenumber">57,60</context>
</context-group>
</trans-unit>
<trans-unit id="address-label.multisig" datatype="html">
@ -2457,7 +2457,7 @@
<source>Health</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/block/block.component.html</context>
<context context-type="linenumber">56,59</context>
<context context-type="linenumber">56</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/blocks-list/blocks-list.component.html</context>
@ -2550,13 +2550,25 @@
<note priority="1" from="description">Total subsidy and fees in a block</note>
<note priority="1" from="meaning">block.subsidy-and-fees</note>
</trans-unit>
<trans-unit id="26f41d32df1646d45fcb03fe6952fb3eccf60b0f" datatype="html">
<source>Projected</source>
<trans-unit id="23fa95fce7b4badf5ad584d4a1712d558266266f" datatype="html">
<source>Expected</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/block/block.component.html</context>
<context context-type="linenumber">209,211</context>
<context context-type="linenumber">209</context>
</context-group>
<note priority="1" from="description">block.projected</note>
<note priority="1" from="description">block.expected</note>
</trans-unit>
<trans-unit id="7cbedd89f60daafaf0e56363900d666a4e02ffb1" datatype="html">
<source>beta</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/block/block.component.html</context>
<context context-type="linenumber">209,210</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/block/block.component.html</context>
<context context-type="linenumber">215,217</context>
</context-group>
<note priority="1" from="description">beta</note>
</trans-unit>
<trans-unit id="1da6d9283e3222148d76c10c8e37abeeb66c93cb" datatype="html">
<source>Actual</source>
@ -2566,19 +2578,19 @@
</context-group>
<note priority="1" from="description">block.actual</note>
</trans-unit>
<trans-unit id="a37e529c0d7b39d95861dd019cb78bb9ac9a3c5d" datatype="html">
<source>Projected Block</source>
<trans-unit id="97577daae15cc7f30ab4d0f4f4dfb8045477aefd" datatype="html">
<source>Expected Block</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/block/block.component.html</context>
<context context-type="linenumber">215,217</context>
<context context-type="linenumber">215</context>
</context-group>
<note priority="1" from="description">block.projected-block</note>
<note priority="1" from="description">block.expected-block</note>
</trans-unit>
<trans-unit id="6efa73f0d6f0844a1e0c341c9b88323f51852d91" datatype="html">
<source>Actual Block</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/block/block.component.html</context>
<context context-type="linenumber">224,226</context>
<context context-type="linenumber">224</context>
</context-group>
<note priority="1" from="description">block.actual-block</note>
</trans-unit>
@ -3308,7 +3320,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/master-page/master-page.component.html</context>
<context context-type="linenumber">52,54</context>
<context context-type="linenumber">51,53</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/statistics/statistics.component.ts</context>
@ -3332,7 +3344,7 @@
<source>Lightning Explorer</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/master-page/master-page.component.html</context>
<context context-type="linenumber">44,45</context>
<context context-type="linenumber">44,47</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts</context>
@ -3340,19 +3352,11 @@
</context-group>
<note priority="1" from="description">master-page.lightning</note>
</trans-unit>
<trans-unit id="7cbedd89f60daafaf0e56363900d666a4e02ffb1" datatype="html">
<source>beta</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/master-page/master-page.component.html</context>
<context context-type="linenumber">45,48</context>
</context-group>
<note priority="1" from="description">beta</note>
</trans-unit>
<trans-unit id="fcfd4675b4c90f08d18d3abede9a9a4dff4cfdc7" datatype="html">
<source>Documentation</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/master-page/master-page.component.html</context>
<context context-type="linenumber">55,57</context>
<context context-type="linenumber">54,56</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/docs/docs/docs.component.html</context>
@ -3901,14 +3905,6 @@
</context-group>
<note priority="1" from="description">search-form.search-title</note>
</trans-unit>
<trans-unit id="7150c828c25c15df9ed0e2a819110c90aabd9881" datatype="html">
<source>Return to tip</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/start/start.component.html</context>
<context context-type="linenumber">20</context>
</context-group>
<note priority="1" from="description">blocks.return-to-tip</note>
</trans-unit>
<trans-unit id="75c20c8a9cd9723d45bee0230dd582d7c2e4ecbc" datatype="html">
<source>Mempool by vBytes (sat/vByte)</source>
<context-group purpose="location">
@ -4760,7 +4756,7 @@
<source>REST API service</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/docs/api-docs/api-docs.component.html</context>
<context context-type="linenumber">39,40</context>
<context context-type="linenumber">41,42</context>
</context-group>
<note priority="1" from="description">api-docs.title</note>
</trans-unit>
@ -4768,11 +4764,11 @@
<source>Endpoint</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/docs/api-docs/api-docs.component.html</context>
<context context-type="linenumber">48,49</context>
<context context-type="linenumber">50,51</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/docs/api-docs/api-docs.component.html</context>
<context context-type="linenumber">102,105</context>
<context context-type="linenumber">104,107</context>
</context-group>
<note priority="1" from="description">Api docs endpoint</note>
</trans-unit>
@ -4780,18 +4776,18 @@
<source>Description</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/docs/api-docs/api-docs.component.html</context>
<context context-type="linenumber">67,68</context>
<context context-type="linenumber">69,70</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/docs/api-docs/api-docs.component.html</context>
<context context-type="linenumber">106,107</context>
<context context-type="linenumber">108,109</context>
</context-group>
</trans-unit>
<trans-unit id="a706b1ded7506620b153dbcdea8108e6691bbbd9" datatype="html">
<source>Default push: <x id="START_TAG_CODE" ctype="x-code" equiv-text="&lt;code&gt;"/><x id="INTERPOLATION" equiv-text="&apos;track-ad"/> action: &apos;want&apos;, data: [&apos;blocks&apos;, ...] <x id="INTERPOLATION_1" equiv-text="{{ &apos;}&apos; }}"/><x id="CLOSE_TAG_CODE" ctype="x-code" equiv-text="&lt;/code&gt;"/> to express what you want pushed. Available: <x id="START_TAG_CODE" ctype="x-code" equiv-text="&lt;code&gt;"/>blocks<x id="CLOSE_TAG_CODE" ctype="x-code" equiv-text="&lt;/code&gt;"/>, <x id="START_TAG_CODE" ctype="x-code" equiv-text="&lt;code&gt;"/>mempool-blocks<x id="CLOSE_TAG_CODE" ctype="x-code" equiv-text="&lt;/code&gt;"/>, <x id="START_TAG_CODE" ctype="x-code" equiv-text="&lt;code&gt;"/>live-2h-chart<x id="CLOSE_TAG_CODE" ctype="x-code" equiv-text="&lt;/code&gt;"/>, and <x id="START_TAG_CODE" ctype="x-code" equiv-text="&lt;code&gt;"/>stats<x id="CLOSE_TAG_CODE" ctype="x-code" equiv-text="&lt;/code&gt;"/>.<x id="LINE_BREAK" ctype="lb" equiv-text="Push transa"/><x id="LINE_BREAK" ctype="lb" equiv-text="Push transa"/>Push transactions related to address: <x id="START_TAG_CODE" ctype="x-code" equiv-text="&lt;code&gt;"/><x id="INTERPOLATION" equiv-text="&apos;track-ad"/> &apos;track-address&apos;: &apos;3PbJ...bF9B&apos; <x id="INTERPOLATION_1" equiv-text="{{ &apos;}&apos; }}"/><x id="CLOSE_TAG_CODE" ctype="x-code" equiv-text="&lt;/code&gt;"/> to receive all new transactions containing that address as input or output. Returns an array of transactions. <x id="START_TAG_CODE" ctype="x-code" equiv-text="&lt;code&gt;"/>address-transactions<x id="CLOSE_TAG_CODE" ctype="x-code" equiv-text="&lt;/code&gt;"/> for new mempool transactions, and <x id="START_TAG_CODE" ctype="x-code" equiv-text="&lt;code&gt;"/>block-transactions<x id="CLOSE_TAG_CODE" ctype="x-code" equiv-text="&lt;/code&gt;"/> for new block confirmed transactions.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/docs/api-docs/api-docs.component.html</context>
<context context-type="linenumber">107,108</context>
<context context-type="linenumber">109,110</context>
</context-group>
<note priority="1" from="description">api-docs.websocket.websocket</note>
</trans-unit>