mirror of
https://github.com/mempool/mempool.git
synced 2025-03-26 17:51:45 +01:00
Updates for general transaction and block fetching.
This commit is contained in:
parent
bb28a56622
commit
3c0fa71a10
@ -6,6 +6,7 @@ import { IEsploraApi } from './esplora-api.interface';
|
||||
import blocks from '../blocks';
|
||||
import bitcoinBaseApi from './bitcoin-base.api';
|
||||
import mempool from '../mempool';
|
||||
import { TransactionExtended } from '../../mempool.interfaces';
|
||||
|
||||
class BitcoinApi implements AbstractBitcoinApi {
|
||||
private rawMempoolCache: IBitcoinApi.RawMempool | null = null;
|
||||
@ -32,6 +33,11 @@ class BitcoinApi implements AbstractBitcoinApi {
|
||||
}
|
||||
|
||||
$getRawTransaction(txId: string, skipConversion = false, addPrevout = false): Promise<IEsploraApi.Transaction> {
|
||||
// If the transaction is in the mempool we also already fetched the fee, just prevouts are missing
|
||||
const txInMempool = mempool.getMempool()[txId];
|
||||
if (txInMempool && addPrevout) {
|
||||
return this.$addPrevouts(txInMempool);
|
||||
}
|
||||
return this.bitcoindClient.getRawTransaction(txId, true)
|
||||
.then((transaction: IBitcoinApi.Transaction) => {
|
||||
if (skipConversion) {
|
||||
@ -55,7 +61,12 @@ class BitcoinApi implements AbstractBitcoinApi {
|
||||
return this.bitcoindClient.getBlockHash(height);
|
||||
}
|
||||
|
||||
$getBlock(hash: string): Promise<IEsploraApi.Block> {
|
||||
async $getBlock(hash: string): Promise<IEsploraApi.Block> {
|
||||
const foundBlock = blocks.getBlocks().find((block) => block.id === hash);
|
||||
if (foundBlock) {
|
||||
return foundBlock;
|
||||
}
|
||||
|
||||
return this.bitcoindClient.getBlock(hash)
|
||||
.then((block: IBitcoinApi.Block) => this.convertBlock(block));
|
||||
}
|
||||
@ -163,6 +174,9 @@ class BitcoinApi implements AbstractBitcoinApi {
|
||||
}
|
||||
|
||||
private async $appendMempoolFeeData(transaction: IEsploraApi.Transaction): Promise<IEsploraApi.Transaction> {
|
||||
if (transaction.fee) {
|
||||
return transaction;
|
||||
}
|
||||
let mempoolEntry: IBitcoinApi.MempoolEntry;
|
||||
if (!mempool.isInSync() && !this.rawMempoolCache) {
|
||||
this.rawMempoolCache = await bitcoinBaseApi.$getRawMempoolVerbose();
|
||||
@ -176,6 +190,17 @@ class BitcoinApi implements AbstractBitcoinApi {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
protected async $addPrevouts(transaction: TransactionExtended): Promise<TransactionExtended> {
|
||||
for (const vin of transaction.vin) {
|
||||
if (vin.prevout) {
|
||||
continue;
|
||||
}
|
||||
const innerTx = await this.$getRawTransaction(vin.txid, false);
|
||||
vin.prevout = innerTx.vout[vin.vout];
|
||||
}
|
||||
return transaction;
|
||||
}
|
||||
|
||||
private async $calculateFeeFromInputs(transaction: IEsploraApi.Transaction, addPrevout: boolean): Promise<IEsploraApi.Transaction> {
|
||||
if (transaction.vin[0].is_coinbase) {
|
||||
transaction.fee = 0;
|
||||
|
@ -8,6 +8,7 @@ import * as sha256 from 'crypto-js/sha256';
|
||||
import * as hexEnc from 'crypto-js/enc-hex';
|
||||
import BitcoinApi from './bitcoin-api';
|
||||
import bitcoinBaseApi from './bitcoin-base.api';
|
||||
import mempool from '../mempool';
|
||||
class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
|
||||
private electrumClient: any;
|
||||
|
||||
@ -27,6 +28,10 @@ class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
|
||||
}
|
||||
|
||||
async $getRawTransaction(txId: string, skipConversion = false, addPrevout = false): Promise<IEsploraApi.Transaction> {
|
||||
const txInMempool = mempool.getMempool()[txId];
|
||||
if (txInMempool && addPrevout) {
|
||||
return this.$addPrevouts(txInMempool);
|
||||
}
|
||||
const transaction: IBitcoinApi.Transaction = await this.electrumClient.blockchain_transaction_get(txId, true);
|
||||
if (!transaction) {
|
||||
throw new Error('Unable to get transaction: ' + txId);
|
||||
@ -93,7 +98,7 @@ class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
|
||||
const history = await this.$getScriptHashHistory(addressInfo.scriptPubKey);
|
||||
const transactions: IEsploraApi.Transaction[] = [];
|
||||
for (const h of history) {
|
||||
const tx = await this.$getRawTransaction(h.tx_hash);
|
||||
const tx = await this.$getRawTransaction(h.tx_hash, false, true);
|
||||
if (tx) {
|
||||
transactions.push(tx);
|
||||
}
|
||||
|
@ -64,19 +64,28 @@ class Blocks {
|
||||
const txIds: string[] = await bitcoinApi.$getTxIdsForBlock(blockHash);
|
||||
|
||||
const mempool = memPool.getMempool();
|
||||
let found = 0;
|
||||
let transactionsFound = 0;
|
||||
|
||||
for (let i = 0; i < txIds.length; i++) {
|
||||
// When using bitcoind, just fetch the coinbase tx for now
|
||||
if (config.MEMPOOL.BACKEND !== 'none' && i === 0) {
|
||||
const tx = await transactionUtils.$getTransactionExtended(txIds[i]);
|
||||
if (tx) {
|
||||
transactions.push(tx);
|
||||
let txFound = false;
|
||||
let findCoinbaseTxTries = 0;
|
||||
// It takes Electrum Server a few seconds to index the transaction after a block is found
|
||||
while (findCoinbaseTxTries < 5 && !txFound) {
|
||||
const tx = await transactionUtils.$getTransactionExtended(txIds[i]);
|
||||
if (tx) {
|
||||
txFound = true;
|
||||
transactions.push(tx);
|
||||
} else {
|
||||
await Common.sleep(1000);
|
||||
findCoinbaseTxTries++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mempool[txIds[i]]) {
|
||||
transactions.push(mempool[txIds[i]]);
|
||||
found++;
|
||||
transactionsFound++;
|
||||
} else if (config.MEMPOOL.BACKEND === 'esplora') {
|
||||
logger.debug(`Fetching block tx ${i} of ${txIds.length}`);
|
||||
const tx = await transactionUtils.$getTransactionExtended(txIds[i]);
|
||||
@ -86,11 +95,11 @@ class Blocks {
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`${found} of ${txIds.length} found in mempool. ${txIds.length - found} not found.`);
|
||||
logger.debug(`${transactionsFound} of ${txIds.length} found in mempool. ${txIds.length - transactionsFound} not found.`);
|
||||
|
||||
const blockExtended: BlockExtended = Object.assign({}, block);
|
||||
blockExtended.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0);
|
||||
blockExtended.coinbaseTx = this.stripCoinbaseTransaction(transactions[0]);
|
||||
blockExtended.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]);
|
||||
transactions.sort((a, b) => b.feePerVsize - a.feePerVsize);
|
||||
blockExtended.medianFee = transactions.length > 1 ? Common.median(transactions.map((tx) => tx.feePerVsize)) : 0;
|
||||
blockExtended.feeRange = transactions.length > 1 ? Common.getFeesInRange(transactions.slice(0, transactions.length - 1), 8) : [0, 0];
|
||||
@ -118,20 +127,6 @@ class Blocks {
|
||||
public getCurrentBlockHeight(): number {
|
||||
return this.currentBlockHeight;
|
||||
}
|
||||
|
||||
private stripCoinbaseTransaction(tx: TransactionExtended): TransactionMinerInfo {
|
||||
return {
|
||||
vin: [{
|
||||
scriptsig: tx.vin[0].scriptsig || tx.vin[0]['coinbase']
|
||||
}],
|
||||
vout: tx.vout
|
||||
.map((vout) => ({
|
||||
scriptpubkey_address: vout.scriptpubkey_address,
|
||||
value: vout.value
|
||||
}))
|
||||
.filter((vout) => vout.value)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default new Blocks();
|
||||
|
@ -56,4 +56,12 @@ export class Common {
|
||||
value: tx.vout.reduce((acc, vout) => acc + (vout.value ? vout.value : 0), 0),
|
||||
};
|
||||
}
|
||||
|
||||
static sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, ms);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -6,27 +6,6 @@ import { IEsploraApi } from './bitcoin/esplora-api.interface';
|
||||
class TransactionUtils {
|
||||
constructor() { }
|
||||
|
||||
public async $addPrevoutsToTransaction(transaction: TransactionExtended): Promise<TransactionExtended> {
|
||||
if (transaction.vin[0].is_coinbase) {
|
||||
return transaction;
|
||||
}
|
||||
for (const vin of transaction.vin) {
|
||||
const innerTx = await bitcoinApi.$getRawTransaction(vin.txid);
|
||||
vin.prevout = innerTx.vout[vin.vout];
|
||||
}
|
||||
return transaction;
|
||||
}
|
||||
|
||||
public extendTransaction(transaction: IEsploraApi.Transaction): TransactionExtended {
|
||||
transaction['vsize'] = Math.round(transaction.weight / 4);
|
||||
transaction['feePerVsize'] = Math.max(1, (transaction.fee || 0) / (transaction.weight / 4));
|
||||
if (!transaction.status.confirmed) {
|
||||
transaction['firstSeen'] = Math.round((new Date().getTime() / 1000));
|
||||
}
|
||||
// @ts-ignore
|
||||
return transaction;
|
||||
}
|
||||
|
||||
public stripCoinbaseTransaction(tx: TransactionExtended): TransactionMinerInfo {
|
||||
return {
|
||||
vin: [{
|
||||
@ -41,10 +20,10 @@ class TransactionUtils {
|
||||
};
|
||||
}
|
||||
|
||||
public async $getTransactionExtended(txId: string, inMempool = false, addPrevouts = false): Promise<TransactionExtended | null> {
|
||||
public async $getTransactionExtended(txId: string, forceBitcoind = false, addPrevouts = false): Promise<TransactionExtended | null> {
|
||||
try {
|
||||
let transaction: IEsploraApi.Transaction;
|
||||
if (inMempool) {
|
||||
if (forceBitcoind) {
|
||||
transaction = await bitcoinApi.$getRawTransactionBitcoind(txId, false, addPrevouts);
|
||||
} else {
|
||||
transaction = await bitcoinApi.$getRawTransaction(txId, false, addPrevouts);
|
||||
@ -52,11 +31,20 @@ class TransactionUtils {
|
||||
return this.extendTransaction(transaction);
|
||||
} catch (e) {
|
||||
logger.debug('getTransactionExtended error: ' + (e.message || e));
|
||||
console.log(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private extendTransaction(transaction: IEsploraApi.Transaction): TransactionExtended {
|
||||
const transactionExtended: TransactionExtended = Object.assign({
|
||||
vsize: Math.round(transaction.weight / 4),
|
||||
feePerVsize: Math.max(1, (transaction.fee || 0) / (transaction.weight / 4)),
|
||||
}, transaction);
|
||||
if (!transaction.status.confirmed) {
|
||||
transactionExtended.firstSeen = Math.round((new Date().getTime() / 1000));
|
||||
}
|
||||
return transactionExtended;
|
||||
}
|
||||
}
|
||||
|
||||
export default new TransactionUtils();
|
||||
|
@ -528,13 +528,7 @@ class Routes {
|
||||
|
||||
public async getTransaction(req: Request, res: Response) {
|
||||
try {
|
||||
let transaction: TransactionExtended | null;
|
||||
const txInMempool = mempool.getMempool()[req.params.txId];
|
||||
if (txInMempool) {
|
||||
transaction = txInMempool;
|
||||
} else {
|
||||
transaction = await transactionUtils.$getTransactionExtended(req.params.txId, false, true);
|
||||
}
|
||||
const transaction = await transactionUtils.$getTransactionExtended(req.params.txId, false, true);
|
||||
|
||||
if (transaction) {
|
||||
res.json(transaction);
|
||||
@ -563,8 +557,9 @@ class Routes {
|
||||
try {
|
||||
const txIds = await bitcoinApi.$getTxIdsForBlock(req.params.hash);
|
||||
const transactions: TransactionExtended[] = [];
|
||||
const startingIndex = Math.max(0, parseInt(req.params.index, 10));
|
||||
|
||||
for (let i = 0; i < Math.min(15, txIds.length); i++) {
|
||||
for (let i = startingIndex; i < Math.min(startingIndex + 10, txIds.length); i++) {
|
||||
const transaction = await transactionUtils.$getTransactionExtended(txIds[i], false, true);
|
||||
if (transaction) {
|
||||
transactions.push(transaction);
|
||||
@ -577,7 +572,12 @@ class Routes {
|
||||
}
|
||||
|
||||
public async getBlockHeight(req: Request, res: Response) {
|
||||
res.status(404).send('Not implemented');
|
||||
try {
|
||||
const blockHash = await bitcoinApi.$getBlockHash(parseInt(req.params.height, 10));
|
||||
res.send(blockHash);
|
||||
} catch (e) {
|
||||
res.status(500).send(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async getAddress(req: Request, res: Response) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user