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 { private isRunning = false; constructor() { } public async $parse() { if (this.isRunning) { return; } try { 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; } catch (e) { this.isRunning = false; throw new Error(e instanceof Error ? e.message : 'Error'); } } public async $getPegDataByMonth(): Promise { 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(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 { 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 { const connection = await DB.pool.getConnection(); const query = `SELECT block, datetime, block_hash FROM last_elements_block`; const [rows] = await connection.query(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(query, [blockHeight, datetime, blockHash]); connection.release(); } } export default new ElementsParser();