use accelerated rates for block templates & show in viz

This commit is contained in:
Mononaut 2023-05-26 21:10:32 -04:00
parent 412d83f961
commit fe76a0fd08
No known key found for this signature in database
GPG Key ID: A3F058E41374C04E
18 changed files with 74 additions and 16 deletions

View File

@ -5,7 +5,7 @@ import { TransactionExtended, MempoolBlockWithTransactions } from '../mempool.in
const PROPAGATION_MARGIN = 180; // in seconds, time since a transaction is first seen after which it is assumed to have propagated to all miners const PROPAGATION_MARGIN = 180; // in seconds, time since a transaction is first seen after which it is assumed to have propagated to all miners
class Audit { class Audit {
auditBlock(transactions: TransactionExtended[], projectedBlocks: MempoolBlockWithTransactions[], mempool: { [txId: string]: TransactionExtended }, accelerations: { [txid: string]: number }) auditBlock(transactions: TransactionExtended[], projectedBlocks: MempoolBlockWithTransactions[], mempool: { [txId: string]: TransactionExtended })
: { censored: string[], added: string[], fresh: string[], sigop: string[], accelerated: string[], score: number, similarity: number } { : { censored: string[], added: string[], fresh: string[], sigop: string[], accelerated: string[], score: number, similarity: number } {
if (!projectedBlocks?.[0]?.transactionIds || !mempool) { if (!projectedBlocks?.[0]?.transactionIds || !mempool) {
return { censored: [], added: [], fresh: [], sigop: [], accelerated: [], score: 0, similarity: 1 }; return { censored: [], added: [], fresh: [], sigop: [], accelerated: [], score: 0, similarity: 1 };
@ -27,7 +27,7 @@ class Audit {
const now = Math.round((Date.now() / 1000)); const now = Math.round((Date.now() / 1000));
for (const tx of transactions) { for (const tx of transactions) {
inBlock[tx.txid] = tx; inBlock[tx.txid] = tx;
if (accelerations[tx.txid]) { if (tx.acceleration) {
accelerated.push(tx.txid); accelerated.push(tx.txid);
} }
} }

View File

@ -213,6 +213,7 @@ class BitcoinRoutes {
effectiveFeePerVsize: tx.effectiveFeePerVsize || null, effectiveFeePerVsize: tx.effectiveFeePerVsize || null,
sigops: tx.sigops, sigops: tx.sigops,
adjustedVsize: tx.adjustedVsize, adjustedVsize: tx.adjustedVsize,
acceleration: tx.acceleration
}); });
return; return;
} }

View File

@ -101,6 +101,7 @@ export class Common {
fee: tx.fee, fee: tx.fee,
vsize: tx.weight / 4, vsize: tx.weight / 4,
value: tx.vout.reduce((acc, vout) => acc + (vout.value ? vout.value : 0), 0), value: tx.vout.reduce((acc, vout) => acc + (vout.value ? vout.value : 0), 0),
acc: tx.acceleration || undefined,
rate: tx.effectiveFeePerVsize, rate: tx.effectiveFeePerVsize,
}; };
} }

View File

@ -169,7 +169,7 @@ class MempoolBlocks {
for (let i = 0; i < Math.max(mempoolBlocks.length, prevBlocks.length); i++) { for (let i = 0; i < Math.max(mempoolBlocks.length, prevBlocks.length); i++) {
let added: TransactionStripped[] = []; let added: TransactionStripped[] = [];
let removed: string[] = []; let removed: string[] = [];
const changed: { txid: string, rate: number | undefined }[] = []; const changed: { txid: string, rate: number | undefined, acc: number | undefined }[] = [];
if (mempoolBlocks[i] && !prevBlocks[i]) { if (mempoolBlocks[i] && !prevBlocks[i]) {
added = mempoolBlocks[i].transactions; added = mempoolBlocks[i].transactions;
} else if (!mempoolBlocks[i] && prevBlocks[i]) { } else if (!mempoolBlocks[i] && prevBlocks[i]) {
@ -191,8 +191,8 @@ class MempoolBlocks {
mempoolBlocks[i].transactions.forEach(tx => { mempoolBlocks[i].transactions.forEach(tx => {
if (!prevIds[tx.txid]) { if (!prevIds[tx.txid]) {
added.push(tx); added.push(tx);
} else if (tx.rate !== prevIds[tx.txid].rate) { } else if (tx.rate !== prevIds[tx.txid].rate || tx.acc !== prevIds[tx.txid].acc) {
changed.push({ txid: tx.txid, rate: tx.rate }); changed.push({ txid: tx.txid, rate: tx.rate, acc: tx.acc });
} }
}); });
} }
@ -205,7 +205,7 @@ class MempoolBlocks {
return mempoolBlockDeltas; return mempoolBlockDeltas;
} }
public async $makeBlockTemplates(newMempool: { [txid: string]: MempoolTransactionExtended }, saveResults: boolean = false, accelerations: { [txid: string]: number } = {}): Promise<MempoolBlockWithTransactions[]> { public async $makeBlockTemplates(newMempool: { [txid: string]: MempoolTransactionExtended }, saveResults: boolean = false): Promise<MempoolBlockWithTransactions[]> {
const start = Date.now(); const start = Date.now();
// reset mempool short ids // reset mempool short ids
@ -221,7 +221,7 @@ class MempoolBlocks {
if (entry.uid != null) { if (entry.uid != null) {
strippedMempool.set(entry.uid, { strippedMempool.set(entry.uid, {
uid: entry.uid, uid: entry.uid,
fee: entry.fee, fee: entry.fee + (entry.acceleration || 0),
weight: (entry.adjustedVsize * 4), weight: (entry.adjustedVsize * 4),
sigops: entry.sigops, sigops: entry.sigops,
feePerVsize: entry.adjustedFeePerVsize || entry.feePerVsize, feePerVsize: entry.adjustedFeePerVsize || entry.feePerVsize,
@ -281,13 +281,14 @@ class MempoolBlocks {
for (const tx of Object.values(added)) { for (const tx of Object.values(added)) {
this.setUid(tx); this.setUid(tx);
} }
const removedUids = removed.map(tx => this.getUid(tx)).filter(uid => uid != null) as number[]; const removedUids = removed.map(tx => this.getUid(tx)).filter(uid => uid != null) as number[];
// prepare a stripped down version of the mempool with only the minimum necessary data // 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 // to reduce the overhead of passing this data to the worker thread
const addedStripped: CompactThreadTransaction[] = added.filter(entry => entry.uid != null).map(entry => { const addedStripped: CompactThreadTransaction[] = added.filter(entry => entry.uid != null).map(entry => {
return { return {
uid: entry.uid || 0, uid: entry.uid || 0,
fee: entry.fee, fee: entry.fee + (entry.acceleration || 0),
weight: (entry.adjustedVsize * 4), weight: (entry.adjustedVsize * 4),
sigops: entry.sigops, sigops: entry.sigops,
feePerVsize: entry.adjustedFeePerVsize || entry.feePerVsize, feePerVsize: entry.adjustedFeePerVsize || entry.feePerVsize,

View File

@ -22,6 +22,8 @@ class Mempool {
private $asyncMempoolChangedCallback: ((newMempool: {[txId: string]: MempoolTransactionExtended; }, newTransactions: MempoolTransactionExtended[], private $asyncMempoolChangedCallback: ((newMempool: {[txId: string]: MempoolTransactionExtended; }, newTransactions: MempoolTransactionExtended[],
deletedTransactions: MempoolTransactionExtended[]) => Promise<void>) | undefined; deletedTransactions: MempoolTransactionExtended[]) => Promise<void>) | undefined;
private accelerations: { [txId: string]: number } = {};
private txPerSecondArray: number[] = []; private txPerSecondArray: number[] = [];
private txPerSecond: number = 0; private txPerSecond: number = 0;
@ -232,6 +234,17 @@ class Mempool {
const newTransactionsStripped = newTransactions.map((tx) => Common.stripTransaction(tx)); const newTransactionsStripped = newTransactions.map((tx) => Common.stripTransaction(tx));
this.latestTransactions = newTransactionsStripped.concat(this.latestTransactions).slice(0, 6); this.latestTransactions = newTransactionsStripped.concat(this.latestTransactions).slice(0, 6);
const newAccelerations: { txid: string, delta: number }[] = [];
newTransactions.forEach(tx => {
if (tx.txid.startsWith('00')) {
const delta = Math.floor(Math.random() * 100000) + 100000;
newAccelerations.push({ txid: tx.txid, delta });
tx.acceleration = delta;
}
});
this.addAccelerations(newAccelerations);
this.removeAccelerations(deletedTransactions.map(tx => tx.txid));
if (!this.inSync && transactions.length === Object.keys(this.mempoolCache).length) { if (!this.inSync && transactions.length === Object.keys(this.mempoolCache).length) {
this.inSync = true; this.inSync = true;
logger.notice('The mempool is now in sync!'); logger.notice('The mempool is now in sync!');
@ -256,6 +269,22 @@ class Mempool {
this.clearTimer(timer); this.clearTimer(timer);
} }
public getAccelerations(): { [txid: string]: number } {
return this.accelerations;
}
public addAccelerations(newAccelerations: { txid: string, delta: number }[]): void {
for (const acceleration of newAccelerations) {
this.accelerations[acceleration.txid] = acceleration.delta;
}
}
public removeAccelerations(txids: string[]): void {
for (const txid of txids) {
delete this.accelerations[txid];
}
}
private startTimer() { private startTimer() {
const state: any = { const state: any = {
start: Date.now(), start: Date.now(),

View File

@ -21,6 +21,7 @@ import Audit from './audit';
import { deepClone } from '../utils/clone'; import { deepClone } from '../utils/clone';
import priceUpdater from '../tasks/price-updater'; import priceUpdater from '../tasks/price-updater';
import { ApiPrice } from '../repositories/PricesRepository'; import { ApiPrice } from '../repositories/PricesRepository';
import mempool from './mempool';
class WebsocketHandler { class WebsocketHandler {
private wss: WebSocket.Server | undefined; private wss: WebSocket.Server | undefined;
@ -559,7 +560,7 @@ class WebsocketHandler {
} }
if (Common.indexingEnabled() && memPool.isInSync()) { if (Common.indexingEnabled() && memPool.isInSync()) {
const { censored, added, fresh, sigop, accelerated, score, similarity } = Audit.auditBlock(transactions, projectedBlocks, auditMempool, accelerations); const { censored, added, fresh, sigop, accelerated, score, similarity } = Audit.auditBlock(transactions, projectedBlocks, auditMempool);
const matchRate = Math.round(score * 100 * 100) / 100; const matchRate = Math.round(score * 100 * 100) / 100;
const stripped = projectedBlocks[0]?.transactions ? projectedBlocks[0].transactions.map((tx) => { const stripped = projectedBlocks[0]?.transactions ? projectedBlocks[0].transactions.map((tx) => {
@ -624,6 +625,8 @@ class WebsocketHandler {
const da = difficultyAdjustment.getDifficultyAdjustment(); const da = difficultyAdjustment.getDifficultyAdjustment();
const fees = feeApi.getRecommendedFee(); const fees = feeApi.getRecommendedFee();
memPool.removeAccelerations(txIds);
// update init data // update init data
this.updateInitData(); this.updateInitData();

View File

@ -86,6 +86,7 @@ export interface TransactionExtended extends IEsploraApi.Transaction {
block: number, block: number,
vsize: number, vsize: number,
}; };
acceleration?: number;
uid?: number; uid?: number;
} }
@ -174,6 +175,7 @@ export interface TransactionStripped {
fee: number; fee: number;
vsize: number; vsize: number;
value: number; value: number;
acc?: number;
rate?: number; // effective fee rate rate?: number; // effective fee rate
} }

View File

@ -133,7 +133,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
} }
} }
update(add: TransactionStripped[], remove: string[], change: { txid: string, rate: number | undefined }[], direction: string = 'left', resetLayout: boolean = false): void { update(add: TransactionStripped[], remove: string[], change: { txid: string, rate: number | undefined, acc: number | undefined }[], direction: string = 'left', resetLayout: boolean = false): void {
if (this.scene) { if (this.scene) {
this.scene.update(add, remove, change, direction, resetLayout); this.scene.update(add, remove, change, direction, resetLayout);
this.start(); this.start();

View File

@ -156,7 +156,7 @@ export default class BlockScene {
this.updateAll(startTime, 200, direction); this.updateAll(startTime, 200, direction);
} }
update(add: TransactionStripped[], remove: string[], change: { txid: string, rate: number | undefined }[], direction: string = 'left', resetLayout: boolean = false): void { update(add: TransactionStripped[], remove: string[], change: { txid: string, rate: number | undefined, acc: number | undefined }[], direction: string = 'left', resetLayout: boolean = false): void {
const startTime = performance.now(); const startTime = performance.now();
const removed = this.removeBatch(remove, startTime, direction); const removed = this.removeBatch(remove, startTime, direction);
@ -181,6 +181,7 @@ export default class BlockScene {
// update effective rates // update effective rates
change.forEach(tx => { change.forEach(tx => {
if (this.txs[tx.txid]) { if (this.txs[tx.txid]) {
this.txs[tx.txid].acc = tx.acc;
this.txs[tx.txid].feerate = tx.rate || (this.txs[tx.txid].fee / this.txs[tx.txid].vsize); this.txs[tx.txid].feerate = tx.rate || (this.txs[tx.txid].fee / this.txs[tx.txid].vsize);
this.txs[tx.txid].rate = tx.rate; this.txs[tx.txid].rate = tx.rate;
this.txs[tx.txid].dirty = true; this.txs[tx.txid].dirty = true;

View File

@ -37,6 +37,7 @@ export default class TxView implements TransactionStripped {
vsize: number; vsize: number;
value: number; value: number;
feerate: number; feerate: number;
acc?: number;
rate?: number; rate?: number;
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected' | 'accelerated'; status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected' | 'accelerated';
context?: 'projected' | 'actual'; context?: 'projected' | 'actual';
@ -61,6 +62,7 @@ export default class TxView implements TransactionStripped {
this.vsize = tx.vsize; this.vsize = tx.vsize;
this.value = tx.value; this.value = tx.value;
this.feerate = tx.rate || (tx.fee / tx.vsize); // sort by effective fee rate where available this.feerate = tx.rate || (tx.fee / tx.vsize); // sort by effective fee rate where available
this.acc = tx.acc;
this.rate = tx.rate; this.rate = tx.rate;
this.status = tx.status; this.status = tx.status;
this.initialised = false; this.initialised = false;
@ -165,6 +167,11 @@ export default class TxView implements TransactionStripped {
const feeLevelColor = feeColors[feeLevelIndex] || feeColors[mempoolFeeColors.length - 1]; const feeLevelColor = feeColors[feeLevelIndex] || feeColors[mempoolFeeColors.length - 1];
// Normal mode // Normal mode
if (!this.scene?.highlightingEnabled) { if (!this.scene?.highlightingEnabled) {
if (this.acc) {
return auditColors.accelerated;
} else {
return feeLevelColor;
}
return feeLevelColor; return feeLevelColor;
} }
// Block audit // Block audit
@ -189,7 +196,11 @@ export default class TxView implements TransactionStripped {
return feeLevelColor; return feeLevelColor;
} }
default: default:
return feeLevelColor; if (this.acc) {
return auditColors.accelerated;
} else {
return feeLevelColor;
}
} }
} }
} }

View File

@ -29,7 +29,8 @@
</td> </td>
</tr> </tr>
<tr *ngIf="effectiveRate && effectiveRate !== feeRate"> <tr *ngIf="effectiveRate && effectiveRate !== feeRate">
<td class="td-width" i18n="transaction.effective-fee-rate|Effective transaction fee rate">Effective fee rate</td> <td *ngIf="!this.acceleration" class="td-width" i18n="transaction.effective-fee-rate|Effective transaction fee rate">Effective fee rate</td>
<td *ngIf="this.acceleration" class="td-width" i18n="transaction.effective-fee-rate|Effective transaction fee rate">Accelerated fee rate</td>
<td> <td>
{{ effectiveRate | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> {{ effectiveRate | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
</td> </td>

View File

@ -21,6 +21,7 @@ export class BlockOverviewTooltipComponent implements OnChanges {
vsize = 1; vsize = 1;
feeRate = 0; feeRate = 0;
effectiveRate; effectiveRate;
acceleration;
tooltipPosition: Position = { x: 0, y: 0 }; tooltipPosition: Position = { x: 0, y: 0 };
@ -53,6 +54,7 @@ export class BlockOverviewTooltipComponent implements OnChanges {
this.vsize = tx.vsize || 1; this.vsize = tx.vsize || 1;
this.feeRate = this.fee / this.vsize; this.feeRate = this.fee / this.vsize;
this.effectiveRate = tx.rate; this.effectiveRate = tx.rate;
this.acceleration = tx.acc;
} }
} }
} }

View File

@ -95,7 +95,6 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
updateBlock(delta: MempoolBlockDelta): void { updateBlock(delta: MempoolBlockDelta): void {
const blockMined = (this.stateService.latestBlockHeight > this.lastBlockHeight); const blockMined = (this.stateService.latestBlockHeight > this.lastBlockHeight);
if (this.blockIndex !== this.index) { if (this.blockIndex !== this.index) {
const direction = (this.blockIndex == null || this.index < this.blockIndex) ? this.poolDirection : this.chainDirection; const direction = (this.blockIndex == null || this.index < this.blockIndex) ? this.poolDirection : this.chainDirection;
this.blockGraph.replace(delta.added, direction); this.blockGraph.replace(delta.added, direction);

View File

@ -478,7 +478,8 @@
</td> </td>
</tr> </tr>
<tr *ngIf="cpfpInfo && hasEffectiveFeeRate"> <tr *ngIf="cpfpInfo && hasEffectiveFeeRate">
<td i18n="transaction.effective-fee-rate|Effective transaction fee rate">Effective fee rate</td> <td *ngIf="cpfpInfo.acceleration" i18n="transaction.effective-fee-rate|Effective transaction fee rate">Accelerated fee rate</td>
<td *ngIf="!cpfpInfo.acceleration" i18n="transaction.effective-fee-rate|Effective transaction fee rate">Effective fee rate</td>
<td> <td>
<div class="effective-fee-container"> <div class="effective-fee-container">
{{ tx.effectiveFeePerVsize | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> {{ tx.effectiveFeePerVsize | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>

View File

@ -178,6 +178,9 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
} else { } else {
this.tx.effectiveFeePerVsize = cpfpInfo.effectiveFeePerVsize; this.tx.effectiveFeePerVsize = cpfpInfo.effectiveFeePerVsize;
} }
if (cpfpInfo.acceleration) {
this.tx.acceleration = cpfpInfo.acceleration;
}
this.cpfpInfo = cpfpInfo; this.cpfpInfo = cpfpInfo;
this.hasEffectiveFeeRate = hasRelatives || (this.tx.effectiveFeePerVsize && (Math.abs(this.tx.effectiveFeePerVsize - this.tx.feePerVsize) > 0.01)); this.hasEffectiveFeeRate = hasRelatives || (this.tx.effectiveFeePerVsize && (Math.abs(this.tx.effectiveFeePerVsize - this.tx.feePerVsize) > 0.01));

View File

@ -19,6 +19,7 @@ export interface Transaction {
ancestors?: Ancestor[]; ancestors?: Ancestor[];
bestDescendant?: BestDescendant | null; bestDescendant?: BestDescendant | null;
cpfpChecked?: boolean; cpfpChecked?: boolean;
acceleration?: number;
deleteAfter?: number; deleteAfter?: number;
_unblinded?: any; _unblinded?: any;
_deduced?: boolean; _deduced?: boolean;

View File

@ -27,6 +27,7 @@ export interface CpfpInfo {
effectiveFeePerVsize?: number; effectiveFeePerVsize?: number;
sigops?: number; sigops?: number;
adjustedVsize?: number; adjustedVsize?: number;
acceleration?: number;
} }
export interface RbfInfo { export interface RbfInfo {

View File

@ -57,7 +57,7 @@ export interface MempoolBlockWithTransactions extends MempoolBlock {
export interface MempoolBlockDelta { export interface MempoolBlockDelta {
added: TransactionStripped[], added: TransactionStripped[],
removed: string[], removed: string[],
changed?: { txid: string, rate: number | undefined }[]; changed?: { txid: string, rate: number | undefined, acc: number | undefined }[];
} }
export interface MempoolInfo { export interface MempoolInfo {
@ -75,6 +75,7 @@ export interface TransactionStripped {
fee: number; fee: number;
vsize: number; vsize: number;
value: number; value: number;
acc?: number; // acceleration delta
rate?: number; // effective fee rate rate?: number; // effective fee rate
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected' | 'accelerated'; status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected' | 'accelerated';
context?: 'projected' | 'actual'; context?: 'projected' | 'actual';