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