/ tracker / block.js
block.js
  1  /*!
  2   * tracker/block.js
  3   * Copyright © 2019 – Katana Cryptographic Ltd. All Rights Reserved.
  4   */
  5  
  6  
  7  import util from '../lib/util.js'
  8  import Logger from '../lib/logger.js'
  9  import db from '../lib/db/mysql-db-wrapper.js'
 10  import Transaction from './transaction.js'
 11  import TransactionsBundle from './transactions-bundle.js'
 12  
 13  /**
 14   * @typedef {import('bitcoinjs-lib').Transaction} bitcoin.Transaction
 15   */
 16  
 17  /**
 18   * @typedef {{ height: number, hash: string, time: number, previousblockhash: string }} BlockHeader
 19   */
 20  
 21  /**
 22   * A class allowing to process a transaction
 23   */
 24  class Block extends TransactionsBundle {
 25  
 26      /**
 27       * Constructor
 28       * @param {BlockHeader} header - block header
 29       * @param {bitcoin.Transaction[] | null} transactions - array of bitcoinjs transaction objects
 30       */
 31      constructor(header, transactions) {
 32          super()
 33          /**
 34           * @type {BlockHeader}
 35           */
 36          this.header = header
 37  
 38          try {
 39              if (transactions != null) {
 40                  this.transactions = transactions.map((tx) => new Transaction(tx))
 41              }
 42          } catch (error) {
 43              Logger.error(error, 'Tracker : Block()')
 44              Logger.error(null, JSON.stringify(header))
 45          }
 46      }
 47  
 48      /**
 49       * Register the block and transactions of interest in db
 50       * @returns {Promise<bitcoin.Transaction[]>} returns an array of transactions to be broadcast
 51       */
 52      async processBlock() {
 53          Logger.info('Tracker : Beginning to process new block.')
 54  
 55          const t0 = Date.now()
 56  
 57          /**
 58           * Deduplicated transactions for broadcast
 59           * @type {Map<string, bitcoin.Transaction>}
 60           */
 61          const txsForBroadcast = new Map()
 62  
 63          const [txsForBroadcast1, txsForBroadcast2] = await Promise.all([this.processOutputs(), this.processInputs()])
 64          for (const tx of txsForBroadcast1) {
 65              txsForBroadcast.set(tx.getId(), tx)
 66          }
 67  
 68          for (const tx of txsForBroadcast2) {
 69              txsForBroadcast.set(tx.getId(), tx)
 70          }
 71  
 72          const aTxsForBroadcast = [...txsForBroadcast.values()]
 73  
 74          const blockId = await this.registerBlock()
 75  
 76          await this.confirmTransactions([...txsForBroadcast.keys()], blockId)
 77  
 78          // Logs and result returned
 79          const ntx = this.transactions.length
 80          const dt = ((Date.now() - t0) / 1000).toFixed(1)
 81          const per = ((Date.now() - t0) / ntx).toFixed(0)
 82          Logger.info(`Tracker :  Finished block ${this.header.height}, ${dt}s, ${ntx} tx, ${per}ms/tx`)
 83  
 84          return aTxsForBroadcast
 85      }
 86  
 87  
 88      /**
 89       * Process the transaction outputs
 90       * @returns {Promise<bitcoin.Transaction[]>} returns an array of transactions to be broadcast
 91       */
 92      async processOutputs() {
 93          /**
 94           * @type {bitcoin.Transaction[]}
 95           */
 96          const txsForBroadcast = []
 97          const filteredTxs = await this.prefilterByOutputs()
 98          await util.asyncPool(10, filteredTxs, async (filteredTx) => {
 99              await filteredTx.processOutputs()
100              if (filteredTx.doBroadcast)
101                  txsForBroadcast.push(filteredTx.tx)
102          })
103          return txsForBroadcast
104      }
105  
106      /**
107       * Process the transaction inputs
108       * @returns {Promise<bitcoin.Transaction[]>} returns an array of transactions to be broadcast
109       */
110      async processInputs() {
111          /**
112           * @type {bitcoin.Transaction[]}
113           */
114          const txsForBroadcast = []
115          const filteredTxs = await this.prefilterByInputs()
116          await util.asyncPool(10, filteredTxs, async (filteredTx) => {
117              await filteredTx.processInputs()
118              if (filteredTx.doBroadcast)
119                  txsForBroadcast.push(filteredTx.tx)
120          })
121          return txsForBroadcast
122      }
123  
124      /**
125       * Store the block in db
126       * @returns {Promise<number>} returns the id of the block
127       */
128      async registerBlock() {
129          const previousBlock = await db.getBlockByHash(this.header.previousblockhash)
130          const previousID = (previousBlock && previousBlock.blockID) ? previousBlock.blockID : null
131  
132          const blockId = await db.addBlock({
133              blockHeight: this.header.height,
134              blockHash: this.header.hash,
135              blockTime: this.header.time,
136              blockParent: previousID
137          })
138  
139          Logger.info(`Tracker :  Added block ${this.header.height} (id=${blockId})`)
140  
141          return blockId
142      }
143  
144      /**
145       * Confirm the transactions in db
146       * @param {string[]} txids - set of transactions IDs stored in db
147       * @param {number} blockId - id of the block
148       * @returns {Promise<any[]>}
149       */
150      async confirmTransactions(txids, blockId) {
151          const txidLists = util.splitList(txids, 100)
152          return util.asyncPool(10, txidLists, list => db.confirmTransactions(list, blockId))
153      }
154  
155      /**
156       * Register the block header
157       * @param {number} prevBlockID - id of previous block
158       * @returns {Promise<number>}
159       */
160      async checkBlockHeader(prevBlockID) {
161          Logger.info('Tracker : Beginning to process new block header.')
162  
163          // Insert the block header into the database
164          const blockId = await db.addBlock({
165              blockHeight: this.header.height,
166              blockHash: this.header.hash,
167              blockTime: this.header.time,
168              blockParent: prevBlockID
169          })
170  
171          Logger.info(`Tracker :  Added block header ${this.header.height} (id=${blockId})`)
172  
173          return blockId
174      }
175  
176  }
177  
178  export default Block