/ pushtx / pushtx-processor.js
pushtx-processor.js
  1  /*!
  2   * pushtx/pushtx-processor.js
  3   * Copyright © 2019 – Katana Cryptographic Ltd. All Rights Reserved.
  4   */
  5  
  6  
  7  import bitcoin from 'bitcoinjs-lib'
  8  import zmq from 'zeromq/v5-compat.js'
  9  
 10  import Logger from '../lib/logger.js'
 11  import errors from '../lib/errors.js'
 12  import db from '../lib/db/mysql-db-wrapper.js'
 13  import { createRpcClient } from '../lib/bitcoind-rpc/rpc-client.js'
 14  import addrHelper from '../lib/bitcoin/addresses-helper.js'
 15  import network from '../lib/bitcoin/network.js'
 16  import keysFile from '../keys/index.js'
 17  import status from './status.js'
 18  
 19  const keys = keysFile[network.key]
 20  
 21  const SourcesFile = network.key === 'bitcoin'
 22      ? (await import('../lib/remote-importer/sources-mainnet.js'))
 23      : (await import('../lib/remote-importer/sources-testnet.js'))
 24  
 25  const Sources = SourcesFile.default
 26  
 27  /**
 28   * A singleton providing a wrapper
 29   * for pushing transactions with the local bitcoind
 30   */
 31  class PushTxProcessor {
 32  
 33      constructor() {
 34          this.notifSock = null
 35          this.sources = new Sources()
 36          // Initialize the rpc client
 37          this.rpcClient = createRpcClient()
 38      }
 39  
 40      /**
 41       * Initialize the sockets for notifications
 42       */
 43      initNotifications(config) {
 44          // Notification socket for the tracker
 45          this.notifSock = zmq.socket('pub')
 46          this.notifSock.bind(config.uriSocket)
 47      }
 48  
 49      /**
 50       * Enforce a strict verification mode on a list of outputs
 51       * @param {string} rawtx - raw bitcoin transaction in hex format
 52       * @param {number[]} vouts - output indices (integer)
 53       * @returns {number[]} returns the indices of the faulty outputs
 54       */
 55      async enforceStrictModeVouts(rawtx, vouts) {
 56          /** @type {number[]} */
 57          const faultyOutputs = []
 58          /** @type {Map<string, number>} */
 59          const addrMap = new Map()
 60  
 61          let tx
 62          try {
 63              tx = bitcoin.Transaction.fromHex(rawtx)
 64          } catch {
 65              throw errors.tx.PARSE
 66          }
 67          // Check in db if addresses are known and have been used
 68          // Check if TX contains outputs to duplicate address
 69          for (let vout of vouts) {
 70              if (vout >= tx.outs.length)
 71                  throw errors.txout.VOUT
 72              const output = tx.outs[vout]
 73              const address = addrHelper.outputScript2Address(output.script)
 74  
 75              if (address) {
 76                  const nbTxs = await db.getAddressNbTransactions(address)
 77                  if (nbTxs == null || nbTxs > 0 || addrMap.has(address)) {
 78                      faultyOutputs.push(vout)
 79                  } else {
 80                      addrMap.set(address, vout)
 81                  }
 82              }
 83          }
 84  
 85          // Checks with indexer if addresses are known and have been used
 86          if (addrMap.size > 0 && keys.indexer.active !== 'local_bitcoind') {
 87              const results = await this.sources.getAddresses([...addrMap.keys()])
 88  
 89              for (let r of results) {
 90                  if (r.ntx > 0) {
 91                      faultyOutputs.push(addrMap.get(r.address))
 92                  }
 93              }
 94          }
 95          return faultyOutputs
 96      }
 97  
 98      /**
 99       * Push transactions to the Bitcoin network
100       * @param {string} rawtx - raw bitcoin transaction in hex format
101       * @returns {string} returns the txid of the transaction
102       */
103      async pushTx(rawtx) {
104          let value = 0
105  
106          // Attempt to parse incoming TX hex as a bitcoin Transaction
107          try {
108              const tx = bitcoin.Transaction.fromHex(rawtx)
109              for (let output of tx.outs)
110                  value += output.value
111              Logger.info(`PushTx : Push for ${(value / 1e8).toFixed(8)} BTC`)
112          } catch {
113              throw errors.tx.PARSE
114          }
115  
116          // At this point, the raw hex parses as a legitimate transaction.
117          // Attempt to send via RPC to the bitcoind instance
118          try {
119              const txid = await this.rpcClient.sendrawtransaction({ hexstring: rawtx })
120              Logger.info('PushTx : Pushed!')
121              // Update the stats
122              status.updateStats(value)
123              // Notify the tracker
124              this.notifSock.send(['pushtx', rawtx])
125              return txid
126          } catch (error) {
127              Logger.info('PushTx : Push failed')
128              throw error
129          }
130      }
131  
132  }
133  
134  export default new PushTxProcessor()