mirror of
https://github.com/mempool/mempool.git
synced 2025-01-04 20:54:26 +01:00
158 lines
5.3 KiB
TypeScript
158 lines
5.3 KiB
TypeScript
import { CpfpInfo, TransactionExtended, TransactionStripped } from '../mempool.interfaces';
|
||
import config from '../config';
|
||
export class Common {
|
||
static nativeAssetId = config.MEMPOOL.NETWORK === 'liquidtestnet' ?
|
||
'144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49'
|
||
: '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d';
|
||
static _isLiquid = config.MEMPOOL.NETWORK === 'liquid' || config.MEMPOOL.NETWORK === 'liquidtestnet';
|
||
|
||
static isLiquid(): boolean {
|
||
return this._isLiquid;
|
||
}
|
||
|
||
static median(numbers: number[]) {
|
||
let medianNr = 0;
|
||
const numsLen = numbers.length;
|
||
if (numsLen % 2 === 0) {
|
||
medianNr = (numbers[numsLen / 2 - 1] + numbers[numsLen / 2]) / 2;
|
||
} else {
|
||
medianNr = numbers[(numsLen - 1) / 2];
|
||
}
|
||
return medianNr;
|
||
}
|
||
|
||
static percentile(numbers: number[], percentile: number) {
|
||
if (percentile === 50) {
|
||
return this.median(numbers);
|
||
}
|
||
const index = Math.ceil(numbers.length * (100 - percentile) * 1e-2);
|
||
if (index < 0 || index > numbers.length - 1) {
|
||
return 0;
|
||
}
|
||
return numbers[index];
|
||
}
|
||
|
||
static getFeesInRange(transactions: TransactionExtended[], rangeLength: number) {
|
||
const arr = [transactions[transactions.length - 1].effectiveFeePerVsize];
|
||
const chunk = 1 / (rangeLength - 1);
|
||
let itemsToAdd = rangeLength - 2;
|
||
|
||
while (itemsToAdd > 0) {
|
||
arr.push(transactions[Math.floor(transactions.length * chunk * itemsToAdd)].effectiveFeePerVsize);
|
||
itemsToAdd--;
|
||
}
|
||
|
||
arr.push(transactions[0].effectiveFeePerVsize);
|
||
return arr;
|
||
}
|
||
|
||
static findRbfTransactions(added: TransactionExtended[], deleted: TransactionExtended[]): { [txid: string]: TransactionExtended } {
|
||
const matches: { [txid: string]: TransactionExtended } = {};
|
||
deleted
|
||
// The replaced tx must have at least one input with nSequence < maxint-1 (That’s the opt-in)
|
||
.filter((tx) => tx.vin.some((vin) => vin.sequence < 0xfffffffe))
|
||
.forEach((deletedTx) => {
|
||
const foundMatches = added.find((addedTx) => {
|
||
// The new tx must, absolutely speaking, pay at least as much fee as the replaced tx.
|
||
return addedTx.fee > deletedTx.fee
|
||
// The new transaction must pay more fee per kB than the replaced tx.
|
||
&& addedTx.feePerVsize > deletedTx.feePerVsize
|
||
// Spends one or more of the same inputs
|
||
&& deletedTx.vin.some((deletedVin) =>
|
||
addedTx.vin.some((vin) => vin.txid === deletedVin.txid));
|
||
});
|
||
if (foundMatches) {
|
||
matches[deletedTx.txid] = foundMatches;
|
||
}
|
||
});
|
||
return matches;
|
||
}
|
||
|
||
static stripTransaction(tx: TransactionExtended): TransactionStripped {
|
||
return {
|
||
txid: tx.txid,
|
||
fee: tx.fee,
|
||
vsize: tx.weight / 4,
|
||
value: tx.vout.reduce((acc, vout) => acc + (vout.value ? vout.value : 0), 0),
|
||
};
|
||
}
|
||
|
||
static sleep(ms: number): Promise<void> {
|
||
return new Promise((resolve) => {
|
||
setTimeout(() => {
|
||
resolve();
|
||
}, ms);
|
||
});
|
||
}
|
||
|
||
static shuffleArray(array: any[]) {
|
||
for (let i = array.length - 1; i > 0; i--) {
|
||
const j = Math.floor(Math.random() * (i + 1));
|
||
[array[i], array[j]] = [array[j], array[i]];
|
||
}
|
||
}
|
||
|
||
static setRelativesAndGetCpfpInfo(tx: TransactionExtended, memPool: { [txid: string]: TransactionExtended }): CpfpInfo {
|
||
const parents = this.findAllParents(tx, memPool);
|
||
const lowerFeeParents = parents.filter((parent) => parent.feePerVsize < tx.effectiveFeePerVsize);
|
||
|
||
let totalWeight = tx.weight + lowerFeeParents.reduce((prev, val) => prev + val.weight, 0);
|
||
let totalFees = tx.fee + lowerFeeParents.reduce((prev, val) => prev + val.fee, 0);
|
||
|
||
tx.ancestors = parents
|
||
.map((t) => {
|
||
return {
|
||
txid: t.txid,
|
||
weight: t.weight,
|
||
fee: t.fee,
|
||
};
|
||
});
|
||
|
||
// Add high (high fee) decendant weight and fees
|
||
if (tx.bestDescendant) {
|
||
totalWeight += tx.bestDescendant.weight;
|
||
totalFees += tx.bestDescendant.fee;
|
||
}
|
||
|
||
tx.effectiveFeePerVsize = Math.max(Common.isLiquid() ? 0.1 : 1, totalFees / (totalWeight / 4));
|
||
tx.cpfpChecked = true;
|
||
|
||
return {
|
||
ancestors: tx.ancestors,
|
||
bestDescendant: tx.bestDescendant || null,
|
||
};
|
||
}
|
||
|
||
|
||
private static findAllParents(tx: TransactionExtended, memPool: { [txid: string]: TransactionExtended }): TransactionExtended[] {
|
||
let parents: TransactionExtended[] = [];
|
||
tx.vin.forEach((parent) => {
|
||
if (parents.find((p) => p.txid === parent.txid)) {
|
||
return;
|
||
}
|
||
|
||
const parentTx = memPool[parent.txid];
|
||
if (parentTx) {
|
||
if (tx.bestDescendant && tx.bestDescendant.fee / (tx.bestDescendant.weight / 4) > parentTx.feePerVsize) {
|
||
if (parentTx.bestDescendant && parentTx.bestDescendant.fee < tx.fee + tx.bestDescendant.fee) {
|
||
parentTx.bestDescendant = {
|
||
weight: tx.weight + tx.bestDescendant.weight,
|
||
fee: tx.fee + tx.bestDescendant.fee,
|
||
txid: tx.txid,
|
||
};
|
||
}
|
||
} else if (tx.feePerVsize > parentTx.feePerVsize) {
|
||
parentTx.bestDescendant = {
|
||
weight: tx.weight,
|
||
fee: tx.fee,
|
||
txid: tx.txid
|
||
};
|
||
}
|
||
parents.push(parentTx);
|
||
parents = parents.concat(this.findAllParents(parentTx, memPool));
|
||
}
|
||
});
|
||
return parents;
|
||
}
|
||
}
|