2021-03-18 23:47:40 +07:00
|
|
|
import logger from '../logger';
|
2022-10-27 10:21:39 -06:00
|
|
|
import { MempoolBlock, TransactionExtended, AuditTransaction, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor } from '../mempool.interfaces';
|
2020-05-24 16:29:30 +07:00
|
|
|
import { Common } from './common';
|
2021-03-18 05:52:46 +00:00
|
|
|
import config from '../config';
|
2022-10-27 10:21:39 -06:00
|
|
|
import { PairingHeap } from '../utils/pairing-heap';
|
2020-02-16 22:15:07 +07:00
|
|
|
|
|
|
|
class MempoolBlocks {
|
2020-06-08 02:08:51 +07:00
|
|
|
private mempoolBlocks: MempoolBlockWithTransactions[] = [];
|
2022-05-31 21:36:42 +00:00
|
|
|
private mempoolBlockDeltas: MempoolBlockDelta[] = [];
|
2020-02-16 22:15:07 +07:00
|
|
|
|
|
|
|
constructor() {}
|
|
|
|
|
|
|
|
public getMempoolBlocks(): MempoolBlock[] {
|
2020-06-08 02:08:51 +07:00
|
|
|
return this.mempoolBlocks.map((block) => {
|
|
|
|
return {
|
|
|
|
blockSize: block.blockSize,
|
|
|
|
blockVSize: block.blockVSize,
|
|
|
|
nTx: block.nTx,
|
|
|
|
totalFees: block.totalFees,
|
|
|
|
medianFee: block.medianFee,
|
|
|
|
feeRange: block.feeRange,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public getMempoolBlocksWithTransactions(): MempoolBlockWithTransactions[] {
|
2020-02-16 22:15:07 +07:00
|
|
|
return this.mempoolBlocks;
|
|
|
|
}
|
|
|
|
|
2022-05-31 21:36:42 +00:00
|
|
|
public getMempoolBlockDeltas(): MempoolBlockDelta[] {
|
2022-06-02 01:29:03 +04:00
|
|
|
return this.mempoolBlockDeltas;
|
2022-05-31 21:36:42 +00:00
|
|
|
}
|
|
|
|
|
2020-02-23 19:16:50 +07:00
|
|
|
public updateMempoolBlocks(memPool: { [txid: string]: TransactionExtended }): void {
|
2020-02-16 22:15:07 +07:00
|
|
|
const latestMempool = memPool;
|
2020-02-23 19:16:50 +07:00
|
|
|
const memPoolArray: TransactionExtended[] = [];
|
2020-02-16 22:15:07 +07:00
|
|
|
for (const i in latestMempool) {
|
|
|
|
if (latestMempool.hasOwnProperty(i)) {
|
|
|
|
memPoolArray.push(latestMempool[i]);
|
|
|
|
}
|
|
|
|
}
|
2021-03-18 23:47:40 +07:00
|
|
|
const start = new Date().getTime();
|
|
|
|
|
|
|
|
// Clear bestDescendants & ancestors
|
|
|
|
memPoolArray.forEach((tx) => {
|
|
|
|
tx.bestDescendant = null;
|
|
|
|
tx.ancestors = [];
|
|
|
|
tx.cpfpChecked = false;
|
|
|
|
if (!tx.effectiveFeePerVsize) {
|
|
|
|
tx.effectiveFeePerVsize = tx.feePerVsize;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// First sort
|
2020-02-16 22:15:07 +07:00
|
|
|
memPoolArray.sort((a, b) => b.feePerVsize - a.feePerVsize);
|
2021-03-18 23:47:40 +07:00
|
|
|
|
|
|
|
// Loop through and traverse all ancestors and sum up all the sizes + fees
|
|
|
|
// Pass down size + fee to all unconfirmed children
|
|
|
|
let sizes = 0;
|
|
|
|
memPoolArray.forEach((tx, i) => {
|
2021-04-10 21:26:05 +04:00
|
|
|
sizes += tx.weight;
|
2021-03-18 23:47:40 +07:00
|
|
|
if (sizes > 4000000 * 8) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Common.setRelativesAndGetCpfpInfo(tx, memPool);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Final sort, by effective fee
|
|
|
|
memPoolArray.sort((a, b) => b.effectiveFeePerVsize - a.effectiveFeePerVsize);
|
|
|
|
|
|
|
|
const end = new Date().getTime();
|
|
|
|
const time = end - start;
|
|
|
|
logger.debug('Mempool blocks calculated in ' + time / 1000 + ' seconds');
|
|
|
|
|
2022-05-31 21:36:42 +00:00
|
|
|
const { blocks, deltas } = this.calculateMempoolBlocks(memPoolArray, this.mempoolBlocks);
|
2022-10-27 10:21:39 -06:00
|
|
|
|
2022-06-02 01:29:03 +04:00
|
|
|
this.mempoolBlocks = blocks;
|
|
|
|
this.mempoolBlockDeltas = deltas;
|
2020-02-16 22:15:07 +07:00
|
|
|
}
|
|
|
|
|
2022-06-02 01:29:03 +04:00
|
|
|
private calculateMempoolBlocks(transactionsSorted: TransactionExtended[], prevBlocks: MempoolBlockWithTransactions[]):
|
|
|
|
{ blocks: MempoolBlockWithTransactions[], deltas: MempoolBlockDelta[] } {
|
2020-06-08 02:08:51 +07:00
|
|
|
const mempoolBlocks: MempoolBlockWithTransactions[] = [];
|
2022-05-31 21:36:42 +00:00
|
|
|
const mempoolBlockDeltas: MempoolBlockDelta[] = [];
|
2021-08-14 03:24:31 +03:00
|
|
|
let blockWeight = 0;
|
2020-02-16 22:15:07 +07:00
|
|
|
let blockSize = 0;
|
2020-02-23 19:16:50 +07:00
|
|
|
let transactions: TransactionExtended[] = [];
|
2020-02-16 22:15:07 +07:00
|
|
|
transactionsSorted.forEach((tx) => {
|
2021-10-05 04:11:13 +04:00
|
|
|
if (blockWeight + tx.weight <= config.MEMPOOL.BLOCK_WEIGHT_UNITS
|
|
|
|
|| mempoolBlocks.length === config.MEMPOOL.MEMPOOL_BLOCKS_AMOUNT - 1) {
|
2021-08-14 03:24:31 +03:00
|
|
|
blockWeight += tx.weight;
|
2020-02-16 22:15:07 +07:00
|
|
|
blockSize += tx.size;
|
|
|
|
transactions.push(tx);
|
|
|
|
} else {
|
2021-08-14 03:24:31 +03:00
|
|
|
mempoolBlocks.push(this.dataToMempoolBlocks(transactions, blockSize, blockWeight, mempoolBlocks.length));
|
|
|
|
blockWeight = tx.weight;
|
2020-06-08 18:55:53 +07:00
|
|
|
blockSize = tx.size;
|
|
|
|
transactions = [tx];
|
2020-02-16 22:15:07 +07:00
|
|
|
}
|
|
|
|
});
|
|
|
|
if (transactions.length) {
|
2021-08-14 03:24:31 +03:00
|
|
|
mempoolBlocks.push(this.dataToMempoolBlocks(transactions, blockSize, blockWeight, mempoolBlocks.length));
|
2020-02-16 22:15:07 +07:00
|
|
|
}
|
2022-10-10 22:13:04 +00:00
|
|
|
|
2022-05-31 21:36:42 +00:00
|
|
|
// Calculate change from previous block states
|
|
|
|
for (let i = 0; i < Math.max(mempoolBlocks.length, prevBlocks.length); i++) {
|
2022-06-02 01:29:03 +04:00
|
|
|
let added: TransactionStripped[] = [];
|
|
|
|
let removed: string[] = [];
|
2022-05-31 21:36:42 +00:00
|
|
|
if (mempoolBlocks[i] && !prevBlocks[i]) {
|
2022-06-02 01:29:03 +04:00
|
|
|
added = mempoolBlocks[i].transactions;
|
2022-05-31 21:36:42 +00:00
|
|
|
} else if (!mempoolBlocks[i] && prevBlocks[i]) {
|
2022-06-02 01:29:03 +04:00
|
|
|
removed = prevBlocks[i].transactions.map(tx => tx.txid);
|
2022-05-31 21:36:42 +00:00
|
|
|
} else if (mempoolBlocks[i] && prevBlocks[i]) {
|
2022-06-02 01:29:03 +04:00
|
|
|
const prevIds = {};
|
|
|
|
const newIds = {};
|
2022-05-31 21:36:42 +00:00
|
|
|
prevBlocks[i].transactions.forEach(tx => {
|
2022-06-02 01:29:03 +04:00
|
|
|
prevIds[tx.txid] = true;
|
|
|
|
});
|
2022-05-31 21:36:42 +00:00
|
|
|
mempoolBlocks[i].transactions.forEach(tx => {
|
2022-06-02 01:29:03 +04:00
|
|
|
newIds[tx.txid] = true;
|
|
|
|
});
|
2022-05-31 21:36:42 +00:00
|
|
|
prevBlocks[i].transactions.forEach(tx => {
|
2022-06-02 01:29:03 +04:00
|
|
|
if (!newIds[tx.txid]) {
|
|
|
|
removed.push(tx.txid);
|
|
|
|
}
|
|
|
|
});
|
2022-05-31 21:36:42 +00:00
|
|
|
mempoolBlocks[i].transactions.forEach(tx => {
|
2022-06-02 01:29:03 +04:00
|
|
|
if (!prevIds[tx.txid]) {
|
|
|
|
added.push(tx);
|
|
|
|
}
|
|
|
|
});
|
2022-05-31 21:36:42 +00:00
|
|
|
}
|
|
|
|
mempoolBlockDeltas.push({
|
|
|
|
added,
|
|
|
|
removed
|
2022-06-02 01:29:03 +04:00
|
|
|
});
|
2022-05-31 21:36:42 +00:00
|
|
|
}
|
2022-10-10 22:13:04 +00:00
|
|
|
|
2022-05-31 21:36:42 +00:00
|
|
|
return {
|
|
|
|
blocks: mempoolBlocks,
|
|
|
|
deltas: mempoolBlockDeltas
|
2022-06-02 01:29:03 +04:00
|
|
|
};
|
2020-02-16 22:15:07 +07:00
|
|
|
}
|
|
|
|
|
2022-10-10 22:13:04 +00:00
|
|
|
/*
|
|
|
|
* Build projected mempool blocks using an approximation of the transaction selection algorithm from Bitcoin Core
|
|
|
|
* (see BlockAssembler in https://github.com/bitcoin/bitcoin/blob/master/src/node/miner.cpp)
|
|
|
|
*
|
2022-10-27 10:21:39 -06:00
|
|
|
* blockLimit: number of blocks to build in total.
|
|
|
|
* weightLimit: maximum weight of transactions to consider using the selection algorithm.
|
|
|
|
* if weightLimit is significantly lower than the mempool size, results may start to diverge from getBlockTemplate
|
|
|
|
* condenseRest: whether to ignore excess transactions or append them to the final block.
|
2022-10-10 22:13:04 +00:00
|
|
|
*/
|
2022-10-27 10:21:39 -06:00
|
|
|
public makeBlockTemplates(mempool: { [txid: string]: TransactionExtended }, blockLimit: number, weightLimit: number | null = null, condenseRest = false): MempoolBlockWithTransactions[] {
|
|
|
|
const start = Date.now();
|
|
|
|
const auditPool: { [txid: string]: AuditTransaction } = {};
|
|
|
|
const mempoolArray: AuditTransaction[] = [];
|
|
|
|
const restOfArray: TransactionExtended[] = [];
|
|
|
|
|
|
|
|
let weight = 0;
|
|
|
|
const maxWeight = weightLimit ? Math.max(4_000_000 * blockLimit, weightLimit) : Infinity;
|
|
|
|
// grab the top feerate txs up to maxWeight
|
|
|
|
Object.values(mempool).sort((a, b) => b.feePerVsize - a.feePerVsize).forEach(tx => {
|
|
|
|
weight += tx.weight;
|
|
|
|
if (weight >= maxWeight) {
|
|
|
|
restOfArray.push(tx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// initializing everything up front helps V8 optimize property access later
|
|
|
|
auditPool[tx.txid] = {
|
|
|
|
txid: tx.txid,
|
|
|
|
fee: tx.fee,
|
|
|
|
size: tx.size,
|
|
|
|
weight: tx.weight,
|
|
|
|
feePerVsize: tx.feePerVsize,
|
|
|
|
vin: tx.vin,
|
|
|
|
relativesSet: false,
|
|
|
|
ancestorMap: new Map<string, AuditTransaction>(),
|
|
|
|
children: new Set<AuditTransaction>(),
|
|
|
|
ancestorFee: 0,
|
|
|
|
ancestorWeight: 0,
|
2022-10-10 22:13:04 +00:00
|
|
|
score: 0,
|
2022-10-27 10:21:39 -06:00
|
|
|
used: false,
|
2022-10-10 22:13:04 +00:00
|
|
|
modified: false,
|
2022-10-27 10:21:39 -06:00
|
|
|
modifiedNode: null,
|
|
|
|
}
|
|
|
|
mempoolArray.push(auditPool[tx.txid]);
|
|
|
|
})
|
2022-10-10 22:13:04 +00:00
|
|
|
|
|
|
|
// Build relatives graph & calculate ancestor scores
|
2022-10-27 10:21:39 -06:00
|
|
|
for (const tx of mempoolArray) {
|
|
|
|
if (!tx.relativesSet) {
|
|
|
|
this.setRelatives(tx, auditPool);
|
|
|
|
}
|
|
|
|
}
|
2022-10-10 22:13:04 +00:00
|
|
|
|
|
|
|
// Sort by descending ancestor score
|
2022-10-27 10:21:39 -06:00
|
|
|
mempoolArray.sort((a, b) => (b.score || 0) - (a.score || 0));
|
2022-10-10 22:13:04 +00:00
|
|
|
|
|
|
|
// Build blocks by greedily choosing the highest feerate package
|
|
|
|
// (i.e. the package rooted in the transaction with the best ancestor score)
|
|
|
|
const blocks: MempoolBlockWithTransactions[] = [];
|
|
|
|
let blockWeight = 4000;
|
|
|
|
let blockSize = 0;
|
2022-10-27 10:21:39 -06:00
|
|
|
let transactions: AuditTransaction[] = [];
|
|
|
|
const modified: PairingHeap<AuditTransaction> = new PairingHeap((a, b): boolean => (a.score || 0) > (b.score || 0));
|
|
|
|
let overflow: AuditTransaction[] = [];
|
2022-10-10 22:13:04 +00:00
|
|
|
let failures = 0;
|
2022-10-27 10:21:39 -06:00
|
|
|
let top = 0;
|
|
|
|
while ((top < mempoolArray.length || !modified.isEmpty()) && (condenseRest || blocks.length < blockLimit)) {
|
|
|
|
// skip invalid transactions
|
|
|
|
while (top < mempoolArray.length && (mempoolArray[top].used || mempoolArray[top].modified)) {
|
|
|
|
top++;
|
|
|
|
}
|
|
|
|
|
2022-10-10 22:13:04 +00:00
|
|
|
// Select best next package
|
|
|
|
let nextTx;
|
2022-10-27 10:21:39 -06:00
|
|
|
const nextPoolTx = mempoolArray[top];
|
|
|
|
const nextModifiedTx = modified.peek();
|
|
|
|
if (nextPoolTx && (!nextModifiedTx || (nextPoolTx.score || 0) > (nextModifiedTx.score || 0))) {
|
|
|
|
nextTx = nextPoolTx;
|
|
|
|
top++;
|
2022-10-10 22:13:04 +00:00
|
|
|
} else {
|
2022-10-27 10:21:39 -06:00
|
|
|
modified.pop();
|
|
|
|
if (nextModifiedTx) {
|
|
|
|
nextTx = nextModifiedTx;
|
|
|
|
nextTx.modifiedNode = undefined;
|
|
|
|
}
|
2022-10-10 22:13:04 +00:00
|
|
|
}
|
|
|
|
|
2022-10-27 10:21:39 -06:00
|
|
|
if (nextTx && !nextTx?.used) {
|
2022-10-10 22:13:04 +00:00
|
|
|
// Check if the package fits into this block
|
2022-10-27 10:21:39 -06:00
|
|
|
if (blockWeight + nextTx.ancestorWeight < config.MEMPOOL.BLOCK_WEIGHT_UNITS) {
|
|
|
|
blockWeight += nextTx.ancestorWeight;
|
|
|
|
const ancestors: AuditTransaction[] = Array.from(nextTx.ancestorMap.values());
|
|
|
|
// sort ancestors by dependency graph (equivalent to sorting by ascending ancestor count)
|
|
|
|
const sortedTxSet = [...ancestors.sort((a, b) => { return (a.ancestorMap.size || 0) - (b.ancestorMap.size || 0); }), nextTx];
|
|
|
|
const effectiveFeeRate = nextTx.ancestorFee / (nextTx.ancestorWeight / 4);
|
2022-10-18 21:03:21 +00:00
|
|
|
sortedTxSet.forEach((ancestor, i, arr) => {
|
2022-10-27 10:21:39 -06:00
|
|
|
const mempoolTx = mempool[ancestor.txid];
|
|
|
|
if (ancestor && !ancestor?.used) {
|
|
|
|
ancestor.used = true;
|
|
|
|
// update original copy of this tx with effective fee rate & relatives data
|
|
|
|
mempoolTx.effectiveFeePerVsize = effectiveFeeRate;
|
|
|
|
mempoolTx.ancestors = (Array.from(ancestor.ancestorMap?.values()) as AuditTransaction[]).map((a) => {
|
|
|
|
return {
|
|
|
|
txid: a.txid,
|
|
|
|
fee: a.fee,
|
|
|
|
weight: a.weight,
|
|
|
|
}
|
|
|
|
})
|
2022-10-10 22:13:04 +00:00
|
|
|
if (i < arr.length - 1) {
|
2022-10-27 10:21:39 -06:00
|
|
|
mempoolTx.bestDescendant = {
|
|
|
|
txid: arr[arr.length - 1].txid,
|
|
|
|
fee: arr[arr.length - 1].fee,
|
|
|
|
weight: arr[arr.length - 1].weight,
|
2022-10-10 22:13:04 +00:00
|
|
|
};
|
|
|
|
}
|
2022-10-27 10:21:39 -06:00
|
|
|
transactions.push(ancestor);
|
|
|
|
blockSize += ancestor.size;
|
2022-10-10 22:13:04 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-10-27 10:21:39 -06:00
|
|
|
// remove these as valid package ancestors for any descendants remaining in the mempool
|
|
|
|
if (sortedTxSet.length) {
|
2022-10-10 22:13:04 +00:00
|
|
|
sortedTxSet.forEach(tx => {
|
2022-10-27 10:21:39 -06:00
|
|
|
this.updateDescendants(tx, auditPool, modified);
|
2022-10-10 22:13:04 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
failures = 0;
|
|
|
|
} else {
|
|
|
|
// hold this package in an overflow list while we check for smaller options
|
|
|
|
overflow.push(nextTx);
|
|
|
|
failures++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// this block is full
|
|
|
|
const exceededPackageTries = failures > 1000 && blockWeight > (config.MEMPOOL.BLOCK_WEIGHT_UNITS - 4000);
|
2022-10-27 10:21:39 -06:00
|
|
|
if (exceededPackageTries && (!condenseRest || blocks.length < blockLimit - 1)) {
|
2022-10-10 22:13:04 +00:00
|
|
|
// construct this block
|
2022-10-27 10:21:39 -06:00
|
|
|
if (transactions.length) {
|
|
|
|
blocks.push(this.dataToMempoolBlocks(transactions.map(t => mempool[t.txid]), blockSize, blockWeight, blocks.length));
|
|
|
|
}
|
2022-10-10 22:13:04 +00:00
|
|
|
// reset for the next block
|
|
|
|
transactions = [];
|
|
|
|
blockSize = 0;
|
|
|
|
blockWeight = 4000;
|
|
|
|
|
|
|
|
// 'overflow' packages didn't fit in this block, but are valid candidates for the next
|
2022-10-27 10:21:39 -06:00
|
|
|
for (const overflowTx of overflow.reverse()) {
|
|
|
|
if (overflowTx.modified) {
|
|
|
|
overflowTx.modifiedNode = modified.add(overflowTx);
|
|
|
|
} else {
|
|
|
|
top--;
|
|
|
|
mempoolArray[top] = overflowTx;
|
|
|
|
}
|
2022-10-10 22:13:04 +00:00
|
|
|
}
|
2022-10-27 10:21:39 -06:00
|
|
|
overflow = [];
|
2022-10-10 22:13:04 +00:00
|
|
|
}
|
2022-10-27 10:21:39 -06:00
|
|
|
}
|
|
|
|
if (condenseRest) {
|
|
|
|
// pack any leftover transactions into the last block
|
|
|
|
for (const tx of overflow) {
|
|
|
|
if (!tx || tx?.used) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
blockWeight += tx.weight;
|
|
|
|
blockSize += tx.size;
|
|
|
|
transactions.push(tx);
|
|
|
|
tx.used = true;
|
2022-10-10 22:13:04 +00:00
|
|
|
}
|
2022-10-27 10:21:39 -06:00
|
|
|
const blockTransactions = transactions.map(t => mempool[t.txid])
|
|
|
|
restOfArray.forEach(tx => {
|
|
|
|
blockWeight += tx.weight;
|
|
|
|
blockSize += tx.size;
|
|
|
|
blockTransactions.push(tx);
|
|
|
|
});
|
|
|
|
if (blockTransactions.length) {
|
|
|
|
blocks.push(this.dataToMempoolBlocks(blockTransactions, blockSize, blockWeight, blocks.length));
|
|
|
|
}
|
|
|
|
transactions = [];
|
|
|
|
} else if (transactions.length) {
|
|
|
|
blocks.push(this.dataToMempoolBlocks(transactions.map(t => mempool[t.txid]), blockSize, blockWeight, blocks.length));
|
2022-10-10 22:13:04 +00:00
|
|
|
}
|
|
|
|
|
2022-10-27 10:21:39 -06:00
|
|
|
const end = Date.now();
|
2022-10-10 22:13:04 +00:00
|
|
|
const time = end - start;
|
|
|
|
logger.debug('Mempool templates calculated in ' + time / 1000 + ' seconds');
|
|
|
|
|
|
|
|
return blocks;
|
|
|
|
}
|
|
|
|
|
2022-10-27 10:21:39 -06:00
|
|
|
// traverse in-mempool ancestors
|
|
|
|
// recursion unavoidable, but should be limited to depth < 25 by mempool policy
|
|
|
|
public setRelatives(
|
|
|
|
tx: AuditTransaction,
|
|
|
|
mempool: { [txid: string]: AuditTransaction },
|
|
|
|
): void {
|
|
|
|
for (const parent of tx.vin) {
|
2022-10-10 22:13:04 +00:00
|
|
|
const parentTx = mempool[parent.txid];
|
2022-10-27 10:21:39 -06:00
|
|
|
if (parentTx && !tx.ancestorMap!.has(parent.txid)) {
|
|
|
|
tx.ancestorMap.set(parent.txid, parentTx);
|
|
|
|
parentTx.children.add(tx);
|
|
|
|
// visit each node only once
|
|
|
|
if (!parentTx.relativesSet) {
|
|
|
|
this.setRelatives(parentTx, mempool);
|
2022-10-10 22:13:04 +00:00
|
|
|
}
|
2022-10-27 10:21:39 -06:00
|
|
|
parentTx.ancestorMap.forEach((ancestor) => {
|
|
|
|
tx.ancestorMap.set(ancestor.txid, ancestor);
|
|
|
|
});
|
2022-10-10 22:13:04 +00:00
|
|
|
}
|
2022-10-27 10:21:39 -06:00
|
|
|
};
|
|
|
|
tx.ancestorFee = tx.fee || 0;
|
|
|
|
tx.ancestorWeight = tx.weight || 0;
|
|
|
|
tx.ancestorMap.forEach((ancestor) => {
|
|
|
|
tx.ancestorFee += ancestor.fee;
|
|
|
|
tx.ancestorWeight += ancestor.weight;
|
2022-10-10 22:13:04 +00:00
|
|
|
});
|
2022-10-27 10:21:39 -06:00
|
|
|
tx.score = tx.ancestorFee / (tx.ancestorWeight || 1);
|
|
|
|
tx.relativesSet = true;
|
2022-10-10 22:13:04 +00:00
|
|
|
}
|
|
|
|
|
2022-10-27 10:21:39 -06:00
|
|
|
// iterate over remaining descendants, removing the root as a valid ancestor & updating the ancestor score
|
|
|
|
// avoids recursion to limit call stack depth
|
2022-10-10 22:13:04 +00:00
|
|
|
private updateDescendants(
|
2022-10-27 10:21:39 -06:00
|
|
|
rootTx: AuditTransaction,
|
|
|
|
mempool: { [txid: string]: AuditTransaction },
|
|
|
|
modified: PairingHeap<AuditTransaction>,
|
|
|
|
): void {
|
|
|
|
const descendantSet: Set<AuditTransaction> = new Set();
|
|
|
|
// stack of nodes left to visit
|
|
|
|
const descendants: AuditTransaction[] = [];
|
|
|
|
let descendantTx;
|
|
|
|
let ancestorIndex;
|
|
|
|
let tmpScore;
|
|
|
|
rootTx.children.forEach(childTx => {
|
|
|
|
if (!descendantSet.has(childTx)) {
|
|
|
|
descendants.push(childTx);
|
|
|
|
descendantSet.add(childTx);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
while (descendants.length) {
|
|
|
|
descendantTx = descendants.pop();
|
|
|
|
if (descendantTx && descendantTx.ancestorMap && descendantTx.ancestorMap.has(rootTx.txid)) {
|
|
|
|
// remove tx as ancestor
|
|
|
|
descendantTx.ancestorMap.delete(rootTx.txid);
|
|
|
|
descendantTx.ancestorFee -= rootTx.fee;
|
|
|
|
descendantTx.ancestorWeight -= rootTx.weight;
|
|
|
|
tmpScore = descendantTx.score;
|
|
|
|
descendantTx.score = descendantTx.ancestorFee / descendantTx.ancestorWeight;
|
|
|
|
|
|
|
|
if (!descendantTx.modifiedNode) {
|
|
|
|
descendantTx.modified = true;
|
|
|
|
descendantTx.modifiedNode = modified.add(descendantTx);
|
|
|
|
} else {
|
|
|
|
// rebalance modified heap if score has changed
|
|
|
|
if (descendantTx.score < tmpScore) {
|
|
|
|
modified.decreasePriority(descendantTx.modifiedNode);
|
|
|
|
} else if (descendantTx.score > tmpScore) {
|
|
|
|
modified.increasePriority(descendantTx.modifiedNode);
|
2022-10-10 22:13:04 +00:00
|
|
|
}
|
|
|
|
}
|
2022-10-27 10:21:39 -06:00
|
|
|
|
|
|
|
// add this node's children to the stack
|
|
|
|
descendantTx.children.forEach(childTx => {
|
|
|
|
// visit each node only once
|
|
|
|
if (!descendantSet.has(childTx)) {
|
|
|
|
descendants.push(childTx);
|
|
|
|
descendantSet.add(childTx);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2022-10-10 22:13:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-24 16:29:30 +07:00
|
|
|
private dataToMempoolBlocks(transactions: TransactionExtended[],
|
2021-08-14 03:24:31 +03:00
|
|
|
blockSize: number, blockWeight: number, blocksIndex: number): MempoolBlockWithTransactions {
|
2020-03-20 02:07:12 +07:00
|
|
|
let rangeLength = 4;
|
2020-02-16 22:15:07 +07:00
|
|
|
if (blocksIndex === 0) {
|
|
|
|
rangeLength = 8;
|
|
|
|
}
|
|
|
|
if (transactions.length > 4000) {
|
2020-03-20 02:07:12 +07:00
|
|
|
rangeLength = 6;
|
2020-02-16 22:15:07 +07:00
|
|
|
} else if (transactions.length > 10000) {
|
|
|
|
rangeLength = 8;
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
blockSize: blockSize,
|
2021-08-14 03:24:31 +03:00
|
|
|
blockVSize: blockWeight / 4,
|
2020-02-16 22:15:07 +07:00
|
|
|
nTx: transactions.length,
|
2020-03-17 21:53:20 +07:00
|
|
|
totalFees: transactions.reduce((acc, cur) => acc + cur.fee, 0),
|
2021-03-18 23:47:40 +07:00
|
|
|
medianFee: Common.percentile(transactions.map((tx) => tx.effectiveFeePerVsize), config.MEMPOOL.RECOMMENDED_FEE_PERCENTILE),
|
2020-05-24 16:29:30 +07:00
|
|
|
feeRange: Common.getFeesInRange(transactions, rangeLength),
|
2020-06-08 02:08:51 +07:00
|
|
|
transactionIds: transactions.map((tx) => tx.txid),
|
2022-05-30 17:29:30 +00:00
|
|
|
transactions: transactions.map((tx) => Common.stripTransaction(tx)),
|
2020-02-16 22:15:07 +07:00
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default new MempoolBlocks();
|