Compare commits
6 Commits
master
...
mononaut/a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5682b157c1 | ||
|
|
77b0a8eccc | ||
|
|
6af6274f43 | ||
|
|
379fa3fe49 | ||
|
|
fe76a0fd08 | ||
|
|
412d83f961 |
@ -122,5 +122,9 @@
|
|||||||
"LIQUID_ONION": "http://liquidmom47f6s3m53ebfxn47p76a6tlnxib3wp6deux7wuzotdr6cyd.onion/api/v1",
|
"LIQUID_ONION": "http://liquidmom47f6s3m53ebfxn47p76a6tlnxib3wp6deux7wuzotdr6cyd.onion/api/v1",
|
||||||
"BISQ_URL": "https://bisq.markets/api",
|
"BISQ_URL": "https://bisq.markets/api",
|
||||||
"BISQ_ONION": "http://bisqmktse2cabavbr2xjq7xw3h6g5ottemo5rolfcwt6aly6tp5fdryd.onion/api"
|
"BISQ_ONION": "http://bisqmktse2cabavbr2xjq7xw3h6g5ottemo5rolfcwt6aly6tp5fdryd.onion/api"
|
||||||
|
},
|
||||||
|
"MEMPOOL_SERVICES": {
|
||||||
|
"API": "https://mempool.space/api",
|
||||||
|
"ACCELERATIONS": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -118,5 +118,9 @@
|
|||||||
},
|
},
|
||||||
"CLIGHTNING": {
|
"CLIGHTNING": {
|
||||||
"SOCKET": "__CLIGHTNING_SOCKET__"
|
"SOCKET": "__CLIGHTNING_SOCKET__"
|
||||||
|
},
|
||||||
|
"MEMPOOl_SERVICES": {
|
||||||
|
"API": "__MEMPOOL_SERVICES_API__",
|
||||||
|
"ACCELERATIONS": "__MEMPOOL_SERVICES_ACCELERATIONS__"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,6 +117,11 @@ describe('Mempool Backend Config', () => {
|
|||||||
GEOLITE2_ASN: '/usr/local/share/GeoIP/GeoLite2-ASN.mmdb',
|
GEOLITE2_ASN: '/usr/local/share/GeoIP/GeoLite2-ASN.mmdb',
|
||||||
GEOIP2_ISP: '/usr/local/share/GeoIP/GeoIP2-ISP.mmdb'
|
GEOIP2_ISP: '/usr/local/share/GeoIP/GeoIP2-ISP.mmdb'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
expect(config.MEMPOOL_SERVICES).toStrictEqual({
|
||||||
|
API: "",
|
||||||
|
ACCELERATIONS: false,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -150,6 +155,8 @@ describe('Mempool Backend Config', () => {
|
|||||||
expect(config.PRICE_DATA_SERVER).toStrictEqual(fixture.PRICE_DATA_SERVER);
|
expect(config.PRICE_DATA_SERVER).toStrictEqual(fixture.PRICE_DATA_SERVER);
|
||||||
|
|
||||||
expect(config.EXTERNAL_DATA_SERVER).toStrictEqual(fixture.EXTERNAL_DATA_SERVER);
|
expect(config.EXTERNAL_DATA_SERVER).toStrictEqual(fixture.EXTERNAL_DATA_SERVER);
|
||||||
|
|
||||||
|
expect(config.MEMPOOL_SERVICES).toStrictEqual(fixture.MEMPOOL_SERVICES);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -5,15 +5,16 @@ 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 })
|
auditBlock(transactions: TransactionExtended[], projectedBlocks: MempoolBlockWithTransactions[], mempool: { [txId: string]: TransactionExtended }, useAccelerations: boolean = false)
|
||||||
: { censored: string[], added: string[], fresh: string[], sigop: 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: [], score: 0, similarity: 1 };
|
return { censored: [], added: [], fresh: [], sigop: [], accelerated: [], score: 0, similarity: 1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const matches: string[] = []; // present in both mined block and template
|
const matches: string[] = []; // present in both mined block and template
|
||||||
const added: string[] = []; // present in mined block, not in template
|
const added: string[] = []; // present in mined block, not in template
|
||||||
const fresh: string[] = []; // missing, but firstSeen within PROPAGATION_MARGIN
|
const fresh: string[] = []; // missing, but firstSeen within PROPAGATION_MARGIN
|
||||||
|
const accelerated: string[] = []; // prioritized by the mempool accelerator
|
||||||
const isCensored = {}; // missing, without excuse
|
const isCensored = {}; // missing, without excuse
|
||||||
const isDisplaced = {};
|
const isDisplaced = {};
|
||||||
let displacedWeight = 0;
|
let displacedWeight = 0;
|
||||||
@ -26,6 +27,9 @@ 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 (mempool[tx.txid] && mempool[tx.txid].acceleration) {
|
||||||
|
accelerated.push(tx.txid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// coinbase is always expected
|
// coinbase is always expected
|
||||||
if (transactions[0]) {
|
if (transactions[0]) {
|
||||||
@ -138,6 +142,7 @@ class Audit {
|
|||||||
added,
|
added,
|
||||||
fresh,
|
fresh,
|
||||||
sigop: [],
|
sigop: [],
|
||||||
|
accelerated,
|
||||||
score,
|
score,
|
||||||
similarity,
|
similarity,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
|
|||||||
import { RowDataPacket } from 'mysql2';
|
import { RowDataPacket } from 'mysql2';
|
||||||
|
|
||||||
class DatabaseMigration {
|
class DatabaseMigration {
|
||||||
private static currentVersion = 61;
|
private static currentVersion = 62;
|
||||||
private queryTimeout = 3600_000;
|
private queryTimeout = 3600_000;
|
||||||
private statisticsAddedIndexed = false;
|
private statisticsAddedIndexed = false;
|
||||||
private uniqueLogs: string[] = [];
|
private uniqueLogs: string[] = [];
|
||||||
@ -533,6 +533,10 @@ class DatabaseMigration {
|
|||||||
await this.updateToSchemaVersion(61);
|
await this.updateToSchemaVersion(61);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (databaseSchemaVersion < 62 && isBitcoin === true) {
|
||||||
|
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD accelerated_txs JSON DEFAULT "[]"');
|
||||||
|
await this.updateToSchemaVersion(61);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { Common, OnlineFeeStatsCalculator } from './common';
|
|||||||
import config from '../config';
|
import config from '../config';
|
||||||
import { Worker } from 'worker_threads';
|
import { Worker } from 'worker_threads';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import mempool from './mempool';
|
||||||
|
|
||||||
class MempoolBlocks {
|
class MempoolBlocks {
|
||||||
private mempoolBlocks: MempoolBlockWithTransactions[] = [];
|
private mempoolBlocks: MempoolBlockWithTransactions[] = [];
|
||||||
@ -169,7 +170,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 +192,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,15 +206,17 @@ class MempoolBlocks {
|
|||||||
return mempoolBlockDeltas;
|
return mempoolBlockDeltas;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async $makeBlockTemplates(newMempool: { [txid: string]: MempoolTransactionExtended }, saveResults: boolean = false): Promise<MempoolBlockWithTransactions[]> {
|
public async $makeBlockTemplates(newMempool: { [txid: string]: MempoolTransactionExtended }, saveResults: boolean = false, useAccelerations: boolean = false): Promise<MempoolBlockWithTransactions[]> {
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
|
|
||||||
// reset mempool short ids
|
// reset mempool short ids
|
||||||
this.resetUids();
|
this.resetUids();
|
||||||
for (const tx of Object.values(newMempool)) {
|
for (const tx of Object.values(newMempool)) {
|
||||||
this.setUid(tx);
|
this.setUid(tx, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const accelerations = useAccelerations ? mempool.getAccelerations() : {};
|
||||||
|
|
||||||
// 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 strippedMempool: Map<number, CompactThreadTransaction> = new Map();
|
const strippedMempool: Map<number, CompactThreadTransaction> = new Map();
|
||||||
@ -221,7 +224,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 + (useAccelerations ? (accelerations[entry.txid] || 0) : 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,
|
||||||
@ -260,7 +263,7 @@ class MempoolBlocks {
|
|||||||
// clean up thread error listener
|
// clean up thread error listener
|
||||||
this.txSelectionWorker?.removeListener('error', threadErrorListener);
|
this.txSelectionWorker?.removeListener('error', threadErrorListener);
|
||||||
|
|
||||||
const processed = this.processBlockTemplates(newMempool, blocks, rates, clusters, saveResults);
|
const processed = this.processBlockTemplates(newMempool, blocks, rates, clusters, accelerations, saveResults);
|
||||||
logger.debug(`makeBlockTemplates completed in ${(Date.now() - start)/1000} seconds`);
|
logger.debug(`makeBlockTemplates completed in ${(Date.now() - start)/1000} seconds`);
|
||||||
return processed;
|
return processed;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -269,25 +272,29 @@ class MempoolBlocks {
|
|||||||
return this.mempoolBlocks;
|
return this.mempoolBlocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async $updateBlockTemplates(newMempool: { [txid: string]: MempoolTransactionExtended }, added: MempoolTransactionExtended[], removed: MempoolTransactionExtended[], saveResults: boolean = false): Promise<void> {
|
public async $updateBlockTemplates(newMempool: { [txid: string]: MempoolTransactionExtended }, added: MempoolTransactionExtended[], removed: MempoolTransactionExtended[], accelerationDelta: string[] = [], saveResults: boolean = false, useAccelerations: boolean = false): Promise<void> {
|
||||||
if (!this.txSelectionWorker) {
|
if (!this.txSelectionWorker) {
|
||||||
// need to reset the worker
|
// need to reset the worker
|
||||||
await this.$makeBlockTemplates(newMempool, saveResults);
|
await this.$makeBlockTemplates(newMempool, saveResults, useAccelerations);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
|
|
||||||
for (const tx of Object.values(added)) {
|
const accelerations = useAccelerations ? mempool.getAccelerations() : {};
|
||||||
|
const addedAndChanged: MempoolTransactionExtended[] = useAccelerations ? accelerationDelta.map(txid => newMempool[txid]).filter(tx => tx != null).concat(added) : added;
|
||||||
|
|
||||||
|
for (const tx of addedAndChanged) {
|
||||||
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[] = addedAndChanged.filter(entry => entry.uid != null).map(entry => {
|
||||||
return {
|
return {
|
||||||
uid: entry.uid || 0,
|
uid: entry.uid || 0,
|
||||||
fee: entry.fee,
|
fee: entry.fee + (useAccelerations ? (accelerations[entry.txid] || 0) : 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,
|
||||||
@ -314,14 +321,14 @@ class MempoolBlocks {
|
|||||||
// clean up thread error listener
|
// clean up thread error listener
|
||||||
this.txSelectionWorker?.removeListener('error', threadErrorListener);
|
this.txSelectionWorker?.removeListener('error', threadErrorListener);
|
||||||
|
|
||||||
this.processBlockTemplates(newMempool, blocks, rates, clusters, saveResults);
|
this.processBlockTemplates(newMempool, blocks, rates, clusters, accelerations, saveResults);
|
||||||
logger.debug(`updateBlockTemplates completed in ${(Date.now() - start) / 1000} seconds`);
|
logger.debug(`updateBlockTemplates completed in ${(Date.now() - start) / 1000} seconds`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.err('updateBlockTemplates failed. ' + (e instanceof Error ? e.message : e));
|
logger.err('updateBlockTemplates failed. ' + (e instanceof Error ? e.message : e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private processBlockTemplates(mempool, blocks: string[][], rates: { [root: string]: number }, clusters: { [root: string]: string[] }, saveResults): MempoolBlockWithTransactions[] {
|
private processBlockTemplates(mempool, blocks: string[][], rates: { [root: string]: number }, clusters: { [root: string]: string[] }, accelerations, saveResults): MempoolBlockWithTransactions[] {
|
||||||
for (const txid of Object.keys(rates)) {
|
for (const txid of Object.keys(rates)) {
|
||||||
if (txid in mempool) {
|
if (txid in mempool) {
|
||||||
mempool[txid].effectiveFeePerVsize = rates[txid];
|
mempool[txid].effectiveFeePerVsize = rates[txid];
|
||||||
@ -360,6 +367,8 @@ class MempoolBlocks {
|
|||||||
};
|
};
|
||||||
mempoolTx.cpfpChecked = true;
|
mempoolTx.cpfpChecked = true;
|
||||||
|
|
||||||
|
mempoolTx.acceleration = accelerations[txid];
|
||||||
|
|
||||||
// online calculation of stack-of-blocks fee stats
|
// online calculation of stack-of-blocks fee stats
|
||||||
if (hasBlockStack && blockIndex === blocks.length - 1 && feeStatsCalculator) {
|
if (hasBlockStack && blockIndex === blocks.length - 1 && feeStatsCalculator) {
|
||||||
feeStatsCalculator.processNext(mempoolTx);
|
feeStatsCalculator.processNext(mempoolTx);
|
||||||
@ -449,12 +458,18 @@ class MempoolBlocks {
|
|||||||
this.nextUid = 1;
|
this.nextUid = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private setUid(tx: MempoolTransactionExtended): number {
|
// use reset=true to overwrite existing uids held by tx objects (required after resetUids)
|
||||||
const uid = this.nextUid;
|
private setUid(tx: MempoolTransactionExtended, reset = false): number {
|
||||||
this.nextUid++;
|
let uid = reset ? null : this.getUid(tx);
|
||||||
this.uidMap.set(uid, tx.txid);
|
if (uid == null) {
|
||||||
tx.uid = uid;
|
uid = this.nextUid;
|
||||||
return uid;
|
this.nextUid++;
|
||||||
|
this.uidMap.set(uid, tx.txid);
|
||||||
|
tx.uid = uid;
|
||||||
|
return uid;
|
||||||
|
} else {
|
||||||
|
return uid;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getUid(tx: MempoolTransactionExtended): number | void {
|
private getUid(tx: MempoolTransactionExtended): number | void {
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import loadingIndicators from './loading-indicators';
|
|||||||
import bitcoinClient from './bitcoin/bitcoin-client';
|
import bitcoinClient from './bitcoin/bitcoin-client';
|
||||||
import bitcoinSecondClient from './bitcoin/bitcoin-second-client';
|
import bitcoinSecondClient from './bitcoin/bitcoin-second-client';
|
||||||
import rbfCache from './rbf-cache';
|
import rbfCache from './rbf-cache';
|
||||||
|
import accelerationApi, { Acceleration } from './services/acceleration';
|
||||||
|
|
||||||
class Mempool {
|
class Mempool {
|
||||||
private inSync: boolean = false;
|
private inSync: boolean = false;
|
||||||
@ -18,9 +19,11 @@ class Mempool {
|
|||||||
private mempoolInfo: IBitcoinApi.MempoolInfo = { loaded: false, size: 0, bytes: 0, usage: 0, total_fee: 0,
|
private mempoolInfo: IBitcoinApi.MempoolInfo = { loaded: false, size: 0, bytes: 0, usage: 0, total_fee: 0,
|
||||||
maxmempool: 300000000, mempoolminfee: 0.00001000, minrelaytxfee: 0.00001000 };
|
maxmempool: 300000000, mempoolminfee: 0.00001000, minrelaytxfee: 0.00001000 };
|
||||||
private mempoolChangedCallback: ((newMempool: {[txId: string]: MempoolTransactionExtended; }, newTransactions: MempoolTransactionExtended[],
|
private mempoolChangedCallback: ((newMempool: {[txId: string]: MempoolTransactionExtended; }, newTransactions: MempoolTransactionExtended[],
|
||||||
deletedTransactions: MempoolTransactionExtended[]) => void) | undefined;
|
deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[]) => void) | undefined;
|
||||||
private $asyncMempoolChangedCallback: ((newMempool: {[txId: string]: MempoolTransactionExtended; }, newTransactions: MempoolTransactionExtended[],
|
private $asyncMempoolChangedCallback: ((newMempool: {[txId: string]: MempoolTransactionExtended; }, newTransactions: MempoolTransactionExtended[],
|
||||||
deletedTransactions: MempoolTransactionExtended[]) => Promise<void>) | undefined;
|
deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[]) => Promise<void>) | undefined;
|
||||||
|
|
||||||
|
private accelerations: { [txId: string]: number } = {};
|
||||||
|
|
||||||
private txPerSecondArray: number[] = [];
|
private txPerSecondArray: number[] = [];
|
||||||
private txPerSecond: number = 0;
|
private txPerSecond: number = 0;
|
||||||
@ -65,12 +68,12 @@ class Mempool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public setMempoolChangedCallback(fn: (newMempool: { [txId: string]: MempoolTransactionExtended; },
|
public setMempoolChangedCallback(fn: (newMempool: { [txId: string]: MempoolTransactionExtended; },
|
||||||
newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[]) => void): void {
|
newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[]) => void): void {
|
||||||
this.mempoolChangedCallback = fn;
|
this.mempoolChangedCallback = fn;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setAsyncMempoolChangedCallback(fn: (newMempool: { [txId: string]: MempoolTransactionExtended; },
|
public setAsyncMempoolChangedCallback(fn: (newMempool: { [txId: string]: MempoolTransactionExtended; },
|
||||||
newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[]) => Promise<void>): void {
|
newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[]) => Promise<void>): void {
|
||||||
this.$asyncMempoolChangedCallback = fn;
|
this.$asyncMempoolChangedCallback = fn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,10 +93,10 @@ class Mempool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.mempoolChangedCallback) {
|
if (this.mempoolChangedCallback) {
|
||||||
this.mempoolChangedCallback(this.mempoolCache, [], []);
|
this.mempoolChangedCallback(this.mempoolCache, [], [], []);
|
||||||
}
|
}
|
||||||
if (this.$asyncMempoolChangedCallback) {
|
if (this.$asyncMempoolChangedCallback) {
|
||||||
await this.$asyncMempoolChangedCallback(this.mempoolCache, [], []);
|
await this.$asyncMempoolChangedCallback(this.mempoolCache, [], [], []);
|
||||||
}
|
}
|
||||||
this.addToSpendMap(Object.values(this.mempoolCache));
|
this.addToSpendMap(Object.values(this.mempoolCache));
|
||||||
}
|
}
|
||||||
@ -229,6 +232,11 @@ class Mempool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const accelerationDelta = await this.$updateAccelerations();
|
||||||
|
if (accelerationDelta.length) {
|
||||||
|
hasChange = true;
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
@ -241,11 +249,11 @@ class Mempool {
|
|||||||
this.mempoolCacheDelta = Math.abs(transactions.length - Object.keys(this.mempoolCache).length);
|
this.mempoolCacheDelta = Math.abs(transactions.length - Object.keys(this.mempoolCache).length);
|
||||||
|
|
||||||
if (this.mempoolChangedCallback && (hasChange || deletedTransactions.length)) {
|
if (this.mempoolChangedCallback && (hasChange || deletedTransactions.length)) {
|
||||||
this.mempoolChangedCallback(this.mempoolCache, newTransactions, deletedTransactions);
|
this.mempoolChangedCallback(this.mempoolCache, newTransactions, deletedTransactions, accelerationDelta);
|
||||||
}
|
}
|
||||||
if (this.$asyncMempoolChangedCallback && (hasChange || deletedTransactions.length)) {
|
if (this.$asyncMempoolChangedCallback && (hasChange || deletedTransactions.length)) {
|
||||||
this.updateTimerProgress(timer, 'running async mempool callback');
|
this.updateTimerProgress(timer, 'running async mempool callback');
|
||||||
await this.$asyncMempoolChangedCallback(this.mempoolCache, newTransactions, deletedTransactions);
|
await this.$asyncMempoolChangedCallback(this.mempoolCache, newTransactions, deletedTransactions, accelerationDelta);
|
||||||
this.updateTimerProgress(timer, 'completed async mempool callback');
|
this.updateTimerProgress(timer, 'completed async mempool callback');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,6 +264,48 @@ class Mempool {
|
|||||||
this.clearTimer(timer);
|
this.clearTimer(timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getAccelerations(): { [txid: string]: number } {
|
||||||
|
return this.accelerations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async $updateAccelerations(): Promise<string[]> {
|
||||||
|
if (!config.MEMPOOL_SERVICES.ACCELERATIONS) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const newAccelerations = await accelerationApi.$fetchAccelerations();
|
||||||
|
|
||||||
|
const changed: string[] = [];
|
||||||
|
|
||||||
|
const newAccelerationMap: { [txid: string]: number } = {};
|
||||||
|
for (const acceleration of newAccelerations) {
|
||||||
|
newAccelerationMap[acceleration.txid] = acceleration.feeDelta;
|
||||||
|
if (this.accelerations[acceleration.txid] == null) {
|
||||||
|
// new acceleration
|
||||||
|
changed.push(acceleration.txid);
|
||||||
|
} else if (this.accelerations[acceleration.txid] !== acceleration.feeDelta) {
|
||||||
|
// feeDelta changed
|
||||||
|
changed.push(acceleration.txid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const oldTxid of Object.keys(this.accelerations)) {
|
||||||
|
if (!newAccelerationMap[oldTxid]) {
|
||||||
|
// removed
|
||||||
|
changed.push(oldTxid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.accelerations = newAccelerationMap;
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.debug(`Failed to update accelerations: ` + (e instanceof Error ? e.message : e));
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private startTimer() {
|
private startTimer() {
|
||||||
const state: any = {
|
const state: any = {
|
||||||
start: Date.now(),
|
start: Date.now(),
|
||||||
|
|||||||
38
backend/src/api/services/acceleration.ts
Normal file
38
backend/src/api/services/acceleration.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { query } from '../../utils/axios-query';
|
||||||
|
import config from '../../config';
|
||||||
|
import { BlockExtended, PoolTag } from '../../mempool.interfaces';
|
||||||
|
|
||||||
|
export interface Acceleration {
|
||||||
|
txid: string,
|
||||||
|
feeDelta: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccelerationApi {
|
||||||
|
public async $fetchAccelerations(): Promise<Acceleration[]> {
|
||||||
|
if (config.MEMPOOL_SERVICES.ACCELERATIONS) {
|
||||||
|
const response = await query(`${config.MEMPOOL_SERVICES.API}/accelerations`);
|
||||||
|
return (response as Acceleration[]) || [];
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async $fetchPools(): Promise<PoolTag[]> {
|
||||||
|
if (config.MEMPOOL_SERVICES.ACCELERATIONS) {
|
||||||
|
const response = await query(`${config.MEMPOOL_SERVICES.API}/partners`);
|
||||||
|
return (response as PoolTag[]) || [];
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async $isAcceleratedBlock(block: BlockExtended): Promise<boolean> {
|
||||||
|
const pools = await this.$fetchPools();
|
||||||
|
if (block?.extras?.pool?.id == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return pools.reduce((match, tag) => match || tag.uniqueId === block.extras.pool.id, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new AccelerationApi();
|
||||||
@ -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 accelerationApi from './services/acceleration';
|
||||||
|
|
||||||
class WebsocketHandler {
|
class WebsocketHandler {
|
||||||
private wss: WebSocket.Server | undefined;
|
private wss: WebSocket.Server | undefined;
|
||||||
@ -144,6 +145,7 @@ class WebsocketHandler {
|
|||||||
response['txPosition'] = {
|
response['txPosition'] = {
|
||||||
txid: trackTxid,
|
txid: trackTxid,
|
||||||
position: tx.position,
|
position: tx.position,
|
||||||
|
accelerated: tx.acceleration || undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -302,7 +304,7 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async $handleMempoolChange(newMempool: { [txid: string]: MempoolTransactionExtended },
|
async $handleMempoolChange(newMempool: { [txid: string]: MempoolTransactionExtended },
|
||||||
newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[]): Promise<void> {
|
newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[]): Promise<void> {
|
||||||
if (!this.wss) {
|
if (!this.wss) {
|
||||||
throw new Error('WebSocket.Server is not set');
|
throw new Error('WebSocket.Server is not set');
|
||||||
}
|
}
|
||||||
@ -310,7 +312,7 @@ class WebsocketHandler {
|
|||||||
this.printLogs();
|
this.printLogs();
|
||||||
|
|
||||||
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
|
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
|
||||||
await mempoolBlocks.$updateBlockTemplates(newMempool, newTransactions, deletedTransactions, true);
|
await mempoolBlocks.$updateBlockTemplates(newMempool, newTransactions, deletedTransactions, accelerationDelta, true, config.MEMPOOL_SERVICES.ACCELERATIONS);
|
||||||
} else {
|
} else {
|
||||||
mempoolBlocks.updateMempoolBlocks(newMempool, true);
|
mempoolBlocks.updateMempoolBlocks(newMempool, true);
|
||||||
}
|
}
|
||||||
@ -544,22 +546,27 @@ class WebsocketHandler {
|
|||||||
if (config.MEMPOOL.AUDIT) {
|
if (config.MEMPOOL.AUDIT) {
|
||||||
let projectedBlocks;
|
let projectedBlocks;
|
||||||
let auditMempool = _memPool;
|
let auditMempool = _memPool;
|
||||||
|
const isAccelerated = config.MEMPOOL_SERVICES.ACCELERATIONS && await accelerationApi.$isAcceleratedBlock(block);
|
||||||
// template calculation functions have mempool side effects, so calculate audits using
|
// 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
|
// a cloned copy of the mempool if we're running a different algorithm for mempool updates
|
||||||
const separateAudit = config.MEMPOOL.ADVANCED_GBT_AUDIT !== config.MEMPOOL.ADVANCED_GBT_MEMPOOL;
|
const separateAudit = config.MEMPOOL.ADVANCED_GBT_AUDIT !== config.MEMPOOL.ADVANCED_GBT_MEMPOOL;
|
||||||
if (separateAudit) {
|
if (separateAudit) {
|
||||||
auditMempool = deepClone(_memPool);
|
auditMempool = deepClone(_memPool);
|
||||||
if (config.MEMPOOL.ADVANCED_GBT_AUDIT) {
|
if (config.MEMPOOL.ADVANCED_GBT_AUDIT) {
|
||||||
projectedBlocks = await mempoolBlocks.$makeBlockTemplates(auditMempool, false);
|
projectedBlocks = await mempoolBlocks.$makeBlockTemplates(auditMempool, false, isAccelerated);
|
||||||
} else {
|
} else {
|
||||||
projectedBlocks = mempoolBlocks.updateMempoolBlocks(auditMempool, false);
|
projectedBlocks = mempoolBlocks.updateMempoolBlocks(auditMempool, false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
projectedBlocks = mempoolBlocks.getMempoolBlocksWithTransactions();
|
if ((config.MEMPOOL_SERVICES.ACCELERATIONS && !isAccelerated)) {
|
||||||
|
projectedBlocks = await mempoolBlocks.$makeBlockTemplates(auditMempool, false, isAccelerated);
|
||||||
|
} else {
|
||||||
|
projectedBlocks = mempoolBlocks.getMempoolBlocksWithTransactions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Common.indexingEnabled() && memPool.isInSync()) {
|
if (Common.indexingEnabled() && memPool.isInSync()) {
|
||||||
const { censored, added, fresh, sigop, score, similarity } = Audit.auditBlock(transactions, projectedBlocks, auditMempool);
|
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) => {
|
||||||
@ -587,6 +594,7 @@ class WebsocketHandler {
|
|||||||
missingTxs: censored,
|
missingTxs: censored,
|
||||||
freshTxs: fresh,
|
freshTxs: fresh,
|
||||||
sigopTxs: sigop,
|
sigopTxs: sigop,
|
||||||
|
acceleratedTxs: accelerated,
|
||||||
matchRate: matchRate,
|
matchRate: matchRate,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -613,7 +621,7 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
|
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
|
||||||
await mempoolBlocks.$makeBlockTemplates(_memPool, true);
|
await mempoolBlocks.$makeBlockTemplates(_memPool, true, config.MEMPOOL_SERVICES.ACCELERATIONS);
|
||||||
} else {
|
} else {
|
||||||
mempoolBlocks.updateMempoolBlocks(_memPool, true);
|
mempoolBlocks.updateMempoolBlocks(_memPool, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -129,6 +129,10 @@ interface IConfig {
|
|||||||
GEOLITE2_ASN: string;
|
GEOLITE2_ASN: string;
|
||||||
GEOIP2_ISP: string;
|
GEOIP2_ISP: string;
|
||||||
},
|
},
|
||||||
|
MEMPOOL_SERVICES: {
|
||||||
|
API: string;
|
||||||
|
ACCELERATIONS: boolean;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaults: IConfig = {
|
const defaults: IConfig = {
|
||||||
@ -258,6 +262,10 @@ const defaults: IConfig = {
|
|||||||
'GEOLITE2_ASN': '/usr/local/share/GeoIP/GeoLite2-ASN.mmdb',
|
'GEOLITE2_ASN': '/usr/local/share/GeoIP/GeoLite2-ASN.mmdb',
|
||||||
'GEOIP2_ISP': '/usr/local/share/GeoIP/GeoIP2-ISP.mmdb'
|
'GEOIP2_ISP': '/usr/local/share/GeoIP/GeoIP2-ISP.mmdb'
|
||||||
},
|
},
|
||||||
|
'MEMPOOL_SERVICES': {
|
||||||
|
'API': '',
|
||||||
|
'ACCELERATIONS': false,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class Config implements IConfig {
|
class Config implements IConfig {
|
||||||
@ -277,6 +285,7 @@ class Config implements IConfig {
|
|||||||
PRICE_DATA_SERVER: IConfig['PRICE_DATA_SERVER'];
|
PRICE_DATA_SERVER: IConfig['PRICE_DATA_SERVER'];
|
||||||
EXTERNAL_DATA_SERVER: IConfig['EXTERNAL_DATA_SERVER'];
|
EXTERNAL_DATA_SERVER: IConfig['EXTERNAL_DATA_SERVER'];
|
||||||
MAXMIND: IConfig['MAXMIND'];
|
MAXMIND: IConfig['MAXMIND'];
|
||||||
|
MEMPOOL_SERVICES: IConfig['MEMPOOL_SERVICES'];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const configs = this.merge(configFromFile, defaults);
|
const configs = this.merge(configFromFile, defaults);
|
||||||
@ -296,6 +305,7 @@ class Config implements IConfig {
|
|||||||
this.PRICE_DATA_SERVER = configs.PRICE_DATA_SERVER;
|
this.PRICE_DATA_SERVER = configs.PRICE_DATA_SERVER;
|
||||||
this.EXTERNAL_DATA_SERVER = configs.EXTERNAL_DATA_SERVER;
|
this.EXTERNAL_DATA_SERVER = configs.EXTERNAL_DATA_SERVER;
|
||||||
this.MAXMIND = configs.MAXMIND;
|
this.MAXMIND = configs.MAXMIND;
|
||||||
|
this.MEMPOOL_SERVICES = configs.MEMPOOL_SERVICES;
|
||||||
}
|
}
|
||||||
|
|
||||||
merge = (...objects: object[]): IConfig => {
|
merge = (...objects: object[]): IConfig => {
|
||||||
|
|||||||
@ -34,6 +34,7 @@ export interface BlockAudit {
|
|||||||
freshTxs: string[],
|
freshTxs: string[],
|
||||||
sigopTxs: string[],
|
sigopTxs: string[],
|
||||||
addedTxs: string[],
|
addedTxs: string[],
|
||||||
|
acceleratedTxs: string[],
|
||||||
matchRate: number,
|
matchRate: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,6 +86,7 @@ export interface TransactionExtended extends IEsploraApi.Transaction {
|
|||||||
block: number,
|
block: number,
|
||||||
vsize: number,
|
vsize: number,
|
||||||
};
|
};
|
||||||
|
acceleration?: number;
|
||||||
uid?: number;
|
uid?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,9 +6,9 @@ import { BlockAudit, AuditScore } from '../mempool.interfaces';
|
|||||||
class BlocksAuditRepositories {
|
class BlocksAuditRepositories {
|
||||||
public async $saveAudit(audit: BlockAudit): Promise<void> {
|
public async $saveAudit(audit: BlockAudit): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await DB.query(`INSERT INTO blocks_audits(time, height, hash, missing_txs, added_txs, fresh_txs, sigop_txs, match_rate)
|
await DB.query(`INSERT INTO blocks_audits(time, height, hash, missing_txs, added_txs, fresh_txs, sigop_txs, accelerated_txs, match_rate)
|
||||||
VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?)`, [audit.time, audit.height, audit.hash, JSON.stringify(audit.missingTxs),
|
VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?, ?)`, [audit.time, audit.height, audit.hash, JSON.stringify(audit.missingTxs),
|
||||||
JSON.stringify(audit.addedTxs), JSON.stringify(audit.freshTxs), JSON.stringify(audit.sigopTxs), audit.matchRate]);
|
JSON.stringify(audit.addedTxs), JSON.stringify(audit.freshTxs), JSON.stringify(audit.sigopTxs), JSON.stringify(audit.acceleratedTxs), audit.matchRate]);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart
|
if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart
|
||||||
logger.debug(`Cannot save block audit for block ${audit.hash} because it has already been indexed, ignoring`);
|
logger.debug(`Cannot save block audit for block ${audit.hash} because it has already been indexed, ignoring`);
|
||||||
@ -51,7 +51,7 @@ class BlocksAuditRepositories {
|
|||||||
const [rows]: any[] = await DB.query(
|
const [rows]: any[] = await DB.query(
|
||||||
`SELECT blocks.height, blocks.hash as id, UNIX_TIMESTAMP(blocks.blockTimestamp) as timestamp, blocks.size,
|
`SELECT blocks.height, blocks.hash as id, UNIX_TIMESTAMP(blocks.blockTimestamp) as timestamp, blocks.size,
|
||||||
blocks.weight, blocks.tx_count,
|
blocks.weight, blocks.tx_count,
|
||||||
transactions, template, missing_txs as missingTxs, added_txs as addedTxs, fresh_txs as freshTxs, sigop_txs as sigopTxs, match_rate as matchRate
|
transactions, template, missing_txs as missingTxs, added_txs as addedTxs, fresh_txs as freshTxs, sigop_txs as sigopTxs, accelerated_txs as acceleratedTxs, match_rate as matchRate
|
||||||
FROM blocks_audits
|
FROM blocks_audits
|
||||||
JOIN blocks ON blocks.hash = blocks_audits.hash
|
JOIN blocks ON blocks.hash = blocks_audits.hash
|
||||||
JOIN blocks_templates ON blocks_templates.id = blocks_audits.hash
|
JOIN blocks_templates ON blocks_templates.id = blocks_audits.hash
|
||||||
@ -64,6 +64,7 @@ class BlocksAuditRepositories {
|
|||||||
rows[0].addedTxs = JSON.parse(rows[0].addedTxs);
|
rows[0].addedTxs = JSON.parse(rows[0].addedTxs);
|
||||||
rows[0].freshTxs = JSON.parse(rows[0].freshTxs);
|
rows[0].freshTxs = JSON.parse(rows[0].freshTxs);
|
||||||
rows[0].sigopTxs = JSON.parse(rows[0].sigopTxs);
|
rows[0].sigopTxs = JSON.parse(rows[0].sigopTxs);
|
||||||
|
rows[0].acceleratedTxs = JSON.parse(rows[0].acceleratedTxs);
|
||||||
rows[0].transactions = JSON.parse(rows[0].transactions);
|
rows[0].transactions = JSON.parse(rows[0].transactions);
|
||||||
rows[0].template = JSON.parse(rows[0].template);
|
rows[0].template = JSON.parse(rows[0].template);
|
||||||
|
|
||||||
|
|||||||
@ -124,5 +124,9 @@
|
|||||||
"GEOLITE2_CITY": "__MAXMIND_GEOLITE2_CITY__",
|
"GEOLITE2_CITY": "__MAXMIND_GEOLITE2_CITY__",
|
||||||
"GEOLITE2_ASN": "__MAXMIND_GEOLITE2_ASN__",
|
"GEOLITE2_ASN": "__MAXMIND_GEOLITE2_ASN__",
|
||||||
"GEOIP2_ISP": "__MAXMIND_GEOIP2_ISP__"
|
"GEOIP2_ISP": "__MAXMIND_GEOIP2_ISP__"
|
||||||
|
},
|
||||||
|
"MEMPOOL_SERVICES": {
|
||||||
|
"API": "__MEMPOOL_SERVICES_API__",
|
||||||
|
"ACCELERATIONS": __MEMPOOL_SERVICES_ACCELERATIONS__
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -126,6 +126,10 @@ __MAXMIND_GEOLITE2_CITY__=${MAXMIND_GEOLITE2_CITY:="/backend/GeoIP/GeoLite2-City
|
|||||||
__MAXMIND_GEOLITE2_ASN__=${MAXMIND_GEOLITE2_ASN:="/backend/GeoIP/GeoLite2-ASN.mmdb"}
|
__MAXMIND_GEOLITE2_ASN__=${MAXMIND_GEOLITE2_ASN:="/backend/GeoIP/GeoLite2-ASN.mmdb"}
|
||||||
__MAXMIND_GEOIP2_ISP__=${MAXMIND_GEOIP2_ISP:=""}
|
__MAXMIND_GEOIP2_ISP__=${MAXMIND_GEOIP2_ISP:=""}
|
||||||
|
|
||||||
|
# MEMPOOL_SERVICES
|
||||||
|
__MEMPOOL_SERVICES_API__==${MEMPOOL_SERVICES_API:=""}
|
||||||
|
__MEMPOOL_SERVICES_ACCELERATIONS__==${MEMPOOL_SERVICES_ACCELERATIONS:=false}
|
||||||
|
|
||||||
|
|
||||||
mkdir -p "${__MEMPOOL_CACHE_DIR__}"
|
mkdir -p "${__MEMPOOL_CACHE_DIR__}"
|
||||||
|
|
||||||
@ -243,5 +247,9 @@ sed -i "s!__MAXMIND_GEOLITE2_CITY__!${__MAXMIND_GEOLITE2_CITY__}!g" mempool-conf
|
|||||||
sed -i "s!__MAXMIND_GEOLITE2_ASN__!${__MAXMIND_GEOLITE2_ASN__}!g" mempool-config.json
|
sed -i "s!__MAXMIND_GEOLITE2_ASN__!${__MAXMIND_GEOLITE2_ASN__}!g" mempool-config.json
|
||||||
sed -i "s!__MAXMIND_GEOIP2_ISP__!${__MAXMIND_GEOIP2_ISP__}!g" mempool-config.json
|
sed -i "s!__MAXMIND_GEOIP2_ISP__!${__MAXMIND_GEOIP2_ISP__}!g" mempool-config.json
|
||||||
|
|
||||||
|
# MEMPOOL_SERVICES
|
||||||
|
sed -i "s!__MEMPOOL_SERVICES_API__!${__MEMPOOL_SERVICES_API__}!g" mempool-config.json
|
||||||
|
sed -i "s!__MEMPOOL_SERVICES_ACCELERATIONS__!${__MEMPOOL_SERVICES_ACCELERATIONS__}!g" mempool-config.json
|
||||||
|
|
||||||
|
|
||||||
node /backend/package/index.js
|
node /backend/package/index.js
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -16,6 +16,7 @@ const auditColors = {
|
|||||||
missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7),
|
missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7),
|
||||||
added: hexToColor('0099ff'),
|
added: hexToColor('0099ff'),
|
||||||
selected: darken(desaturate(hexToColor('0099ff'), 0.3), 0.7),
|
selected: darken(desaturate(hexToColor('0099ff'), 0.3), 0.7),
|
||||||
|
accelerated: hexToColor('8F5FF6'),
|
||||||
};
|
};
|
||||||
|
|
||||||
// convert from this class's update format to TxSprite's update format
|
// convert from this class's update format to TxSprite's update format
|
||||||
@ -36,8 +37,9 @@ 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';
|
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected' | 'accelerated';
|
||||||
context?: 'projected' | 'actual';
|
context?: 'projected' | 'actual';
|
||||||
scene?: BlockScene;
|
scene?: BlockScene;
|
||||||
|
|
||||||
@ -60,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;
|
||||||
@ -164,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
|
||||||
@ -179,6 +187,8 @@ export default class TxView implements TransactionStripped {
|
|||||||
return auditColors.added;
|
return auditColors.added;
|
||||||
case 'selected':
|
case 'selected':
|
||||||
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
|
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
|
||||||
|
case 'accelerated':
|
||||||
|
return auditColors.accelerated;
|
||||||
case 'found':
|
case 'found':
|
||||||
if (this.context === 'projected') {
|
if (this.context === 'projected') {
|
||||||
return auditFeeColors[feeLevelIndex] || auditFeeColors[mempoolFeeColors.length - 1];
|
return auditFeeColors[feeLevelIndex] || auditFeeColors[mempoolFeeColors.length - 1];
|
||||||
@ -186,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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
@ -48,6 +49,7 @@
|
|||||||
<td *ngSwitchCase="'fresh'"><span class="badge badge-warning" i18n="transaction.audit.recently-broadcasted">Recently broadcasted</span></td>
|
<td *ngSwitchCase="'fresh'"><span class="badge badge-warning" i18n="transaction.audit.recently-broadcasted">Recently broadcasted</span></td>
|
||||||
<td *ngSwitchCase="'added'"><span class="badge badge-warning" i18n="transaction.audit.added">Added</span></td>
|
<td *ngSwitchCase="'added'"><span class="badge badge-warning" i18n="transaction.audit.added">Added</span></td>
|
||||||
<td *ngSwitchCase="'selected'"><span class="badge badge-warning" i18n="transaction.audit.marginal">Marginal fee rate</span></td>
|
<td *ngSwitchCase="'selected'"><span class="badge badge-warning" i18n="transaction.audit.marginal">Marginal fee rate</span></td>
|
||||||
|
<td *ngSwitchCase="'accelerated'"><span class="badge badge-success" i18n="transaction.audit.accelerated">Accelerated</span></td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -336,6 +336,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
const isSelected = {};
|
const isSelected = {};
|
||||||
const isFresh = {};
|
const isFresh = {};
|
||||||
const isSigop = {};
|
const isSigop = {};
|
||||||
|
const isAccelerated = {};
|
||||||
this.numMissing = 0;
|
this.numMissing = 0;
|
||||||
this.numUnexpected = 0;
|
this.numUnexpected = 0;
|
||||||
|
|
||||||
@ -358,6 +359,9 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
for (const txid of blockAudit.sigopTxs || []) {
|
for (const txid of blockAudit.sigopTxs || []) {
|
||||||
isSigop[txid] = true;
|
isSigop[txid] = true;
|
||||||
}
|
}
|
||||||
|
for (const txid of blockAudit.acceleratedTxs || []) {
|
||||||
|
isAccelerated[txid] = true;
|
||||||
|
}
|
||||||
// set transaction statuses
|
// set transaction statuses
|
||||||
for (const tx of blockAudit.template) {
|
for (const tx of blockAudit.template) {
|
||||||
tx.context = 'projected';
|
tx.context = 'projected';
|
||||||
@ -370,6 +374,9 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
isMissing[tx.txid] = true;
|
isMissing[tx.txid] = true;
|
||||||
this.numMissing++;
|
this.numMissing++;
|
||||||
}
|
}
|
||||||
|
if (isAccelerated[tx.txid]) {
|
||||||
|
tx.status = 'accelerated';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (const [index, tx] of blockAudit.transactions.entries()) {
|
for (const [index, tx] of blockAudit.transactions.entries()) {
|
||||||
tx.context = 'actual';
|
tx.context = 'actual';
|
||||||
@ -384,6 +391,9 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
isSelected[tx.txid] = true;
|
isSelected[tx.txid] = true;
|
||||||
this.numUnexpected++;
|
this.numUnexpected++;
|
||||||
}
|
}
|
||||||
|
if (isAccelerated[tx.txid]) {
|
||||||
|
tx.status = 'accelerated';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (const tx of blockAudit.transactions) {
|
for (const tx of blockAudit.transactions) {
|
||||||
inBlock[tx.txid] = true;
|
inBlock[tx.txid] = true;
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -47,7 +47,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="arrowVisible" id="arrow-up" [ngStyle]="{'right': rightPosition + 75 + 'px', transition: transition }"></div>
|
<div *ngIf="arrowVisible" id="arrow-up" [ngStyle]="{'right': rightPosition + 75 + 'px', transition: transition }" [class.blink]="txPosition?.accelerated"></div>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
|||||||
@ -170,3 +170,33 @@
|
|||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.blink{
|
||||||
|
width:400px;
|
||||||
|
height:400px;
|
||||||
|
border-bottom: 35px solid #FFF;
|
||||||
|
animation: blink 0.2s infinite;
|
||||||
|
}
|
||||||
|
@keyframes blink{
|
||||||
|
0% {
|
||||||
|
border-bottom: 35px solid green;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
border-bottom: 35px solid yellow;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
border-bottom: 35px solid orange;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes blink{
|
||||||
|
0% {
|
||||||
|
border-bottom: 35px solid green;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
border-bottom: 35px solid yellow;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
border-bottom: 35px solid orange;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -26,6 +26,7 @@ import { animate, style, transition, trigger } from '@angular/animations';
|
|||||||
export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
@Input() minimal: boolean = false;
|
@Input() minimal: boolean = false;
|
||||||
@Input() blockWidth: number = 125;
|
@Input() blockWidth: number = 125;
|
||||||
|
@Input() containerWidth: number = null;
|
||||||
@Input() count: number = null;
|
@Input() count: number = null;
|
||||||
@Input() spotlight: number = 0;
|
@Input() spotlight: number = 0;
|
||||||
|
|
||||||
@ -252,7 +253,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reduceEmptyBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] {
|
reduceEmptyBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] {
|
||||||
const innerWidth = this.stateService.env.BASE_MODULE !== 'liquid' && window.innerWidth <= 767.98 ? window.innerWidth : window.innerWidth / 2;
|
const innerWidth = this.containerWidth || (this.stateService.env.BASE_MODULE !== 'liquid' && window.innerWidth <= 767.98 ? window.innerWidth : window.innerWidth / 2);
|
||||||
const blocksAmount = Math.min(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT, Math.floor(innerWidth / (this.blockWidth + this.blockPadding)));
|
const blocksAmount = Math.min(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT, Math.floor(innerWidth / (this.blockWidth + this.blockPadding)));
|
||||||
while (blocks.length < blocksAmount) {
|
while (blocks.length < blocksAmount) {
|
||||||
blocks.push({
|
blocks.push({
|
||||||
@ -272,7 +273,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reduceMempoolBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] {
|
reduceMempoolBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] {
|
||||||
const innerWidth = this.stateService.env.BASE_MODULE !== 'liquid' && window.innerWidth <= 767.98 ? window.innerWidth : window.innerWidth / 2;
|
const innerWidth = this.containerWidth || (this.stateService.env.BASE_MODULE !== 'liquid' && window.innerWidth <= 767.98 ? window.innerWidth : window.innerWidth / 2);
|
||||||
let blocksAmount;
|
let blocksAmount;
|
||||||
if (this.count) {
|
if (this.count) {
|
||||||
blocksAmount = 8;
|
blocksAmount = 8;
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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));
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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 {
|
||||||
@ -158,7 +159,7 @@ export interface TransactionStripped {
|
|||||||
fee: number;
|
fee: number;
|
||||||
vsize: number;
|
vsize: number;
|
||||||
value: number;
|
value: number;
|
||||||
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected';
|
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected' | 'accelerated';
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RbfTransaction extends TransactionStripped {
|
interface RbfTransaction extends TransactionStripped {
|
||||||
@ -168,6 +169,7 @@ interface RbfTransaction extends TransactionStripped {
|
|||||||
export interface MempoolPosition {
|
export interface MempoolPosition {
|
||||||
block: number,
|
block: number,
|
||||||
vsize: number,
|
vsize: number,
|
||||||
|
accelerated?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RewardStats {
|
export interface RewardStats {
|
||||||
|
|||||||
@ -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,8 +75,9 @@ 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';
|
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected' | 'accelerated';
|
||||||
context?: 'projected' | 'actual';
|
context?: 'projected' | 'actual';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user