From 3ec676ca9025b5e7009dbc47578b9477a7d9b7d5 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 4 Aug 2023 19:11:49 +0900 Subject: [PATCH] Speed up RBF detection --- backend/src/api/common.ts | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index 64bac2036..83ba48c06 100644 --- a/backend/src/api/common.ts +++ b/backend/src/api/common.ts @@ -61,8 +61,10 @@ export class Common { static findRbfTransactions(added: MempoolTransactionExtended[], deleted: MempoolTransactionExtended[]): { [txid: string]: MempoolTransactionExtended[] } { const matches: { [txid: string]: MempoolTransactionExtended[] } = {}; - added - .forEach((addedTx) => { + + // For small N, a naive nested loop is extremely fast, but it doesn't scale + if (added.length < 1000 && deleted.length < 50) { + added.forEach((addedTx) => { const foundMatches = deleted.filter((deletedTx) => { // The new tx must, absolutely speaking, pay at least as much fee as the replaced tx. return addedTx.fee > deletedTx.fee @@ -73,9 +75,40 @@ export class Common { addedTx.vin.some((vin) => vin.txid === deletedVin.txid && vin.vout === deletedVin.vout)); }); if (foundMatches?.length) { - matches[addedTx.txid] = foundMatches; + matches[addedTx.txid] = [...new Set(foundMatches)]; } }); + } else { + // for large N, build a lookup table of prevouts we can check in ~constant time + const deletedSpendMap: { [txid: string]: { [vout: number]: MempoolTransactionExtended } } = {}; + for (const tx of deleted) { + for (const vin of tx.vin) { + if (!deletedSpendMap[vin.txid]) { + deletedSpendMap[vin.txid] = {}; + } + deletedSpendMap[vin.txid][vin.vout] = tx; + } + } + + for (const addedTx of added) { + const foundMatches = new Set(); + for (const vin of addedTx.vin) { + const deletedTx = deletedSpendMap[vin.txid]?.[vin.vout]; + if (deletedTx && deletedTx.txid !== addedTx.txid + // The new tx must, absolutely speaking, pay at least as much fee as the replaced tx. + && addedTx.fee > deletedTx.fee + // The new transaction must pay more fee per kB than the replaced tx. + && addedTx.adjustedFeePerVsize > deletedTx.adjustedFeePerVsize + ) { + foundMatches.add(deletedTx); + } + if (foundMatches.size) { + matches[addedTx.txid] = [...foundMatches]; + } + } + } + } + return matches; }