UNPKG

@node-dlc/chainmon

Version:
187 lines (163 loc) 5.33 kB
import { OutPoint, Script, Tx, Value } from '@node-dlc/bitcoin'; import { BitcoindClient, BlockSummary } from '@node-dlc/bitcoind'; import { Block } from 'bitcoinjs-lib'; import { EventEmitter } from 'events'; export class BlockWatcher extends EventEmitter { /** * Outpoints that are being watched */ public watchedOutPoints: Map<string, OutPoint>; /** * ScriptPubKeys that are being watched */ public watchedScriptPubKeys: Map<string, [Script, Value]>; public receivedBlocks: Map<string, BlockSummary>; public previousHashToHash: Map<string, string>; private _client: BitcoindClient; /** * TxWatcher listens for transactions that match certain patterns * and events when a transaction is found matching the pattern * * @param client */ constructor(client: BitcoindClient) { super(); this._client = client; this.watchedOutPoints = new Map<string, OutPoint>(); this.watchedScriptPubKeys = new Map<string, [Script, Value]>(); this.receivedBlocks = new Map<string, BlockSummary>(); this.previousHashToHash = new Map<string, string>(); } /** * Starts watching for transactions */ public start(latestBlock: BlockSummary): void { this.receivedBlocks.set(latestBlock.hash, latestBlock); this._client.subscribeRawBlock(); this._client.on('rawblock', this._onRawBlock.bind(this)); } /** * Stops watching for transactions */ public stop(): void { // this._client.close(); } /** * Watches an outpoint for broadcase in a new transaction * @param outpoint */ public watchOutpoint(outpoint: OutPoint): void { const key = outpoint.toString(); this.watchedOutPoints.set(key, outpoint); } /** * Watches a scriptpubkey for broadcast in a new transaction * @param scriptPubKey * @param value */ public watchScriptPubKey(scriptPubKey: Script, value?: Value): void { const key = scriptPubKey.toString(); this.watchedScriptPubKeys.set(key, [scriptPubKey, value]); } //////////////////////////////////////////////////////////////// private _checkOutpoints(block: BlockSummary, tx: Tx) { for (const vin of tx.inputs) { const key = vin.outpoint.toString(); const watchedOutpoint = this.watchedOutPoints.get(key); if (watchedOutpoint) { this.watchedOutPoints.delete(key); this.emit('outpointspent', block, tx, watchedOutpoint); } } } private _checkScriptPubkeys(block: BlockSummary, tx: Tx) { for (const vout of tx.outputs) { const key = vout.scriptPubKey.toString(); const watchedScriptPubKey = this.watchedScriptPubKeys.get(key); if (watchedScriptPubKey) { const [, value] = watchedScriptPubKey; if (!value || (value && vout.value === value)) { this.watchedScriptPubKeys.delete(key); this.emit('scriptpubkeyreceived', block, tx, watchedScriptPubKey); } } } } private _onRawBlock(buf: Buffer) { const block = Block.fromBuffer(buf); let txs: [string]; for (const transaction of block.transactions) { if (!txs) txs = [transaction.getId()]; } const previousblockhash = Buffer.from(block.prevHash) .reverse() .toString('hex'); const previousBlock = this.receivedBlocks.get(previousblockhash); const blockSize = buf.length; if (previousBlock) { this._processBlock(block, blockSize, txs, previousBlock); } else { this._findPreviousBlockAndProcess(buf, previousblockhash); } } private _processBlock( block: Block, blockSize: number, txs: [string], previousBlock: BlockSummary, ) { const blockHash = block.getId(); const blockSummary: BlockSummary = { hash: blockHash, confirmations: 1, size: blockSize, weight: block.weight(), height: previousBlock.height + 1, version: block.version, versionHex: '', merkleroot: block.merkleRoot.toString('hex'), tx: txs, time: block.timestamp, mediantime: block.timestamp, nonce: block.nonce, bits: '', difficulty: '', chainwork: '', nTx: block.transactions.length, previousblockhash: previousBlock.hash, nextblockhash: '', }; this.receivedBlocks.set(blockSummary.hash, blockSummary); for (const transaction of block.transactions) { try { const tx = Tx.fromBuffer(transaction.toBuffer()); this._checkOutpoints(blockSummary, tx); this._checkScriptPubkeys(blockSummary, tx); } catch (e) { console.error( 'Failed to deserialize tx', transaction.toBuffer().toString('hex'), ); } } const existingBlockWithPrevHash = this.previousHashToHash.get( blockSummary.previousblockhash, ); if (existingBlockWithPrevHash) { this.emit('orphanblock', existingBlockWithPrevHash, blockSummary.hash); } else { this.previousHashToHash.set( blockSummary.previousblockhash, blockSummary.hash, ); } } private async _findPreviousBlockAndProcess( currentBlockBuf: Buffer, previousblockhash: string, ) { const previousBuf = await this._client.getRawBlock(previousblockhash); this._onRawBlock(previousBuf); this._onRawBlock(currentBlockBuf); } }