Compute decoded tx CPFP data in the backend
This commit is contained in:
@@ -12,14 +12,14 @@ import backendInfo from '../backend-info';
|
||||
import transactionUtils from '../transaction-utils';
|
||||
import { IEsploraApi } from './esplora-api.interface';
|
||||
import loadingIndicators from '../loading-indicators';
|
||||
import { MempoolTransactionExtended, TransactionExtended } from '../../mempool.interfaces';
|
||||
import { TransactionExtended } from '../../mempool.interfaces';
|
||||
import logger from '../../logger';
|
||||
import blocks from '../blocks';
|
||||
import bitcoinClient from './bitcoin-client';
|
||||
import difficultyAdjustment from '../difficulty-adjustment';
|
||||
import transactionRepository from '../../repositories/TransactionRepository';
|
||||
import rbfCache from '../rbf-cache';
|
||||
import { calculateMempoolTxCpfp } from '../cpfp';
|
||||
import { calculateMempoolTxCpfp, calculateLocalTxCpfp } from '../cpfp';
|
||||
import { handleError } from '../../utils/api';
|
||||
|
||||
class BitcoinRoutes {
|
||||
@@ -50,6 +50,7 @@ class BitcoinRoutes {
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks-bulk/:from', this.getBlocksByBulk.bind(this))
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks-bulk/:from/:to', this.getBlocksByBulk.bind(this))
|
||||
.post(config.MEMPOOL.API_URL_PREFIX + 'prevouts', this.$getPrevouts)
|
||||
.post(config.MEMPOOL.API_URL_PREFIX + 'cpfp', this.getCpfpLocalTx)
|
||||
// Temporarily add txs/package endpoint for all backends until esplora supports it
|
||||
.post(config.MEMPOOL.API_URL_PREFIX + 'txs/package', this.$submitPackage)
|
||||
;
|
||||
@@ -829,11 +830,11 @@ class BitcoinRoutes {
|
||||
try {
|
||||
const outpoints = req.body;
|
||||
if (!Array.isArray(outpoints) || outpoints.some((item) => !/^[a-fA-F0-9]{64}$/.test(item.txid) || typeof item.vout !== 'number')) {
|
||||
return res.status(400).json({ error: 'Invalid input format' });
|
||||
return res.status(400).json({ message: 'Invalid input format' });
|
||||
}
|
||||
|
||||
if (outpoints.length > 100) {
|
||||
return res.status(400).json({ error: 'Too many prevouts requested' });
|
||||
return res.status(400).json({ message: 'Too many prevouts requested' });
|
||||
}
|
||||
|
||||
const result = Array(outpoints.length).fill(null);
|
||||
@@ -842,12 +843,14 @@ class BitcoinRoutes {
|
||||
for (let i = 0; i < outpoints.length; i++) {
|
||||
const outpoint = outpoints[i];
|
||||
let prevout: IEsploraApi.Vout | null = null;
|
||||
let tx: MempoolTransactionExtended | null = null;
|
||||
let unconfirmed: boolean | null = null;
|
||||
|
||||
const mempoolTx = memPool[outpoint.txid];
|
||||
if (mempoolTx) {
|
||||
prevout = mempoolTx.vout[outpoint.vout];
|
||||
tx = mempoolTx;
|
||||
if (outpoint.vout < mempoolTx.vout.length) {
|
||||
prevout = mempoolTx.vout[outpoint.vout];
|
||||
unconfirmed = true;
|
||||
}
|
||||
} else {
|
||||
const rawPrevout = await bitcoinClient.getTxOut(outpoint.txid, outpoint.vout, false);
|
||||
if (rawPrevout) {
|
||||
@@ -858,11 +861,12 @@ class BitcoinRoutes {
|
||||
scriptpubkey_type: transactionUtils.translateScriptPubKeyType(rawPrevout.scriptPubKey.type),
|
||||
scriptpubkey_address: rawPrevout.scriptPubKey && rawPrevout.scriptPubKey.address ? rawPrevout.scriptPubKey.address : '',
|
||||
};
|
||||
unconfirmed = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (prevout) {
|
||||
result[i] = { prevout, tx };
|
||||
result[i] = { prevout, unconfirmed };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -872,6 +876,30 @@ class BitcoinRoutes {
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
private getCpfpLocalTx(req: Request, res: Response) {
|
||||
try {
|
||||
const tx = req.body;
|
||||
|
||||
if (
|
||||
!tx || typeof tx !== "object" ||
|
||||
!tx.txid || typeof tx.txid !== "string" ||
|
||||
typeof tx.weight !== "number" ||
|
||||
typeof tx.sigops !== "number" ||
|
||||
typeof tx.fee !== "number" ||
|
||||
!Array.isArray(tx.vin) ||
|
||||
!Array.isArray(tx.vout)
|
||||
) {
|
||||
return res.status(400).json({ message: 'Invalid transaction format: missing or incorrect fields' });
|
||||
}
|
||||
|
||||
const cpfpInfo = calculateLocalTxCpfp(tx, mempool.getMempool());
|
||||
res.json(cpfpInfo);
|
||||
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new BitcoinRoutes();
|
||||
|
||||
@@ -222,6 +222,34 @@ export function calculateMempoolTxCpfp(tx: MempoolTransactionExtended, mempool:
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Takes an unbroadcasted transaction and a copy of the current mempool, and calculates an estimate
|
||||
* of the CPFP data if the transaction were to enter the mempool. This only returns potential ancerstors
|
||||
* and effective fee rate, and does not update the CPFP data of other transactions in the cluster.
|
||||
*/
|
||||
export function calculateLocalTxCpfp(tx: MempoolTransactionExtended, mempool: { [txid: string]: MempoolTransactionExtended }): CpfpInfo {
|
||||
const ancestorMap = new Map<string, GraphTx>();
|
||||
const graphTx = convertToGraphTx(tx, memPool.getSpendMap());
|
||||
ancestorMap.set(tx.txid, graphTx);
|
||||
|
||||
const allRelatives = expandRelativesGraph(mempool, ancestorMap, memPool.getSpendMap());
|
||||
const relativesMap = initializeRelatives(allRelatives);
|
||||
const cluster = calculateCpfpCluster(tx.txid, relativesMap);
|
||||
|
||||
let totalVsize = 0;
|
||||
let totalFee = 0;
|
||||
for (const tx of cluster.values()) {
|
||||
totalVsize += tx.vsize;
|
||||
totalFee += tx.fees.base;
|
||||
}
|
||||
|
||||
return {
|
||||
ancestors: Array.from(cluster.get(tx.txid)?.ancestors.values() || []).map(ancestor => ({ txid: ancestor.txid, weight: ancestor.weight, fee: ancestor.fees.base })),
|
||||
effectiveFeePerVsize: totalFee / totalVsize
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a root transaction and a list of in-mempool ancestors,
|
||||
* Calculate the CPFP cluster
|
||||
|
||||
Reference in New Issue
Block a user