use bulk /txs endpoint to check cached rbf tx status

This commit is contained in:
Mononaut 2023-08-05 16:08:54 +09:00
parent 2339a0771e
commit 38909cfc42
No known key found for this signature in database
GPG Key ID: A3F058E41374C04E
4 changed files with 62 additions and 0 deletions

View File

@ -3,6 +3,7 @@ import { IEsploraApi } from './esplora-api.interface';
export interface AbstractBitcoinApi { export interface AbstractBitcoinApi {
$getRawMempool(): Promise<IEsploraApi.Transaction['txid'][]>; $getRawMempool(): Promise<IEsploraApi.Transaction['txid'][]>;
$getRawTransaction(txId: string, skipConversion?: boolean, addPrevout?: boolean, lazyPrevouts?: boolean): Promise<IEsploraApi.Transaction>; $getRawTransaction(txId: string, skipConversion?: boolean, addPrevout?: boolean, lazyPrevouts?: boolean): Promise<IEsploraApi.Transaction>;
$getRawTransactions(txids: string[]): Promise<IEsploraApi.Transaction[]>;
$getMempoolTransactions(txids: string[]): Promise<IEsploraApi.Transaction[]>; $getMempoolTransactions(txids: string[]): Promise<IEsploraApi.Transaction[]>;
$getAllMempoolTransactions(lastTxid: string); $getAllMempoolTransactions(lastTxid: string);
$getTransactionHex(txId: string): Promise<string>; $getTransactionHex(txId: string): Promise<string>;

View File

@ -60,6 +60,10 @@ class BitcoinApi implements AbstractBitcoinApi {
}); });
} }
$getRawTransactions(txids: string[]): Promise<IEsploraApi.Transaction[]> {
throw new Error('Method getRawTransactions not supported by the Bitcoin RPC API.');
}
$getMempoolTransactions(txids: string[]): Promise<IEsploraApi.Transaction[]> { $getMempoolTransactions(txids: string[]): Promise<IEsploraApi.Transaction[]> {
throw new Error('Method getMempoolTransactions not supported by the Bitcoin RPC API.'); throw new Error('Method getMempoolTransactions not supported by the Bitcoin RPC API.');
} }

View File

@ -213,6 +213,10 @@ class ElectrsApi implements AbstractBitcoinApi {
return this.failoverRouter.$get<IEsploraApi.Transaction>('/tx/' + txId); return this.failoverRouter.$get<IEsploraApi.Transaction>('/tx/' + txId);
} }
async $getRawTransactions(txids: string[]): Promise<IEsploraApi.Transaction[]> {
return this.$postWrapper<IEsploraApi.Transaction[]>(config.ESPLORA.REST_API_URL + '/txs', txids, 'json');
}
async $getMempoolTransactions(txids: string[]): Promise<IEsploraApi.Transaction[]> { async $getMempoolTransactions(txids: string[]): Promise<IEsploraApi.Transaction[]> {
return this.failoverRouter.$post<IEsploraApi.Transaction[]>('/internal/mempool/txs', txids, 'json'); return this.failoverRouter.$post<IEsploraApi.Transaction[]>('/internal/mempool/txs', txids, 'json');
} }

View File

@ -2,6 +2,7 @@ import config from "../config";
import logger from "../logger"; import logger from "../logger";
import { MempoolTransactionExtended, TransactionStripped } from "../mempool.interfaces"; import { MempoolTransactionExtended, TransactionStripped } from "../mempool.interfaces";
import bitcoinApi from './bitcoin/bitcoin-api-factory'; import bitcoinApi from './bitcoin/bitcoin-api-factory';
import { IEsploraApi } from "./bitcoin/esplora-api.interface";
import { Common } from "./common"; import { Common } from "./common";
import redisCache from "./redis-cache"; import redisCache from "./redis-cache";
@ -383,6 +384,7 @@ class RbfCache {
}); });
logger.debug(`loaded ${txs.length} txs, ${trees.length} trees into rbf cache, ${expiring.length} due to expire, ${this.staleCount} were stale`); 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.staleCount = 0;
await this.checkTrees();
this.cleanup(); this.cleanup();
} catch (e) { } catch (e) {
logger.err('failed to restore RBF cache: ' + (e instanceof Error ? e.message : e)); logger.err('failed to restore RBF cache: ' + (e instanceof Error ? e.message : e));
@ -481,6 +483,57 @@ class RbfCache {
return tree; return tree;
} }
private async checkTrees(): Promise<void> {
const found: { [txid: string]: boolean } = {};
const txids = Array.from(this.txs.values()).map(tx => tx.txid).filter(txid => {
return !this.expiring.has(txid) && !this.getRbfTree(txid)?.mined;
});
const processTxs = (txs: IEsploraApi.Transaction[]): void => {
for (const tx of txs) {
found[tx.txid] = true;
if (tx.status?.confirmed) {
const tree = this.getRbfTree(tx.txid);
if (tree) {
this.setTreeMined(tree, tx.txid);
tree.mined = true;
this.evict(tx.txid, false);
}
}
}
};
if (config.MEMPOOL.BACKEND === 'esplora') {
const sliceLength = 10000;
for (let i = 0; i < Math.ceil(txids.length / sliceLength); i++) {
const slice = txids.slice(i * sliceLength, (i + 1) * sliceLength);
try {
const txs = await bitcoinApi.$getRawTransactions(slice);
processTxs(txs);
} catch (err) {
logger.err('failed to fetch some cached rbf transactions');
}
}
} else {
const txs: IEsploraApi.Transaction[] = [];
for (const txid of txids) {
try {
const tx = await bitcoinApi.$getRawTransaction(txid, true, false);
txs.push(tx);
} catch (err) {
// some 404s are expected, so continue quietly
}
}
processTxs(txs);
}
for (const txid of txids) {
if (!found[txid]) {
this.evict(txid, false);
}
}
}
public getLatestRbfSummary(): ReplacementInfo[] { public getLatestRbfSummary(): ReplacementInfo[] {
const rbfList = this.getRbfTrees(false); const rbfList = this.getRbfTrees(false);
return rbfList.slice(0, 6).map(rbfTree => { return rbfList.slice(0, 6).map(rbfTree => {