keep cached RBF info for 24 hours after tx leaves the mempool

This commit is contained in:
Mononaut 2022-12-09 14:35:51 -06:00
parent 0481f57304
commit d778530620
No known key found for this signature in database
GPG Key ID: A3F058E41374C04E
4 changed files with 37 additions and 37 deletions

View File

@ -595,7 +595,7 @@ class BitcoinRoutes {
private async getRbfHistory(req: Request, res: Response) { private async getRbfHistory(req: Request, res: Response) {
try { try {
const result = rbfCache.getReplaces(req.params.txId); const result = rbfCache.getReplaces(req.params.txId);
res.json(result?.txids || []); res.json(result || []);
} catch (e) { } catch (e) {
res.status(500).send(e instanceof Error ? e.message : e); res.status(500).send(e instanceof Error ? e.message : e);
} }

View File

@ -236,6 +236,7 @@ class Mempool {
const lazyDeleteAt = this.mempoolCache[tx].deleteAfter; const lazyDeleteAt = this.mempoolCache[tx].deleteAfter;
if (lazyDeleteAt && lazyDeleteAt < now) { if (lazyDeleteAt && lazyDeleteAt < now) {
delete this.mempoolCache[tx]; delete this.mempoolCache[tx];
rbfCache.evict(tx);
} }
} }
} }

View File

@ -1,46 +1,29 @@
import { TransactionExtended } from "../mempool.interfaces"; import { TransactionExtended } from "../mempool.interfaces";
export interface CachedRbf {
txid: string;
expires: Date;
}
export interface CachedRbfs {
txids: string[];
expires: Date;
}
class RbfCache { class RbfCache {
private replacedby: { [txid: string]: CachedRbf; } = {}; private replacedBy: { [txid: string]: string; } = {};
private replaces: { [txid: string]: CachedRbfs } = {}; private replaces: { [txid: string]: string[] } = {};
private txs: { [txid: string]: TransactionExtended } = {}; private txs: { [txid: string]: TransactionExtended } = {};
private expiring: { [txid: string]: Date } = {};
constructor() { constructor() {
setInterval(this.cleanup.bind(this), 1000 * 60 * 60); setInterval(this.cleanup.bind(this), 1000 * 60 * 60);
} }
public add(replacedTx: TransactionExtended, newTxId: string): void { public add(replacedTx: TransactionExtended, newTxId: string): void {
const expiry = new Date(Date.now() + 1000 * 604800); // 1 week this.replacedBy[replacedTx.txid] = newTxId;
this.replacedby[replacedTx.txid] = {
expires: expiry,
txid: newTxId,
};
this.txs[replacedTx.txid] = replacedTx; this.txs[replacedTx.txid] = replacedTx;
if (!this.replaces[newTxId]) { if (!this.replaces[newTxId]) {
this.replaces[newTxId] = { this.replaces[newTxId] = [];
txids: [],
expires: expiry,
};
} }
this.replaces[newTxId].txids.push(replacedTx.txid); this.replaces[newTxId].push(replacedTx.txid);
this.replaces[newTxId].expires = expiry;
} }
public getReplacedBy(txId: string): CachedRbf | undefined { public getReplacedBy(txId: string): string | undefined {
return this.replacedby[txId]; return this.replacedBy[txId];
} }
public getReplaces(txId: string): CachedRbfs | undefined { public getReplaces(txId: string): string[] | undefined {
return this.replaces[txId]; return this.replaces[txId];
} }
@ -48,17 +31,32 @@ class RbfCache {
return this.txs[txId]; return this.txs[txId];
} }
// flag a transaction as removed from the mempool
public evict(txid): void {
this.expiring[txid] = new Date(Date.now() + 1000 * 86400); // 24 hours
}
private cleanup(): void { private cleanup(): void {
const currentDate = new Date(); const currentDate = new Date();
for (const c in this.replacedby) { for (const txid in this.expiring) {
if (this.replacedby[c].expires < currentDate) { if (this.expiring[txid] < currentDate) {
delete this.replacedby[c]; delete this.expiring[txid];
delete this.txs[c]; this.remove(txid);
} }
} }
for (const c in this.replaces) { }
if (this.replaces[c].expires < currentDate) {
delete this.replaces[c]; // remove a transaction & all previous versions from the cache
private remove(txid): void {
// don't remove a transaction while a newer version remains in the mempool
if (this.replaces[txid] && !this.replacedBy[txid]) {
const replaces = this.replaces[txid];
delete this.replaces[txid];
for (const tx of replaces) {
// recursively remove prior versions from the cache
delete this.replacedBy[tx];
delete this.txs[tx];
this.remove(tx);
} }
} }
} }

View File

@ -58,10 +58,10 @@ class WebsocketHandler {
client['track-tx'] = parsedMessage['track-tx']; client['track-tx'] = parsedMessage['track-tx'];
// Client is telling the transaction wasn't found // Client is telling the transaction wasn't found
if (parsedMessage['watch-mempool']) { if (parsedMessage['watch-mempool']) {
const rbfCacheTx = rbfCache.getReplacedBy(client['track-tx']); const rbfCacheTxid = rbfCache.getReplacedBy(client['track-tx']);
if (rbfCacheTx) { if (rbfCacheTxid) {
response['txReplaced'] = { response['txReplaced'] = {
txid: rbfCacheTx.txid, txid: rbfCacheTxid,
}; };
client['track-tx'] = null; client['track-tx'] = null;
} else { } else {
@ -467,6 +467,7 @@ class WebsocketHandler {
for (const txId of txIds) { for (const txId of txIds) {
delete _memPool[txId]; delete _memPool[txId];
removed.push(txId); removed.push(txId);
rbfCache.evict(txId);
} }
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) { if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {