From 74b87b60065b617b93cbcec6219a0f2281f26387 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 26 Jul 2023 10:47:59 +0900 Subject: [PATCH] Support p2pk track-address websocket subscriptions --- backend/src/api/transaction-utils.ts | 9 ++++ backend/src/api/websocket-handler.ts | 76 +++++++++++++++++++++++++++- 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/backend/src/api/transaction-utils.ts b/backend/src/api/transaction-utils.ts index 0b10afdfb..b8a9a108a 100644 --- a/backend/src/api/transaction-utils.ts +++ b/backend/src/api/transaction-utils.ts @@ -3,6 +3,7 @@ import { IEsploraApi } from './bitcoin/esplora-api.interface'; import { Common } from './common'; import bitcoinApi, { bitcoinCoreApi } from './bitcoin/bitcoin-api-factory'; import * as bitcoinjs from 'bitcoinjs-lib'; +import crypto from 'node:crypto'; class TransactionUtils { constructor() { } @@ -170,6 +171,14 @@ class TransactionUtils { 16 ); } + + public calcScriptHash(script: string): string { + if (!/^[0-9a-fA-F]*$/.test(script) || script.length % 2 !== 0) { + throw new Error('script is not a valid hex string'); + } + const buf = Buffer.from(script, 'hex'); + return crypto.createHash('sha256').update(buf).digest('hex'); + } } export default new TransactionUtils(); diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 56c8513cd..3438e0e0c 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -183,15 +183,22 @@ class WebsocketHandler { } if (parsedMessage && parsedMessage['track-address']) { - if (/^([a-km-zA-HJ-NP-Z1-9]{26,35}|[a-km-zA-HJ-NP-Z1-9]{80}|[a-z]{2,5}1[ac-hj-np-z02-9]{8,100}|[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,100})$/ + if (/^([a-km-zA-HJ-NP-Z1-9]{26,35}|[a-km-zA-HJ-NP-Z1-9]{80}|[a-z]{2,5}1[ac-hj-np-z02-9]{8,100}|[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,100}|[0-9a-fA-F]{130})$/ .test(parsedMessage['track-address'])) { let matchedAddress = parsedMessage['track-address']; if (/^[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,100}$/.test(parsedMessage['track-address'])) { matchedAddress = matchedAddress.toLowerCase(); } - client['track-address'] = matchedAddress; + if (/^[0-9a-fA-F]{130}$/.test(parsedMessage['track-address'])) { + client['track-address'] = null; + client['track-scripthash'] = transactionUtils.calcScriptHash('41' + matchedAddress + 'ac'); + } else { + client['track-address'] = matchedAddress; + client['track-scripthash'] = null; + } } else { client['track-address'] = null; + client['track-scripthash'] = null; } } @@ -546,6 +553,44 @@ class WebsocketHandler { } } + if (client['track-scripthash']) { + const foundTransactions: TransactionExtended[] = []; + + for (const tx of newTransactions) { + const someVin = tx.vin.some((vin) => !!vin.prevout && vin.prevout.scriptpubkey_type === 'p2pk' && vin.prevout.scriptpubkey === client['track-scripthash']); + if (someVin) { + if (config.MEMPOOL.BACKEND !== 'esplora') { + try { + const fullTx = await transactionUtils.$getMempoolTransactionExtended(tx.txid, true); + foundTransactions.push(fullTx); + } catch (e) { + logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e)); + } + } else { + foundTransactions.push(tx); + } + return; + } + const someVout = tx.vout.some((vout) => vout.scriptpubkey_type === 'p2pk' && vout.scriptpubkey === client['track-scripthash']); + if (someVout) { + if (config.MEMPOOL.BACKEND !== 'esplora') { + try { + const fullTx = await transactionUtils.$getMempoolTransactionExtended(tx.txid, true); + foundTransactions.push(fullTx); + } catch (e) { + logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e)); + } + } else { + foundTransactions.push(tx); + } + } + } + + if (foundTransactions.length) { + response['address-transactions'] = JSON.stringify(foundTransactions); + } + } + if (client['track-asset']) { const foundTransactions: TransactionExtended[] = []; @@ -821,6 +866,33 @@ class WebsocketHandler { } } + if (client['track-scripthash']) { + const foundTransactions: TransactionExtended[] = []; + + transactions.forEach((tx) => { + if (tx.vin && tx.vin.some((vin) => !!vin.prevout && vin.prevout.scriptpubkey_type === 'p2pk' && vin.prevout.scriptpubkey === client['track-scripthash'])) { + foundTransactions.push(tx); + return; + } + if (tx.vout && tx.vout.some((vout) => vout.scriptpubkey_type === 'p2pk' && vout.scriptpubkey === client['track-scripthash'])) { + foundTransactions.push(tx); + } + }); + + if (foundTransactions.length) { + foundTransactions.forEach((tx) => { + tx.status = { + confirmed: true, + block_height: block.height, + block_hash: block.id, + block_time: block.timestamp, + }; + }); + + response['block-transactions'] = JSON.stringify(foundTransactions); + } + } + if (client['track-asset']) { const foundTransactions: TransactionExtended[] = [];