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()