Elements blockchain parser. Save all peg in/out i the database.
This commit is contained in:
		
							parent
							
								
									ccee78bc57
								
							
						
					
					
						commit
						54e6f408ce
					
				@ -72,7 +72,7 @@ export namespace IBitcoinApi {
 | 
			
		||||
    time: number;                    //  (numeric) Same as blocktime
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  interface Vin {
 | 
			
		||||
  export interface Vin {
 | 
			
		||||
    txid?: string;                   //  (string) The transaction id
 | 
			
		||||
    vout?: number;                   //  (string)
 | 
			
		||||
    scriptSig?: {                    //  (json object) The script
 | 
			
		||||
@ -82,18 +82,22 @@ export namespace IBitcoinApi {
 | 
			
		||||
    sequence: number;                //  (numeric) The script sequence number
 | 
			
		||||
    txinwitness?: string[];          //  (string) hex-encoded witness data
 | 
			
		||||
    coinbase?: string;
 | 
			
		||||
    is_pegin?: boolean;              //  (boolean) Elements peg-in
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  interface Vout {
 | 
			
		||||
  export interface Vout {
 | 
			
		||||
    value: number;                   //  (numeric) The value in BTC
 | 
			
		||||
    n: number;                       //  (numeric) index
 | 
			
		||||
    asset?: string;                  //  (string) Elements asset id
 | 
			
		||||
    scriptPubKey: {                  //  (json object)
 | 
			
		||||
      asm: string;                   //  (string) the asm
 | 
			
		||||
      hex: string;                   //  (string) the hex
 | 
			
		||||
      reqSigs?: number;              //  (numeric) The required sigs
 | 
			
		||||
      type: string;                  //  (string) The type, eg 'pubkeyhash'
 | 
			
		||||
      addresses?: string[];           //  (string) bitcoin addresses
 | 
			
		||||
      address?: string;              //  (string) bitcoin address
 | 
			
		||||
      addresses?: string[];           //  (string) bitcoin addresses
 | 
			
		||||
      pegout_chain?: string;         //  (string) Elements peg-out chain
 | 
			
		||||
      pegout_addresses?: string[];   //  (string) Elements peg-out addresses
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,8 @@
 | 
			
		||||
import { CpfpInfo, TransactionExtended, TransactionStripped } from '../mempool.interfaces';
 | 
			
		||||
import config from '../config';
 | 
			
		||||
export class Common {
 | 
			
		||||
  static nativeAssetId = '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d';
 | 
			
		||||
 | 
			
		||||
  static median(numbers: number[]) {
 | 
			
		||||
    let medianNr = 0;
 | 
			
		||||
    const numsLen = numbers.length;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										105
									
								
								backend/src/api/liquid/elements-parser.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								backend/src/api/liquid/elements-parser.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,105 @@
 | 
			
		||||
import { IBitcoinApi } from '../bitcoin/bitcoin-api.interface';
 | 
			
		||||
import bitcoinClient from '../bitcoin/bitcoin-client';
 | 
			
		||||
import bitcoinSecondClient from '../bitcoin/bitcoin-second-client';
 | 
			
		||||
import { Common } from '../common';
 | 
			
		||||
import { DB } from '../../database';
 | 
			
		||||
import logger from '../../logger';
 | 
			
		||||
 | 
			
		||||
class ElementsParser {
 | 
			
		||||
  isRunning = false;
 | 
			
		||||
  constructor() { }
 | 
			
		||||
 | 
			
		||||
  public async $parse() {
 | 
			
		||||
    if (this.isRunning) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    this.isRunning = true;
 | 
			
		||||
    const result = await bitcoinClient.getChainTips();
 | 
			
		||||
    const tip = result[0].height;
 | 
			
		||||
    const latestBlock = await this.$getLatestBlockFromDatabase();
 | 
			
		||||
    for (let height = latestBlock.block + 1; height <= tip; height++) {
 | 
			
		||||
      const blockHash: IBitcoinApi.ChainTips = await bitcoinClient.getBlockHash(height);
 | 
			
		||||
      const block: IBitcoinApi.Block = await bitcoinClient.getBlock(blockHash, 2);
 | 
			
		||||
      await this.$parseBlock(block);
 | 
			
		||||
      await this.$saveLatestBlockToDatabase(block.height, block.time, block.hash);
 | 
			
		||||
    }
 | 
			
		||||
    this.isRunning = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $getPegDataByMonth(): Promise<any> {
 | 
			
		||||
    const connection = await DB.pool.getConnection();
 | 
			
		||||
    const query = `SELECT SUM(amount) AS amount, DATE_FORMAT(FROM_UNIXTIME(datetime), '%Y-%m-01') AS date FROM elements_pegs GROUP BY DATE_FORMAT(FROM_UNIXTIME(datetime), '%Y%m')`;
 | 
			
		||||
    const [rows] = await connection.query<any>(query);
 | 
			
		||||
    connection.release();
 | 
			
		||||
    return rows;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected async $parseBlock(block: IBitcoinApi.Block) {
 | 
			
		||||
    for (const tx of block.tx) {
 | 
			
		||||
      await this.$parseInputs(tx, block);
 | 
			
		||||
      await this.$parseOutputs(tx, block);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected async $parseInputs(tx: IBitcoinApi.Transaction, block: IBitcoinApi.Block) {
 | 
			
		||||
    for (const [index, input] of tx.vin.entries()) {
 | 
			
		||||
      if (input.is_pegin) {
 | 
			
		||||
        await this.$parsePegIn(input, index, tx.txid, block);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected async $parsePegIn(input: IBitcoinApi.Vin, vindex: number, txid: string, block: IBitcoinApi.Block) {
 | 
			
		||||
    const bitcoinTx: IBitcoinApi.Transaction = await bitcoinSecondClient.getRawTransaction(input.txid, true);
 | 
			
		||||
    const prevout = bitcoinTx.vout[input.vout || 0];
 | 
			
		||||
    const outputAddress = prevout.scriptPubKey.address || (prevout.scriptPubKey.addresses && prevout.scriptPubKey.addresses[0]) || '';
 | 
			
		||||
    await this.$savePegToDatabase(block.height, block.time, prevout.value * 100000000, txid, vindex,
 | 
			
		||||
      outputAddress, bitcoinTx.txid, prevout.n, 1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected async $parseOutputs(tx: IBitcoinApi.Transaction, block: IBitcoinApi.Block) {
 | 
			
		||||
    for (const output of tx.vout) {
 | 
			
		||||
      if (output.scriptPubKey.pegout_chain) {
 | 
			
		||||
        await this.$savePegToDatabase(block.height, block.time, 0 - output.value * 100000000, tx.txid, output.n,
 | 
			
		||||
          (output.scriptPubKey.pegout_addresses && output.scriptPubKey.pegout_addresses[0] || ''), '', 0, 0);
 | 
			
		||||
      }
 | 
			
		||||
      if (!output.scriptPubKey.pegout_chain && output.scriptPubKey.type === 'nulldata'
 | 
			
		||||
        && output.value && output.value > 0 && output.asset && output.asset === Common.nativeAssetId) {
 | 
			
		||||
        await this.$savePegToDatabase(block.height, block.time, 0 - output.value * 100000000, tx.txid, output.n,
 | 
			
		||||
          (output.scriptPubKey.pegout_addresses && output.scriptPubKey.pegout_addresses[0] || ''), '', 0, 1);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected async $savePegToDatabase(height: number, blockTime: number, amount: number, txid: string,
 | 
			
		||||
    txindex: number, bitcoinaddress: string, bitcointxid: string, bitcoinindex: number, final_tx: number): Promise<void> {
 | 
			
		||||
    const connection = await DB.pool.getConnection();
 | 
			
		||||
    const query = `INSERT INTO elements_pegs(
 | 
			
		||||
        block, datetime, amount, txid, txindex, bitcoinaddress, bitcointxid, bitcoinindex, final_tx
 | 
			
		||||
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`;
 | 
			
		||||
 | 
			
		||||
    const params: (string | number)[] = [
 | 
			
		||||
      height, blockTime, amount, txid, txindex, bitcoinaddress, bitcointxid, bitcoinindex, final_tx
 | 
			
		||||
    ];
 | 
			
		||||
    await connection.query(query, params);
 | 
			
		||||
    connection.release();
 | 
			
		||||
    logger.debug(`Saved L-BTC peg from block height #${height} with TXID ${txid}.`);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected async $getLatestBlockFromDatabase(): Promise<any> {
 | 
			
		||||
    const connection = await DB.pool.getConnection();
 | 
			
		||||
    const query = `SELECT block, datetime, block_hash FROM last_elements_block`;
 | 
			
		||||
    const [rows] = await connection.query<any>(query);
 | 
			
		||||
    connection.release();
 | 
			
		||||
    return rows[0];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected async $saveLatestBlockToDatabase(blockHeight: number, datetime: number, blockHash: string) {
 | 
			
		||||
    const connection = await DB.pool.getConnection();
 | 
			
		||||
    const query = `UPDATE last_elements_block SET block = ?, datetime = ?, block_hash = ?`;
 | 
			
		||||
    await connection.query<any>(query, [blockHeight, datetime, blockHash]);
 | 
			
		||||
    connection.release();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default new ElementsParser();
 | 
			
		||||
@ -14,7 +14,6 @@ import transactionUtils from './transaction-utils';
 | 
			
		||||
 | 
			
		||||
class WebsocketHandler {
 | 
			
		||||
  private wss: WebSocket.Server | undefined;
 | 
			
		||||
  private nativeAssetId = '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d';
 | 
			
		||||
  private extraInitProperties = {};
 | 
			
		||||
 | 
			
		||||
  constructor() { }
 | 
			
		||||
@ -308,7 +307,7 @@ class WebsocketHandler {
 | 
			
		||||
 | 
			
		||||
        newTransactions.forEach((tx) => {
 | 
			
		||||
 | 
			
		||||
          if (client['track-asset'] === this.nativeAssetId) {
 | 
			
		||||
          if (client['track-asset'] === Common.nativeAssetId) {
 | 
			
		||||
            if (tx.vin.some((vin) => !!vin.is_pegin)) {
 | 
			
		||||
              foundTransactions.push(tx);
 | 
			
		||||
              return;
 | 
			
		||||
@ -439,7 +438,7 @@ class WebsocketHandler {
 | 
			
		||||
        const foundTransactions: TransactionExtended[] = [];
 | 
			
		||||
 | 
			
		||||
        transactions.forEach((tx) => {
 | 
			
		||||
          if (client['track-asset'] === this.nativeAssetId) {
 | 
			
		||||
          if (client['track-asset'] === Common.nativeAssetId) {
 | 
			
		||||
            if (tx.vin && tx.vin.some((vin) => !!vin.is_pegin)) {
 | 
			
		||||
              foundTransactions.push(tx);
 | 
			
		||||
              return;
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ import logger from './logger';
 | 
			
		||||
import backendInfo from './api/backend-info';
 | 
			
		||||
import loadingIndicators from './api/loading-indicators';
 | 
			
		||||
import mempool from './api/mempool';
 | 
			
		||||
import elementsParser from './api/liquid/elements-parser';
 | 
			
		||||
 | 
			
		||||
class Server {
 | 
			
		||||
  private wss: WebSocket.Server | undefined;
 | 
			
		||||
@ -141,6 +142,15 @@ class Server {
 | 
			
		||||
    if (this.wss) {
 | 
			
		||||
      websocketHandler.setWebsocketServer(this.wss);
 | 
			
		||||
    }
 | 
			
		||||
    if (config.MEMPOOL.NETWORK === 'liquid') {
 | 
			
		||||
      blocks.setNewBlockCallback(async () => {
 | 
			
		||||
        try {
 | 
			
		||||
          await elementsParser.$parse();
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
          logger.warn('Elements parsing error: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    websocketHandler.setupConnectionHandling();
 | 
			
		||||
    statistics.setNewStatisticsEntryCallback(websocketHandler.handleNewStatistic.bind(websocketHandler));
 | 
			
		||||
    blocks.setNewBlockCallback(websocketHandler.handleNewBlock.bind(websocketHandler));
 | 
			
		||||
@ -254,6 +264,12 @@ class Server {
 | 
			
		||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'address-prefix/:prefix', routes.getAddressPrefix)
 | 
			
		||||
      ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config.MEMPOOL.NETWORK === 'liquid') {
 | 
			
		||||
      this.app
 | 
			
		||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs/month', routes.$getElementsPegsByMonth)
 | 
			
		||||
      ;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -18,6 +18,7 @@ import blocks from './api/blocks';
 | 
			
		||||
import loadingIndicators from './api/loading-indicators';
 | 
			
		||||
import { Common } from './api/common';
 | 
			
		||||
import bitcoinClient from './api/bitcoin/bitcoin-client';
 | 
			
		||||
import elementsParser from './api/liquid/elements-parser';
 | 
			
		||||
 | 
			
		||||
class Routes {
 | 
			
		||||
  constructor() {}
 | 
			
		||||
@ -754,6 +755,15 @@ class Routes {
 | 
			
		||||
      res.status(500).send(e instanceof Error ? e.message : e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $getElementsPegsByMonth(req: Request, res: Response) {
 | 
			
		||||
    try {
 | 
			
		||||
      const pegs = await elementsParser.$getPegDataByMonth();
 | 
			
		||||
      res.json(pegs);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      res.status(500).send(e instanceof Error ? e.message : e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default new Routes();
 | 
			
		||||
 | 
			
		||||
@ -84,3 +84,23 @@ ALTER TABLE `transactions`
 | 
			
		||||
 | 
			
		||||
ALTER TABLE `statistics`
 | 
			
		||||
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
CREATE TABLE `last_elements_block` (
 | 
			
		||||
  `block` int(11) NOT NULL,
 | 
			
		||||
  `datetime` int(11) NOT NULL,
 | 
			
		||||
  `block_hash` varchar(65) NOT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 | 
			
		||||
 | 
			
		||||
INSERT INTO `last_elements_block` VALUES(0, 0, '');
 | 
			
		||||
 | 
			
		||||
CREATE TABLE `elements_pegs` (
 | 
			
		||||
  `block` int(11) NOT NULL,
 | 
			
		||||
  `datetime` int(11) NOT NULL,
 | 
			
		||||
  `amount` bigint(20) NOT NULL,
 | 
			
		||||
  `txid` varchar(65) NOT NULL,
 | 
			
		||||
  `txindex` int(11) NOT NULL,
 | 
			
		||||
  `bitcoinaddress` varchar(100) NOT NULL,
 | 
			
		||||
  `bitcointxid` varchar(65) NOT NULL,
 | 
			
		||||
  `bitcoinindex` int(11) NOT NULL,
 | 
			
		||||
  `final_tx` int(11) NOT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user