diff --git a/backend/src/api/rbf-cache.ts b/backend/src/api/rbf-cache.ts index 7f910e0f7..d75fb0ba8 100644 --- a/backend/src/api/rbf-cache.ts +++ b/backend/src/api/rbf-cache.ts @@ -1,3 +1,4 @@ +import logger from "../logger"; import { TransactionExtended, TransactionStripped } from "../mempool.interfaces"; import bitcoinApi from './bitcoin/bitcoin-api-factory'; import { Common } from "./common"; @@ -23,10 +24,10 @@ class RbfCache { private dirtyTrees: Set = new Set(); private treeMap: Map = new Map(); // map of txids to sequence ids private txs: Map = new Map(); - private expiring: Map = new Map(); + private expiring: Map = new Map(); constructor() { - setInterval(this.cleanup.bind(this), 1000 * 60 * 60); + setInterval(this.cleanup.bind(this), 1000 * 60 * 10); } public add(replaced: TransactionExtended[], newTxExtended: TransactionExtended): void { @@ -146,6 +147,9 @@ class RbfCache { } public mined(txid): void { + if (!this.txs.has(txid)) { + return; + } const treeId = this.treeMap.get(txid); if (treeId && this.rbfTrees.has(treeId)) { const tree = this.rbfTrees.get(treeId); @@ -159,18 +163,21 @@ class RbfCache { } // flag a transaction as removed from the mempool - public evict(txid): void { - this.expiring.set(txid, new Date(Date.now() + 1000 * 86400)); // 24 hours + public evict(txid, fast: boolean = false): void { + if (this.txs.has(txid) && (fast || !this.expiring.has(txid))) { + this.expiring.set(txid, fast ? Date.now() + (1000 * 60 * 10) : Date.now() + (1000 * 86400)); // 24 hours + } } private cleanup(): void { - const currentDate = new Date(); - for (const txid in this.expiring) { - if ((this.expiring.get(txid) || 0) < currentDate) { + const now = Date.now(); + for (const txid of this.expiring.keys()) { + if ((this.expiring.get(txid) || 0) < now) { this.expiring.delete(txid); this.remove(txid); } } + logger.debug(`rbf cache contains ${this.txs.size} txs, ${this.expiring.size} due to expire`); } // remove a transaction & all previous versions from the cache @@ -237,7 +244,9 @@ class RbfCache { await this.importTree(deflatedTree.root, deflatedTree.root, deflatedTree, this.txs); } expiring.forEach(expiringEntry => { - this.expiring.set(expiringEntry[0], expiringEntry[1]); + if (this.txs.has(expiringEntry[0])) { + this.expiring.set(expiringEntry[0], new Date(expiringEntry[1]).getTime()); + } }); this.cleanup(); } @@ -269,18 +278,29 @@ class RbfCache { // check if any transactions in this tree have already been confirmed mined = mined || treeInfo.mined; + let exists = mined; if (!mined) { try { const apiTx = await bitcoinApi.$getRawTransaction(txid); + if (apiTx) { + exists = true; + } if (apiTx?.status?.confirmed) { mined = true; - this.evict(txid); + treeInfo.txMined = true; + this.evict(txid, true); } } catch (e) { // most transactions do not exist } } + // if the root tx is not in the mempool or the blockchain + // evict this tree as soon as possible + if (root === txid && !exists) { + this.evict(txid, true); + } + // recursively reconstruct child trees for (const childId of treeInfo.replaces) { const replaced = await this.importTree(root, childId, deflated, txs, mined);