Fetch block txs from mempool/electrs in bulk

This commit is contained in:
Mononaut 2023-07-24 16:58:30 +09:00
parent 81d1c0a4d5
commit 0ebfd6f017
No known key found for this signature in database
GPG key ID: A3F058E41374C04E
5 changed files with 42 additions and 35 deletions

View file

@ -8,6 +8,7 @@ export interface AbstractBitcoinApi {
$getBlockHeightTip(): Promise<number>; $getBlockHeightTip(): Promise<number>;
$getBlockHashTip(): Promise<string>; $getBlockHashTip(): Promise<string>;
$getTxIdsForBlock(hash: string): Promise<string[]>; $getTxIdsForBlock(hash: string): Promise<string[]>;
$getTxsForBlock(hash: string): Promise<IEsploraApi.Transaction[]>;
$getBlockHash(height: number): Promise<string>; $getBlockHash(height: number): Promise<string>;
$getBlockHeader(hash: string): Promise<string>; $getBlockHeader(hash: string): Promise<string>;
$getBlock(hash: string): Promise<IEsploraApi.Block>; $getBlock(hash: string): Promise<IEsploraApi.Block>;

View file

@ -81,6 +81,10 @@ class BitcoinApi implements AbstractBitcoinApi {
.then((rpcBlock: IBitcoinApi.Block) => rpcBlock.tx); .then((rpcBlock: IBitcoinApi.Block) => rpcBlock.tx);
} }
$getTxsForBlock(hash: string): Promise<IEsploraApi.Transaction[]> {
throw new Error('Method getTxsForBlock not supported by the Bitcoin RPC API.');
}
$getRawBlock(hash: string): Promise<Buffer> { $getRawBlock(hash: string): Promise<Buffer> {
return this.bitcoindClient.getBlock(hash, 0) return this.bitcoindClient.getBlock(hash, 0)
.then((raw: string) => Buffer.from(raw, "hex")); .then((raw: string) => Buffer.from(raw, "hex"));

View file

@ -89,6 +89,10 @@ class ElectrsApi implements AbstractBitcoinApi {
return this.$queryWrapper<string[]>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/txids'); return this.$queryWrapper<string[]>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/txids');
} }
$getTxsForBlock(hash: string): Promise<IEsploraApi.Transaction[]> {
return this.$queryWrapper<IEsploraApi.Transaction[]>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/txs');
}
$getBlockHash(height: number): Promise<string> { $getBlockHash(height: number): Promise<string> {
return this.$queryWrapper<string>(config.ESPLORA.REST_API_URL + '/block-height/' + height); return this.$queryWrapper<string>(config.ESPLORA.REST_API_URL + '/block-height/' + height);
} }

View file

@ -80,22 +80,30 @@ class Blocks {
quiet: boolean = false, quiet: boolean = false,
addMempoolData: boolean = false, addMempoolData: boolean = false,
): Promise<TransactionExtended[]> { ): Promise<TransactionExtended[]> {
const transactions: TransactionExtended[] = []; let transactions: TransactionExtended[] = [];
if (!txIds) {
txIds = await bitcoinApi.$getTxIdsForBlock(blockHash);
}
const mempool = memPool.getMempool(); const mempool = memPool.getMempool();
let transactionsFound = 0; let transactionsFound = 0;
let transactionsFetched = 0; let transactionsFetched = 0;
if (config.MEMPOOL.BACKEND === 'esplora') {
const rawTransactions = await bitcoinApi.$getTxsForBlock(blockHash);
transactions = rawTransactions.map(tx => transactionUtils.extendTransaction(tx));
if (!quiet) {
logger.debug(`${transactions.length} fetched through backend service.`);
}
} else {
if (!txIds) {
txIds = await bitcoinApi.$getTxIdsForBlock(blockHash);
}
for (let i = 0; i < txIds.length; i++) { for (let i = 0; i < txIds.length; i++) {
if (mempool[txIds[i]]) { if (mempool[txIds[i]]) {
// We update blocks before the mempool (index.ts), therefore we can // We update blocks before the mempool (index.ts), therefore we can
// optimize here by directly fetching txs in the "outdated" mempool // optimize here by directly fetching txs in the "outdated" mempool
transactions.push(mempool[txIds[i]]); transactions.push(mempool[txIds[i]]);
transactionsFound++; transactionsFound++;
} else if (config.MEMPOOL.BACKEND === 'esplora' || !memPool.hasPriority() || i === 0) { } else if (!memPool.hasPriority() || i === 0) {
// Otherwise we fetch the tx data through backend services (esplora, electrum, core rpc...) // Otherwise we fetch the tx data through backend services (esplora, electrum, core rpc...)
if (!quiet && (i % (Math.round((txIds.length) / 10)) === 0 || i + 1 === txIds.length)) { // Avoid log spam if (!quiet && (i % (Math.round((txIds.length) / 10)) === 0 || i + 1 === txIds.length)) { // Avoid log spam
logger.debug(`Indexing tx ${i + 1} of ${txIds.length} in block #${blockHeight}`); logger.debug(`Indexing tx ${i + 1} of ${txIds.length} in block #${blockHeight}`);
@ -104,16 +112,6 @@ class Blocks {
const tx = await transactionUtils.$getTransactionExtended(txIds[i], false, false, false, addMempoolData); const tx = await transactionUtils.$getTransactionExtended(txIds[i], false, false, false, addMempoolData);
transactions.push(tx); transactions.push(tx);
transactionsFetched++; transactionsFetched++;
} catch (e) {
try {
if (config.MEMPOOL.BACKEND === 'esplora') {
// Try again with core
const tx = await transactionUtils.$getTransactionExtended(txIds[i], false, false, true, addMempoolData);
transactions.push(tx);
transactionsFetched++;
} else {
throw e;
}
} catch (e) { } catch (e) {
if (i === 0) { if (i === 0) {
const msg = `Cannot fetch coinbase tx ${txIds[i]}. Reason: ` + (e instanceof Error ? e.message : e); const msg = `Cannot fetch coinbase tx ${txIds[i]}. Reason: ` + (e instanceof Error ? e.message : e);
@ -124,7 +122,6 @@ class Blocks {
} }
} }
} }
}
if (onlyCoinbase === true) { if (onlyCoinbase === true) {
break; // Fetch the first transaction and exit break; // Fetch the first transaction and exit
@ -134,6 +131,7 @@ class Blocks {
if (!quiet) { if (!quiet) {
logger.debug(`${transactionsFound} of ${txIds.length} found in mempool. ${transactionsFetched} fetched through backend service.`); logger.debug(`${transactionsFound} of ${txIds.length} found in mempool. ${transactionsFetched} fetched through backend service.`);
} }
}
return transactions; return transactions;
} }

View file

@ -53,7 +53,7 @@ class TransactionUtils {
return (await this.$getTransactionExtended(txId, addPrevouts, lazyPrevouts, forceCore, true)) as MempoolTransactionExtended; return (await this.$getTransactionExtended(txId, addPrevouts, lazyPrevouts, forceCore, true)) as MempoolTransactionExtended;
} }
private extendTransaction(transaction: IEsploraApi.Transaction): TransactionExtended { public extendTransaction(transaction: IEsploraApi.Transaction): TransactionExtended {
// @ts-ignore // @ts-ignore
if (transaction.vsize) { if (transaction.vsize) {
// @ts-ignore // @ts-ignore