Add API endpoint for block summary data
This commit is contained in:
		
							parent
							
								
									2d529bd581
								
							
						
					
					
						commit
						288bddcaf2
					
				@ -73,6 +73,14 @@ export namespace IBitcoinApi {
 | 
			
		||||
    time: number;                    //  (numeric) Same as blocktime
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export interface VerboseBlock extends Block {
 | 
			
		||||
    tx: VerboseTransaction[];        // The transactions in the format of the getrawtransaction RPC. Different from verbosity = 1 "tx" result
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export interface VerboseTransaction extends Transaction {
 | 
			
		||||
    fee?: number;                   //  (numeric) The transaction fee in BTC, omitted if block undo data is not available
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  export interface Vin {
 | 
			
		||||
    txid?: string;                   //  (string) The transaction id
 | 
			
		||||
    vout?: number;                   //  (string)
 | 
			
		||||
 | 
			
		||||
@ -2,11 +2,12 @@ import config from '../config';
 | 
			
		||||
import bitcoinApi from './bitcoin/bitcoin-api-factory';
 | 
			
		||||
import logger from '../logger';
 | 
			
		||||
import memPool from './mempool';
 | 
			
		||||
import { BlockExtended, PoolTag, TransactionExtended, TransactionMinerInfo } from '../mempool.interfaces';
 | 
			
		||||
import { BlockExtended, BlockSummary, PoolTag, TransactionExtended, TransactionStripped, TransactionMinerInfo } from '../mempool.interfaces';
 | 
			
		||||
import { Common } from './common';
 | 
			
		||||
import diskCache from './disk-cache';
 | 
			
		||||
import transactionUtils from './transaction-utils';
 | 
			
		||||
import bitcoinClient from './bitcoin/bitcoin-client';
 | 
			
		||||
import { IBitcoinApi } from './bitcoin/bitcoin-api.interface';
 | 
			
		||||
import { IEsploraApi } from './bitcoin/esplora-api.interface';
 | 
			
		||||
import poolsRepository from '../repositories/PoolsRepository';
 | 
			
		||||
import blocksRepository from '../repositories/BlocksRepository';
 | 
			
		||||
@ -22,6 +23,7 @@ import poolsParser from './pools-parser';
 | 
			
		||||
 | 
			
		||||
class Blocks {
 | 
			
		||||
  private blocks: BlockExtended[] = [];
 | 
			
		||||
  private blockSummaries: BlockSummary[] = [];
 | 
			
		||||
  private currentBlockHeight = 0;
 | 
			
		||||
  private currentDifficulty = 0;
 | 
			
		||||
  private lastDifficultyAdjustmentTime = 0;
 | 
			
		||||
@ -38,6 +40,14 @@ class Blocks {
 | 
			
		||||
    this.blocks = blocks;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public getBlockSummaries(): BlockSummary[] {
 | 
			
		||||
    return this.blockSummaries;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public setBlockSummaries(blockSummaries: BlockSummary[]) {
 | 
			
		||||
    this.blockSummaries = blockSummaries;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public setNewBlockCallback(fn: (block: BlockExtended, txIds: string[], transactions: TransactionExtended[]) => void) {
 | 
			
		||||
    this.newBlockCallbacks.push(fn);
 | 
			
		||||
  }
 | 
			
		||||
@ -106,6 +116,27 @@ class Blocks {
 | 
			
		||||
    return transactions;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Return a block summary (list of stripped transactions)
 | 
			
		||||
   * @param block
 | 
			
		||||
   * @returns BlockSummary
 | 
			
		||||
   */
 | 
			
		||||
  private summarizeBlock(block: IBitcoinApi.VerboseBlock): BlockSummary {
 | 
			
		||||
    const stripped = block.tx.map((tx) => {
 | 
			
		||||
      return {
 | 
			
		||||
        txid: tx.txid,
 | 
			
		||||
        vsize: tx.vsize,
 | 
			
		||||
        fee: tx.fee ? Math.round(tx.fee * 100000000) : 0,
 | 
			
		||||
        value: Math.round(tx.vout.reduce((acc, vout) => acc + (vout.value ? vout.value : 0), 0) * 100000000)
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      id: block.hash,
 | 
			
		||||
      transactions: stripped
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Return a block with additional data (reward, coinbase, fees...)
 | 
			
		||||
   * @param block
 | 
			
		||||
@ -341,10 +372,12 @@ class Blocks {
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const blockHash = await bitcoinApi.$getBlockHash(this.currentBlockHeight);
 | 
			
		||||
      const block = BitcoinApi.convertBlock(await bitcoinClient.getBlock(blockHash));
 | 
			
		||||
      const verboseBlock = await bitcoinClient.getBlock(blockHash, 2);
 | 
			
		||||
      const block = BitcoinApi.convertBlock(verboseBlock);
 | 
			
		||||
      const txIds: string[] = await bitcoinApi.$getTxIdsForBlock(blockHash);
 | 
			
		||||
      const transactions = await this.$getTransactionsExtended(blockHash, block.height, false);
 | 
			
		||||
      const blockExtended: BlockExtended = await this.$getBlockExtended(block, transactions);
 | 
			
		||||
      const blockSummary: BlockSummary = this.summarizeBlock(verboseBlock);
 | 
			
		||||
 | 
			
		||||
      if (Common.indexingEnabled()) {
 | 
			
		||||
        if (!fastForwarded) {
 | 
			
		||||
@ -375,6 +408,10 @@ class Blocks {
 | 
			
		||||
      if (this.blocks.length > config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4) {
 | 
			
		||||
        this.blocks = this.blocks.slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4);
 | 
			
		||||
      }
 | 
			
		||||
      this.blockSummaries.push(blockSummary);
 | 
			
		||||
      if (this.blockSummaries.length > config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4) {
 | 
			
		||||
        this.blockSummaries = this.blockSummaries.slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 4);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (this.newBlockCallbacks.length) {
 | 
			
		||||
        this.newBlockCallbacks.forEach((cb) => cb(blockExtended, txIds, transactions));
 | 
			
		||||
@ -440,6 +477,17 @@ class Blocks {
 | 
			
		||||
    return blockExtended;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $getStrippedBlockTransactions(hash: string): Promise<TransactionStripped[]> {
 | 
			
		||||
    // Check the memory cache
 | 
			
		||||
    const cachedSummary = this.getBlockSummaries().find((b) => b.id === hash);
 | 
			
		||||
    if (cachedSummary) {
 | 
			
		||||
      return cachedSummary.transactions;
 | 
			
		||||
    }
 | 
			
		||||
    const block = await bitcoinClient.getBlock(hash, 2);
 | 
			
		||||
    const summary = this.summarizeBlock(block);
 | 
			
		||||
    return summary.transactions;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $getBlocks(fromHeight?: number, limit: number = 15): Promise<BlockExtended[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      let currentHeight = fromHeight !== undefined ? fromHeight : this.getCurrentBlockHeight();
 | 
			
		||||
 | 
			
		||||
@ -43,6 +43,7 @@ class DiskCache {
 | 
			
		||||
      await fsPromises.writeFile(DiskCache.FILE_NAME, JSON.stringify({
 | 
			
		||||
        cacheSchemaVersion: this.cacheSchemaVersion,
 | 
			
		||||
        blocks: blocks.getBlocks(),
 | 
			
		||||
        blockSummaries: blocks.getBlockSummaries(),
 | 
			
		||||
        mempool: {},
 | 
			
		||||
        mempoolArray: mempoolArray.splice(0, chunkSize),
 | 
			
		||||
      }), {flag: 'w'});
 | 
			
		||||
@ -109,6 +110,7 @@ class DiskCache {
 | 
			
		||||
 | 
			
		||||
      memPool.setMempool(data.mempool);
 | 
			
		||||
      blocks.setBlocks(data.blocks);
 | 
			
		||||
      blocks.setBlockSummaries(data.blockSummaries || []);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.warn('Failed to parse mempoool and blocks cache. Skipping. Reason: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -314,7 +314,8 @@ class Server {
 | 
			
		||||
    this.app
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'blocks', routes.getBlocks.bind(routes))
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'blocks/:height', routes.getBlocks.bind(routes))
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash', routes.getBlock);
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash', routes.getBlock)
 | 
			
		||||
      .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/summary', routes.getStrippedBlockTransactions);
 | 
			
		||||
 | 
			
		||||
    if (config.MEMPOOL.BACKEND !== 'esplora') {
 | 
			
		||||
      this.app
 | 
			
		||||
 | 
			
		||||
@ -106,6 +106,11 @@ export interface BlockExtended extends IEsploraApi.Block {
 | 
			
		||||
  extras: BlockExtension;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface BlockSummary {
 | 
			
		||||
  id: string;
 | 
			
		||||
  transactions: TransactionStripped[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface TransactionMinerInfo {
 | 
			
		||||
  vin: VinStrippedToScriptsig[];
 | 
			
		||||
  vout: VoutStrippedToScriptPubkey[];
 | 
			
		||||
 | 
			
		||||
@ -726,6 +726,16 @@ class Routes {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async getStrippedBlockTransactions(req: Request, res: Response) {
 | 
			
		||||
    try {
 | 
			
		||||
      const transactions = await blocks.$getStrippedBlockTransactions(req.params.hash);
 | 
			
		||||
      res.setHeader('Expires', new Date(Date.now() + 1000 * 600).toUTCString());
 | 
			
		||||
      res.json(transactions);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      res.status(500).send(e instanceof Error ? e.message : e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async getBlocks(req: Request, res: Response) {
 | 
			
		||||
    try {
 | 
			
		||||
      if (['mainnet', 'testnet', 'signet', 'regtest'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user