diff --git a/backend/src/api/disk-cache.ts b/backend/src/api/disk-cache.ts index 6f603489a..093f07f0d 100644 --- a/backend/src/api/disk-cache.ts +++ b/backend/src/api/disk-cache.ts @@ -252,7 +252,11 @@ class DiskCache { } if (rbfData?.rbf) { - rbfCache.load(rbfData.rbf); + rbfCache.load({ + txs: rbfData.rbf.txs.map(([txid, entry]) => ({ value: entry })), + trees: rbfData.rbf.trees, + expiring: rbfData.rbf.expiring.map(([txid, value]) => ({ key: txid, value })), + }); } } catch (e) { logger.warn('Failed to parse rbf cache. Skipping. Reason: ' + (e instanceof Error ? e.message : e)); diff --git a/backend/src/api/rbf-cache.ts b/backend/src/api/rbf-cache.ts index b5592252c..6e1f37afb 100644 --- a/backend/src/api/rbf-cache.ts +++ b/backend/src/api/rbf-cache.ts @@ -53,6 +53,9 @@ class RbfCache { private expiring: Map = new Map(); private cacheQueue: CacheEvent[] = []; + private evictionCount = 0; + private staleCount = 0; + constructor() { setInterval(this.cleanup.bind(this), 1000 * 60 * 10); } @@ -245,6 +248,7 @@ class RbfCache { // flag a transaction as removed from the mempool public evict(txid: string, fast: boolean = false): void { + this.evictionCount++; if (this.txs.has(txid) && (fast || !this.expiring.has(txid))) { const expiryTime = fast ? Date.now() + (1000 * 60 * 10) : Date.now() + (1000 * 86400); // 24 hours this.addExpiration(txid, expiryTime); @@ -272,18 +276,23 @@ class RbfCache { this.remove(txid); } } - logger.debug(`rbf cache contains ${this.txs.size} txs, ${this.rbfTrees.size} trees, ${this.expiring.size} due to expire`); + logger.debug(`rbf cache contains ${this.txs.size} txs, ${this.rbfTrees.size} trees, ${this.expiring.size} due to expire (${this.evictionCount} newly expired)`); + this.evictionCount = 0; } // remove a transaction & all previous versions from the cache private remove(txid): void { // don't remove a transaction if a newer version remains in the mempool if (!this.replacedBy.has(txid)) { + const root = this.treeMap.get(txid); const replaces = this.replaces.get(txid); this.replaces.delete(txid); this.treeMap.delete(txid); this.removeTx(txid); this.removeExpiration(txid); + if (root === txid) { + this.removeTree(txid); + } for (const tx of (replaces || [])) { // recursively remove prior versions from the cache this.replacedBy.delete(tx); @@ -359,18 +368,25 @@ class RbfCache { } public async load({ txs, trees, expiring }): Promise { - txs.forEach(txEntry => { - this.txs.set(txEntry.key, txEntry.value); - }); - for (const deflatedTree of trees) { - await this.importTree(deflatedTree.root, deflatedTree.root, deflatedTree, this.txs); - } - expiring.forEach(expiringEntry => { - if (this.txs.has(expiringEntry.key)) { - this.expiring.set(expiringEntry.key, new Date(expiringEntry.value).getTime()); + try { + txs.forEach(txEntry => { + this.txs.set(txEntry.value.txid, txEntry.value); + }); + this.staleCount = 0; + for (const deflatedTree of trees) { + await this.importTree(deflatedTree.root, deflatedTree.root, deflatedTree, this.txs); } - }); - this.cleanup(); + expiring.forEach(expiringEntry => { + if (this.txs.has(expiringEntry.key)) { + this.expiring.set(expiringEntry.key, new Date(expiringEntry.value).getTime()); + } + }); + logger.debug(`loaded ${txs.length} txs, ${trees.length} trees into rbf cache, ${expiring.length} due to expire, ${this.staleCount} were stale`); + this.staleCount = 0; + this.cleanup(); + } catch (e) { + logger.err('failed to restore RBF cache: ' + (e instanceof Error ? e.message : e)); + } } exportTree(tree: RbfTree, deflated: any = null) { @@ -398,6 +414,13 @@ class RbfCache { const treeInfo = deflated[txid]; const replaces: RbfTree[] = []; + // if the root tx is unknown, remove this tree and return early + if (root === txid && !txs.has(txid)) { + this.staleCount++; + this.removeTree(deflated.key); + return; + } + // check if any transactions in this tree have already been confirmed mined = mined || treeInfo.mined; let exists = mined; @@ -413,7 +436,7 @@ class RbfCache { this.evict(txid, true); } } catch (e) { - // most transactions do not exist + // most transactions only exist in our cache } } diff --git a/backend/src/api/redis-cache.ts b/backend/src/api/redis-cache.ts index fcde8013a..00b280274 100644 --- a/backend/src/api/redis-cache.ts +++ b/backend/src/api/redis-cache.ts @@ -219,7 +219,7 @@ class RedisCache { await memPool.$setMempool(loadedMempool); await rbfCache.load({ txs: rbfTxs, - trees: rbfTrees.map(loadedTree => loadedTree.value), + trees: rbfTrees.map(loadedTree => { loadedTree.value.key = loadedTree.key; return loadedTree.value; }), expiring: rbfExpirations, }); }