mirror of
https://github.com/mempool/mempool.git
synced 2025-02-23 06:35:15 +01:00
Merge pull request #3838 from mempool/mononaut/dependent-rate-indexing
calculate & index ancestor-dependent effective rates
This commit is contained in:
commit
e8420853e2
4 changed files with 69 additions and 21 deletions
|
@ -1,4 +1,4 @@
|
||||||
import { Ancestor, CpfpInfo, CpfpSummary, EffectiveFeeStats, MempoolBlockWithTransactions, TransactionExtended, MempoolTransactionExtended, TransactionStripped, WorkingEffectiveFeeStats } from '../mempool.interfaces';
|
import { Ancestor, CpfpInfo, CpfpSummary, CpfpCluster, EffectiveFeeStats, MempoolBlockWithTransactions, TransactionExtended, MempoolTransactionExtended, TransactionStripped, WorkingEffectiveFeeStats } from '../mempool.interfaces';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import { NodeSocket } from '../repositories/NodesSocketsRepository';
|
import { NodeSocket } from '../repositories/NodesSocketsRepository';
|
||||||
import { isIP } from 'net';
|
import { isIP } from 'net';
|
||||||
|
@ -374,40 +374,80 @@ export class Common {
|
||||||
}
|
}
|
||||||
|
|
||||||
static calculateCpfp(height: number, transactions: TransactionExtended[]): CpfpSummary {
|
static calculateCpfp(height: number, transactions: TransactionExtended[]): CpfpSummary {
|
||||||
const clusters: { root: string, height: number, txs: Ancestor[], effectiveFeePerVsize: number }[] = [];
|
const clusters: CpfpCluster[] = []; // list of all cpfp clusters in this block
|
||||||
let cluster: TransactionExtended[] = [];
|
const clusterMap: { [txid: string]: CpfpCluster } = {}; // map transactions to their cpfp cluster
|
||||||
let ancestors: { [txid: string]: boolean } = {};
|
let clusterTxs: TransactionExtended[] = []; // working list of elements of the current cluster
|
||||||
|
let ancestors: { [txid: string]: boolean } = {}; // working set of ancestors of the current cluster root
|
||||||
const txMap = {};
|
const txMap = {};
|
||||||
|
// initialize the txMap
|
||||||
|
for (const tx of transactions) {
|
||||||
|
txMap[tx.txid] = tx;
|
||||||
|
}
|
||||||
|
// reverse pass to identify CPFP clusters
|
||||||
for (let i = transactions.length - 1; i >= 0; i--) {
|
for (let i = transactions.length - 1; i >= 0; i--) {
|
||||||
const tx = transactions[i];
|
const tx = transactions[i];
|
||||||
txMap[tx.txid] = tx;
|
|
||||||
if (!ancestors[tx.txid]) {
|
if (!ancestors[tx.txid]) {
|
||||||
let totalFee = 0;
|
let totalFee = 0;
|
||||||
let totalVSize = 0;
|
let totalVSize = 0;
|
||||||
cluster.forEach(tx => {
|
clusterTxs.forEach(tx => {
|
||||||
totalFee += tx?.fee || 0;
|
totalFee += tx?.fee || 0;
|
||||||
totalVSize += (tx.weight / 4);
|
totalVSize += (tx.weight / 4);
|
||||||
});
|
});
|
||||||
const effectiveFeePerVsize = totalFee / totalVSize;
|
const effectiveFeePerVsize = totalFee / totalVSize;
|
||||||
if (cluster.length > 1) {
|
let cluster: CpfpCluster;
|
||||||
clusters.push({
|
if (clusterTxs.length > 1) {
|
||||||
root: cluster[0].txid,
|
cluster = {
|
||||||
|
root: clusterTxs[0].txid,
|
||||||
height,
|
height,
|
||||||
txs: cluster.map(tx => { return { txid: tx.txid, weight: tx.weight, fee: tx.fee || 0 }; }),
|
txs: clusterTxs.map(tx => { return { txid: tx.txid, weight: tx.weight, fee: tx.fee || 0 }; }),
|
||||||
effectiveFeePerVsize,
|
effectiveFeePerVsize,
|
||||||
});
|
};
|
||||||
|
clusters.push(cluster);
|
||||||
}
|
}
|
||||||
cluster.forEach(tx => {
|
clusterTxs.forEach(tx => {
|
||||||
txMap[tx.txid].effectiveFeePerVsize = effectiveFeePerVsize;
|
txMap[tx.txid].effectiveFeePerVsize = effectiveFeePerVsize;
|
||||||
|
if (cluster) {
|
||||||
|
clusterMap[tx.txid] = cluster;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
cluster = [];
|
// reset working vars
|
||||||
|
clusterTxs = [];
|
||||||
ancestors = {};
|
ancestors = {};
|
||||||
}
|
}
|
||||||
cluster.push(tx);
|
clusterTxs.push(tx);
|
||||||
tx.vin.forEach(vin => {
|
tx.vin.forEach(vin => {
|
||||||
ancestors[vin.txid] = true;
|
ancestors[vin.txid] = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// forward pass to enforce ancestor rate caps
|
||||||
|
for (const tx of transactions) {
|
||||||
|
let minAncestorRate = tx.effectiveFeePerVsize;
|
||||||
|
for (const vin of tx.vin) {
|
||||||
|
if (txMap[vin.txid]?.effectiveFeePerVsize) {
|
||||||
|
minAncestorRate = Math.min(minAncestorRate, txMap[vin.txid].effectiveFeePerVsize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check rounded values to skip cases with almost identical fees
|
||||||
|
const roundedMinAncestorRate = Math.ceil(minAncestorRate);
|
||||||
|
const roundedEffectiveFeeRate = Math.floor(tx.effectiveFeePerVsize);
|
||||||
|
if (roundedMinAncestorRate < roundedEffectiveFeeRate) {
|
||||||
|
tx.effectiveFeePerVsize = minAncestorRate;
|
||||||
|
if (!clusterMap[tx.txid]) {
|
||||||
|
// add a single-tx cluster to record the dependent rate
|
||||||
|
const cluster = {
|
||||||
|
root: tx.txid,
|
||||||
|
height,
|
||||||
|
txs: [{ txid: tx.txid, weight: tx.weight, fee: tx.fee || 0 }],
|
||||||
|
effectiveFeePerVsize: minAncestorRate,
|
||||||
|
};
|
||||||
|
clusterMap[tx.txid] = cluster;
|
||||||
|
clusters.push(cluster);
|
||||||
|
} else {
|
||||||
|
// update the existing cluster with the dependent rate
|
||||||
|
clusterMap[tx.txid].effectiveFeePerVsize = minAncestorRate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
transactions,
|
transactions,
|
||||||
clusters,
|
clusters,
|
||||||
|
|
|
@ -253,9 +253,16 @@ export interface WorkingEffectiveFeeStats extends EffectiveFeeStats {
|
||||||
maxFee: number;
|
maxFee: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CpfpCluster {
|
||||||
|
root: string,
|
||||||
|
height: number,
|
||||||
|
txs: Ancestor[],
|
||||||
|
effectiveFeePerVsize: number,
|
||||||
|
}
|
||||||
|
|
||||||
export interface CpfpSummary {
|
export interface CpfpSummary {
|
||||||
transactions: TransactionExtended[];
|
transactions: TransactionExtended[];
|
||||||
clusters: { root: string, height: number, txs: Ancestor[], effectiveFeePerVsize: number }[];
|
clusters: CpfpCluster[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Statistic {
|
export interface Statistic {
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import cluster, { Cluster } from 'cluster';
|
|
||||||
import { RowDataPacket } from 'mysql2';
|
import { RowDataPacket } from 'mysql2';
|
||||||
import DB from '../database';
|
import DB from '../database';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
import { Ancestor } from '../mempool.interfaces';
|
import { Ancestor, CpfpCluster } from '../mempool.interfaces';
|
||||||
import transactionRepository from '../repositories/TransactionRepository';
|
import transactionRepository from '../repositories/TransactionRepository';
|
||||||
|
|
||||||
class CpfpRepository {
|
class CpfpRepository {
|
||||||
|
@ -12,7 +11,7 @@ class CpfpRepository {
|
||||||
}
|
}
|
||||||
// skip clusters of transactions with the same fees
|
// skip clusters of transactions with the same fees
|
||||||
const roundedEffectiveFee = Math.round(effectiveFeePerVsize * 100) / 100;
|
const roundedEffectiveFee = Math.round(effectiveFeePerVsize * 100) / 100;
|
||||||
const equalFee = txs.reduce((acc, tx) => {
|
const equalFee = txs.length > 1 && txs.reduce((acc, tx) => {
|
||||||
return (acc && Math.round(((tx.fee || 0) / (tx.weight / 4)) * 100) / 100 === roundedEffectiveFee);
|
return (acc && Math.round(((tx.fee || 0) / (tx.weight / 4)) * 100) / 100 === roundedEffectiveFee);
|
||||||
}, true);
|
}, true);
|
||||||
if (equalFee) {
|
if (equalFee) {
|
||||||
|
@ -54,9 +53,9 @@ class CpfpRepository {
|
||||||
const txs: any[] = [];
|
const txs: any[] = [];
|
||||||
|
|
||||||
for (const cluster of clusters) {
|
for (const cluster of clusters) {
|
||||||
if (cluster.txs?.length > 1) {
|
if (cluster.txs?.length) {
|
||||||
const roundedEffectiveFee = Math.round(cluster.effectiveFeePerVsize * 100) / 100;
|
const roundedEffectiveFee = Math.round(cluster.effectiveFeePerVsize * 100) / 100;
|
||||||
const equalFee = cluster.txs.reduce((acc, tx) => {
|
const equalFee = cluster.txs.length > 1 && cluster.txs.reduce((acc, tx) => {
|
||||||
return (acc && Math.round(((tx.fee || 0) / (tx.weight / 4)) * 100) / 100 === roundedEffectiveFee);
|
return (acc && Math.round(((tx.fee || 0) / (tx.weight / 4)) * 100) / 100 === roundedEffectiveFee);
|
||||||
}, true);
|
}, true);
|
||||||
if (!equalFee) {
|
if (!equalFee) {
|
||||||
|
@ -111,7 +110,7 @@ class CpfpRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async $getCluster(clusterRoot: string): Promise<Cluster | void> {
|
public async $getCluster(clusterRoot: string): Promise<CpfpCluster | void> {
|
||||||
const [clusterRows]: any = await DB.query(
|
const [clusterRows]: any = await DB.query(
|
||||||
`
|
`
|
||||||
SELECT *
|
SELECT *
|
||||||
|
@ -121,6 +120,7 @@ class CpfpRepository {
|
||||||
[clusterRoot]
|
[clusterRoot]
|
||||||
);
|
);
|
||||||
const cluster = clusterRows[0];
|
const cluster = clusterRows[0];
|
||||||
|
cluster.effectiveFeePerVsize = cluster.fee_rate;
|
||||||
if (cluster?.txs) {
|
if (cluster?.txs) {
|
||||||
cluster.txs = this.unpack(cluster.txs);
|
cluster.txs = this.unpack(cluster.txs);
|
||||||
return cluster;
|
return cluster;
|
||||||
|
|
|
@ -105,6 +105,7 @@ class TransactionRepository {
|
||||||
return {
|
return {
|
||||||
descendants,
|
descendants,
|
||||||
ancestors,
|
ancestors,
|
||||||
|
effectiveFeePerVsize: cluster.effectiveFeePerVsize,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue