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