2020-10-13 15:27:52 +07:00
|
|
|
import logger from '../logger';
|
2020-02-26 17:49:53 +07:00
|
|
|
import * as WebSocket from 'ws';
|
2022-07-04 08:37:36 -07:00
|
|
|
import {
|
2023-05-29 15:56:29 -04:00
|
|
|
BlockExtended, TransactionExtended, MempoolTransactionExtended, WebsocketResponse,
|
2023-03-04 10:51:13 +09:00
|
|
|
OptimizedStatistic, ILoadingIndicators
|
2022-07-04 08:37:36 -07:00
|
|
|
} from '../mempool.interfaces';
|
2020-02-26 17:49:53 +07:00
|
|
|
import blocks from './blocks';
|
|
|
|
import memPool from './mempool';
|
2020-05-27 15:18:04 +07:00
|
|
|
import backendInfo from './backend-info';
|
2020-02-26 17:49:53 +07:00
|
|
|
import mempoolBlocks from './mempool-blocks';
|
2020-06-08 18:55:53 +07:00
|
|
|
import { Common } from './common';
|
2021-01-05 18:57:06 +07:00
|
|
|
import loadingIndicators from './loading-indicators';
|
2021-01-10 17:38:59 +07:00
|
|
|
import config from '../config';
|
|
|
|
import transactionUtils from './transaction-utils';
|
2023-07-14 16:08:57 +09:00
|
|
|
import rbfCache, { ReplacementInfo } from './rbf-cache';
|
2022-03-12 14:55:42 +01:00
|
|
|
import difficultyAdjustment from './difficulty-adjustment';
|
2022-05-27 12:52:40 +02:00
|
|
|
import feeApi from './fee-api';
|
2022-07-06 22:27:45 +02:00
|
|
|
import BlocksAuditsRepository from '../repositories/BlocksAuditsRepository';
|
2022-07-07 19:11:42 +02:00
|
|
|
import BlocksSummariesRepository from '../repositories/BlocksSummariesRepository';
|
2022-10-19 17:10:45 +00:00
|
|
|
import Audit from './audit';
|
2023-02-10 07:26:49 -06:00
|
|
|
import { deepClone } from '../utils/clone';
|
2023-02-15 16:05:14 +09:00
|
|
|
import priceUpdater from '../tasks/price-updater';
|
2023-03-04 10:51:13 +09:00
|
|
|
import { ApiPrice } from '../repositories/PricesRepository';
|
2020-02-26 17:49:53 +07:00
|
|
|
|
2023-06-12 15:31:47 -04:00
|
|
|
// valid 'want' subscriptions
|
|
|
|
const wantable = [
|
|
|
|
'blocks',
|
|
|
|
'mempool-blocks',
|
|
|
|
'live-2h-chart',
|
|
|
|
'stats',
|
|
|
|
];
|
|
|
|
|
2020-02-26 17:49:53 +07:00
|
|
|
class WebsocketHandler {
|
|
|
|
private wss: WebSocket.Server | undefined;
|
2020-07-14 21:26:02 +07:00
|
|
|
private extraInitProperties = {};
|
2020-02-26 17:49:53 +07:00
|
|
|
|
2023-05-01 13:08:29 -06:00
|
|
|
private numClients = 0;
|
|
|
|
private numConnected = 0;
|
|
|
|
private numDisconnected = 0;
|
|
|
|
|
2023-06-12 15:31:47 -04:00
|
|
|
private socketData: { [key: string]: string } = {};
|
2023-05-09 19:48:02 -06:00
|
|
|
private serializedInitData: string = '{}';
|
2023-07-14 16:08:57 +09:00
|
|
|
private lastRbfSummary: ReplacementInfo | null = null;
|
2023-05-09 19:48:02 -06:00
|
|
|
|
2020-05-27 15:18:04 +07:00
|
|
|
constructor() { }
|
2020-02-26 17:49:53 +07:00
|
|
|
|
|
|
|
setWebsocketServer(wss: WebSocket.Server) {
|
|
|
|
this.wss = wss;
|
|
|
|
}
|
|
|
|
|
2023-06-12 15:31:47 -04:00
|
|
|
setExtraInitData(property: string, value: any) {
|
2020-07-14 21:26:02 +07:00
|
|
|
this.extraInitProperties[property] = value;
|
2023-06-12 15:31:47 -04:00
|
|
|
this.updateSocketDataFields(this.extraInitProperties);
|
2023-05-09 19:48:02 -06:00
|
|
|
}
|
|
|
|
|
2023-06-12 15:31:47 -04:00
|
|
|
private updateSocketDataFields(data: { [property: string]: any }): void {
|
2023-05-09 19:48:02 -06:00
|
|
|
for (const property of Object.keys(data)) {
|
|
|
|
if (data[property] != null) {
|
2023-06-12 15:31:47 -04:00
|
|
|
this.socketData[property] = JSON.stringify(data[property]);
|
2023-05-09 19:48:02 -06:00
|
|
|
} else {
|
2023-06-12 15:31:47 -04:00
|
|
|
delete this.socketData[property];
|
2023-05-09 19:48:02 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
this.serializedInitData = '{'
|
2023-06-12 15:31:47 -04:00
|
|
|
+ Object.keys(this.socketData).map(key => `"${key}": ${this.socketData[key]}`).join(', ')
|
|
|
|
+ '}';
|
2023-05-09 19:48:02 -06:00
|
|
|
}
|
|
|
|
|
2023-06-12 15:31:47 -04:00
|
|
|
private updateSocketData(): void {
|
2023-05-09 19:48:02 -06:00
|
|
|
const _blocks = blocks.getBlocks().slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT);
|
|
|
|
const da = difficultyAdjustment.getDifficultyAdjustment();
|
2023-06-12 15:31:47 -04:00
|
|
|
this.updateSocketDataFields({
|
2023-05-09 19:48:02 -06:00
|
|
|
'mempoolInfo': memPool.getMempoolInfo(),
|
|
|
|
'vBytesPerSecond': memPool.getVBytesPerSecond(),
|
|
|
|
'blocks': _blocks,
|
|
|
|
'conversions': priceUpdater.getLatestPrices(),
|
|
|
|
'mempool-blocks': mempoolBlocks.getMempoolBlocks(),
|
|
|
|
'transactions': memPool.getLatestTransactions(),
|
|
|
|
'backendInfo': backendInfo.getBackendInfo(),
|
|
|
|
'loadingIndicators': loadingIndicators.getLoadingIndicators(),
|
|
|
|
'da': da?.previousTime ? da : undefined,
|
|
|
|
'fees': feeApi.getRecommendedFee(),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public getSerializedInitData(): string {
|
|
|
|
return this.serializedInitData;
|
2020-07-14 21:26:02 +07:00
|
|
|
}
|
|
|
|
|
2020-02-26 17:49:53 +07:00
|
|
|
setupConnectionHandling() {
|
|
|
|
if (!this.wss) {
|
|
|
|
throw new Error('WebSocket.Server is not set');
|
|
|
|
}
|
|
|
|
|
|
|
|
this.wss.on('connection', (client: WebSocket) => {
|
2023-05-01 13:08:29 -06:00
|
|
|
this.numConnected++;
|
2020-11-15 11:43:34 +09:00
|
|
|
client.on('error', logger.info);
|
2023-05-01 13:08:29 -06:00
|
|
|
client.on('close', () => {
|
|
|
|
this.numDisconnected++;
|
|
|
|
});
|
2021-04-27 02:13:48 +04:00
|
|
|
client.on('message', async (message: string) => {
|
2020-02-26 17:49:53 +07:00
|
|
|
try {
|
2020-04-13 02:06:10 +07:00
|
|
|
const parsedMessage: WebsocketResponse = JSON.parse(message);
|
|
|
|
const response = {};
|
2020-02-26 17:49:53 +07:00
|
|
|
|
2023-06-12 15:31:47 -04:00
|
|
|
const wantNow = {};
|
|
|
|
if (parsedMessage && parsedMessage.action === 'want' && Array.isArray(parsedMessage.data)) {
|
|
|
|
for (const sub of wantable) {
|
|
|
|
const key = `want-${sub}`;
|
|
|
|
const wants = parsedMessage.data.includes(sub);
|
|
|
|
if (wants && client['wants'] && !client[key]) {
|
|
|
|
wantNow[key] = true;
|
|
|
|
}
|
|
|
|
client[key] = wants;
|
|
|
|
}
|
|
|
|
client['wants'] = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// send initial data when a client first starts a subscription
|
2023-06-12 16:32:04 -04:00
|
|
|
if (wantNow['want-blocks'] || (parsedMessage && parsedMessage['refresh-blocks'])) {
|
2023-06-12 15:31:47 -04:00
|
|
|
response['blocks'] = this.socketData['blocks'];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wantNow['want-mempool-blocks']) {
|
|
|
|
response['mempool-blocks'] = this.socketData['mempool-blocks'];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wantNow['want-stats']) {
|
|
|
|
response['mempoolInfo'] = this.socketData['mempoolInfo'];
|
|
|
|
response['vBytesPerSecond'] = this.socketData['vBytesPerSecond'];
|
|
|
|
response['fees'] = this.socketData['fees'];
|
|
|
|
response['da'] = this.socketData['da'];
|
2020-02-26 17:49:53 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (parsedMessage && parsedMessage['track-tx']) {
|
|
|
|
if (/^[a-fA-F0-9]{64}$/.test(parsedMessage['track-tx'])) {
|
|
|
|
client['track-tx'] = parsedMessage['track-tx'];
|
2023-04-21 08:40:21 +09:00
|
|
|
const trackTxid = client['track-tx'];
|
2022-03-08 14:49:25 +01:00
|
|
|
// Client is telling the transaction wasn't found
|
2020-04-13 02:06:10 +07:00
|
|
|
if (parsedMessage['watch-mempool']) {
|
2023-04-21 08:40:21 +09:00
|
|
|
const rbfCacheTxid = rbfCache.getReplacedBy(trackTxid);
|
2022-12-09 14:35:51 -06:00
|
|
|
if (rbfCacheTxid) {
|
2023-06-12 15:31:47 -04:00
|
|
|
response['txReplaced'] = JSON.stringify({
|
2022-12-09 14:35:51 -06:00
|
|
|
txid: rbfCacheTxid,
|
2023-06-12 15:31:47 -04:00
|
|
|
});
|
2022-03-08 14:49:25 +01:00
|
|
|
client['track-tx'] = null;
|
|
|
|
} else {
|
|
|
|
// It might have appeared before we had the time to start watching for it
|
2023-04-21 08:40:21 +09:00
|
|
|
const tx = memPool.getMempool()[trackTxid];
|
2022-03-08 14:49:25 +01:00
|
|
|
if (tx) {
|
|
|
|
if (config.MEMPOOL.BACKEND === 'esplora') {
|
2023-06-12 15:31:47 -04:00
|
|
|
response['tx'] = JSON.stringify(tx);
|
2022-03-08 14:49:25 +01:00
|
|
|
} else {
|
|
|
|
// tx.prevout is missing from transactions when in bitcoind mode
|
|
|
|
try {
|
2023-05-29 15:56:29 -04:00
|
|
|
const fullTx = await transactionUtils.$getMempoolTransactionExtended(tx.txid, true);
|
2023-06-12 15:31:47 -04:00
|
|
|
response['tx'] = JSON.stringify(fullTx);
|
2022-03-08 14:49:25 +01:00
|
|
|
} catch (e) {
|
|
|
|
logger.debug('Error finding transaction: ' + (e instanceof Error ? e.message : e));
|
|
|
|
}
|
|
|
|
}
|
2021-08-09 13:01:29 +03:00
|
|
|
} else {
|
2021-04-27 02:13:48 +04:00
|
|
|
try {
|
2023-05-29 15:56:29 -04:00
|
|
|
const fullTx = await transactionUtils.$getMempoolTransactionExtended(client['track-tx'], true);
|
2023-06-12 15:31:47 -04:00
|
|
|
response['tx'] = JSON.stringify(fullTx);
|
2021-04-27 02:13:48 +04:00
|
|
|
} catch (e) {
|
2022-03-08 14:49:25 +01:00
|
|
|
logger.debug('Error finding transaction. ' + (e instanceof Error ? e.message : e));
|
|
|
|
client['track-mempool-tx'] = parsedMessage['track-tx'];
|
2021-04-27 02:13:48 +04:00
|
|
|
}
|
|
|
|
}
|
2020-04-13 02:06:10 +07:00
|
|
|
}
|
|
|
|
}
|
2023-04-21 08:40:21 +09:00
|
|
|
const tx = memPool.getMempool()[trackTxid];
|
|
|
|
if (tx && tx.position) {
|
2023-06-12 15:31:47 -04:00
|
|
|
response['txPosition'] = JSON.stringify({
|
2023-04-21 08:40:21 +09:00
|
|
|
txid: trackTxid,
|
|
|
|
position: tx.position,
|
2023-06-12 15:31:47 -04:00
|
|
|
});
|
2023-04-21 08:40:21 +09:00
|
|
|
}
|
2020-02-26 17:49:53 +07:00
|
|
|
} else {
|
|
|
|
client['track-tx'] = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parsedMessage && parsedMessage['track-address']) {
|
2023-07-28 16:35:42 +09:00
|
|
|
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}|04[a-fA-F0-9]{128}|(02|03)[a-fA-F0-9]{64})$/
|
2020-02-26 17:49:53 +07:00
|
|
|
.test(parsedMessage['track-address'])) {
|
2021-09-05 00:30:24 +04:00
|
|
|
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();
|
|
|
|
}
|
2023-07-28 16:35:42 +09:00
|
|
|
if (/^04[a-fA-F0-9]{128}$/.test(parsedMessage['track-address'])) {
|
2023-07-26 10:47:59 +09:00
|
|
|
client['track-address'] = null;
|
2023-07-28 16:04:03 +09:00
|
|
|
client['track-scriptpubkey'] = '41' + matchedAddress + 'ac';
|
2023-07-28 16:35:42 +09:00
|
|
|
} else if (/^|(02|03)[a-fA-F0-9]{64}$/.test(parsedMessage['track-address'])) {
|
|
|
|
client['track-address'] = null;
|
|
|
|
client['track-scriptpubkey'] = '21' + matchedAddress + 'ac';
|
2023-07-26 10:47:59 +09:00
|
|
|
} else {
|
|
|
|
client['track-address'] = matchedAddress;
|
2023-07-28 16:04:03 +09:00
|
|
|
client['track-scriptpubkey'] = null;
|
2023-07-26 10:47:59 +09:00
|
|
|
}
|
2020-02-26 17:49:53 +07:00
|
|
|
} else {
|
|
|
|
client['track-address'] = null;
|
2023-07-28 16:04:03 +09:00
|
|
|
client['track-scriptpubkey'] = null;
|
2020-02-26 17:49:53 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-05 15:26:23 +07:00
|
|
|
if (parsedMessage && parsedMessage['track-asset']) {
|
|
|
|
if (/^[a-fA-F0-9]{64}$/.test(parsedMessage['track-asset'])) {
|
|
|
|
client['track-asset'] = parsedMessage['track-asset'];
|
|
|
|
} else {
|
|
|
|
client['track-asset'] = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-02 01:29:03 +04:00
|
|
|
if (parsedMessage && parsedMessage['track-mempool-block'] !== undefined) {
|
2022-05-30 17:29:30 +00:00
|
|
|
if (Number.isInteger(parsedMessage['track-mempool-block']) && parsedMessage['track-mempool-block'] >= 0) {
|
|
|
|
const index = parsedMessage['track-mempool-block'];
|
|
|
|
client['track-mempool-block'] = index;
|
|
|
|
const mBlocksWithTransactions = mempoolBlocks.getMempoolBlocksWithTransactions();
|
2023-06-12 15:31:47 -04:00
|
|
|
response['projected-block-transactions'] = JSON.stringify({
|
2022-06-06 14:31:17 +04:00
|
|
|
index: index,
|
|
|
|
blockTransactions: mBlocksWithTransactions[index]?.transactions || [],
|
2023-06-12 15:31:47 -04:00
|
|
|
});
|
2022-05-30 17:29:30 +00:00
|
|
|
} else {
|
|
|
|
client['track-mempool-block'] = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-14 16:51:53 -06:00
|
|
|
if (parsedMessage && parsedMessage['track-rbf'] !== undefined) {
|
|
|
|
if (['all', 'fullRbf'].includes(parsedMessage['track-rbf'])) {
|
|
|
|
client['track-rbf'] = parsedMessage['track-rbf'];
|
2023-06-12 15:31:47 -04:00
|
|
|
response['rbfLatest'] = JSON.stringify(rbfCache.getRbfTrees(parsedMessage['track-rbf'] === 'fullRbf'));
|
2022-12-14 16:51:53 -06:00
|
|
|
} else {
|
|
|
|
client['track-rbf'] = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-14 16:08:57 +09:00
|
|
|
if (parsedMessage && parsedMessage['track-rbf-summary'] != null) {
|
|
|
|
if (parsedMessage['track-rbf-summary']) {
|
|
|
|
client['track-rbf-summary'] = true;
|
2023-07-17 17:53:26 +09:00
|
|
|
if (this.socketData['rbfSummary'] != null) {
|
|
|
|
response['rbfLatestSummary'] = this.socketData['rbfSummary'];
|
|
|
|
}
|
2023-07-14 16:08:57 +09:00
|
|
|
} else {
|
|
|
|
client['track-rbf-summary'] = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-26 17:49:53 +07:00
|
|
|
if (parsedMessage.action === 'init') {
|
2023-07-16 18:39:51 +09:00
|
|
|
if (!this.socketData['blocks']?.length || !this.socketData['da'] || !this.socketData['backendInfo'] || !this.socketData['conversions']) {
|
2023-06-12 15:31:47 -04:00
|
|
|
this.updateSocketData();
|
2023-05-09 19:48:02 -06:00
|
|
|
}
|
2023-06-12 15:31:47 -04:00
|
|
|
if (!this.socketData['blocks']?.length) {
|
2020-02-26 17:49:53 +07:00
|
|
|
return;
|
|
|
|
}
|
2023-05-09 19:48:02 -06:00
|
|
|
client.send(this.serializedInitData);
|
2020-02-26 17:49:53 +07:00
|
|
|
}
|
2020-03-06 02:05:26 +07:00
|
|
|
|
|
|
|
if (parsedMessage.action === 'ping') {
|
2023-06-12 15:31:47 -04:00
|
|
|
response['pong'] = JSON.stringify(true);
|
2020-04-13 02:06:10 +07:00
|
|
|
}
|
|
|
|
|
2020-10-07 20:15:42 +07:00
|
|
|
if (parsedMessage['track-donation'] && parsedMessage['track-donation'].length === 22) {
|
|
|
|
client['track-donation'] = parsedMessage['track-donation'];
|
|
|
|
}
|
|
|
|
|
2021-03-05 15:38:46 +07:00
|
|
|
if (parsedMessage['track-bisq-market']) {
|
|
|
|
if (/^[a-z]{3}_[a-z]{3}$/.test(parsedMessage['track-bisq-market'])) {
|
|
|
|
client['track-bisq-market'] = parsedMessage['track-bisq-market'];
|
|
|
|
} else {
|
|
|
|
client['track-bisq-market'] = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-13 02:06:10 +07:00
|
|
|
if (Object.keys(response).length) {
|
2023-06-12 15:31:47 -04:00
|
|
|
const serializedResponse = this.serializeResponse(response);
|
|
|
|
client.send(serializedResponse);
|
2020-03-06 02:05:26 +07:00
|
|
|
}
|
2020-02-26 17:49:53 +07:00
|
|
|
} catch (e) {
|
2021-08-31 15:09:33 +03:00
|
|
|
logger.debug('Error parsing websocket message: ' + (e instanceof Error ? e.message : e));
|
2020-02-26 17:49:53 +07:00
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-10-07 20:15:42 +07:00
|
|
|
handleNewDonation(id: string) {
|
|
|
|
if (!this.wss) {
|
|
|
|
throw new Error('WebSocket.Server is not set');
|
|
|
|
}
|
|
|
|
|
2022-07-04 08:37:36 -07:00
|
|
|
this.wss.clients.forEach((client) => {
|
2020-10-07 20:15:42 +07:00
|
|
|
if (client.readyState !== WebSocket.OPEN) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (client['track-donation'] === id) {
|
|
|
|
client.send(JSON.stringify({ donationConfirmed: true }));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-01-05 18:57:06 +07:00
|
|
|
handleLoadingChanged(indicators: ILoadingIndicators) {
|
|
|
|
if (!this.wss) {
|
|
|
|
throw new Error('WebSocket.Server is not set');
|
|
|
|
}
|
|
|
|
|
2023-06-12 15:31:47 -04:00
|
|
|
this.updateSocketDataFields({ 'loadingIndicators': indicators });
|
2023-05-09 19:48:02 -06:00
|
|
|
|
2023-05-09 17:44:38 -06:00
|
|
|
const response = JSON.stringify({ loadingIndicators: indicators });
|
2022-07-04 08:37:36 -07:00
|
|
|
this.wss.clients.forEach((client) => {
|
2021-01-05 18:57:06 +07:00
|
|
|
if (client.readyState !== WebSocket.OPEN) {
|
|
|
|
return;
|
|
|
|
}
|
2023-05-09 17:44:38 -06:00
|
|
|
client.send(response);
|
2021-01-05 18:57:06 +07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-03-04 10:51:13 +09:00
|
|
|
handleNewConversionRates(conversionRates: ApiPrice) {
|
2021-01-06 23:31:33 +07:00
|
|
|
if (!this.wss) {
|
|
|
|
throw new Error('WebSocket.Server is not set');
|
|
|
|
}
|
|
|
|
|
2023-06-12 15:31:47 -04:00
|
|
|
this.updateSocketDataFields({ 'conversions': conversionRates });
|
2023-05-09 19:48:02 -06:00
|
|
|
|
2023-05-09 17:44:38 -06:00
|
|
|
const response = JSON.stringify({ conversions: conversionRates });
|
2022-07-04 08:37:36 -07:00
|
|
|
this.wss.clients.forEach((client) => {
|
2021-01-06 23:31:33 +07:00
|
|
|
if (client.readyState !== WebSocket.OPEN) {
|
|
|
|
return;
|
|
|
|
}
|
2023-05-09 17:44:38 -06:00
|
|
|
client.send(response);
|
2021-01-06 23:31:33 +07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-06-09 02:08:46 +07:00
|
|
|
handleNewStatistic(stats: OptimizedStatistic) {
|
2020-02-26 17:49:53 +07:00
|
|
|
if (!this.wss) {
|
|
|
|
throw new Error('WebSocket.Server is not set');
|
|
|
|
}
|
|
|
|
|
2023-05-01 13:08:29 -06:00
|
|
|
this.printLogs();
|
|
|
|
|
2023-05-09 17:44:38 -06:00
|
|
|
const response = JSON.stringify({
|
|
|
|
'live-2h-chart': stats
|
|
|
|
});
|
|
|
|
|
2022-07-04 08:37:36 -07:00
|
|
|
this.wss.clients.forEach((client) => {
|
2020-02-26 17:49:53 +07:00
|
|
|
if (client.readyState !== WebSocket.OPEN) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!client['want-live-2h-chart']) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-05-09 17:44:38 -06:00
|
|
|
client.send(response);
|
2020-02-26 17:49:53 +07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-07-08 01:07:06 -04:00
|
|
|
handleReorg(): void {
|
|
|
|
if (!this.wss) {
|
|
|
|
throw new Error('WebSocket.Server is not set');
|
|
|
|
}
|
|
|
|
|
|
|
|
const da = difficultyAdjustment.getDifficultyAdjustment();
|
|
|
|
|
|
|
|
// update init data
|
|
|
|
this.updateSocketDataFields({
|
|
|
|
'blocks': blocks.getBlocks(),
|
|
|
|
'da': da?.previousTime ? da : undefined,
|
|
|
|
});
|
|
|
|
|
|
|
|
this.wss.clients.forEach((client) => {
|
|
|
|
if (client.readyState !== WebSocket.OPEN) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const response = {};
|
|
|
|
|
|
|
|
if (client['want-blocks']) {
|
|
|
|
response['blocks'] = this.socketData['blocks'];
|
|
|
|
}
|
|
|
|
if (client['want-stats']) {
|
|
|
|
response['da'] = this.socketData['da'];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Object.keys(response).length) {
|
|
|
|
const serializedResponse = this.serializeResponse(response);
|
|
|
|
client.send(serializedResponse);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-06-28 19:51:52 -04:00
|
|
|
async $handleMempoolChange(newMempool: { [txid: string]: MempoolTransactionExtended }, mempoolSize: number,
|
2023-05-29 15:56:29 -04:00
|
|
|
newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[]): Promise<void> {
|
2020-02-26 17:49:53 +07:00
|
|
|
if (!this.wss) {
|
|
|
|
throw new Error('WebSocket.Server is not set');
|
|
|
|
}
|
|
|
|
|
2023-05-01 13:08:29 -06:00
|
|
|
this.printLogs();
|
|
|
|
|
2022-11-30 17:56:53 +09:00
|
|
|
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
|
2023-06-25 20:35:41 -04:00
|
|
|
if (config.MEMPOOL.RUST_GBT) {
|
2023-06-28 19:51:52 -04:00
|
|
|
await mempoolBlocks.$rustUpdateBlockTemplates(newMempool, mempoolSize, newTransactions, deletedTransactions);
|
2023-06-25 20:35:41 -04:00
|
|
|
} else {
|
|
|
|
await mempoolBlocks.$updateBlockTemplates(newMempool, newTransactions, deletedTransactions, true);
|
|
|
|
}
|
2022-11-30 17:56:53 +09:00
|
|
|
} else {
|
2023-01-30 16:26:37 -06:00
|
|
|
mempoolBlocks.updateMempoolBlocks(newMempool, true);
|
2022-11-16 18:18:59 -06:00
|
|
|
}
|
2022-11-30 17:56:53 +09:00
|
|
|
|
2022-11-20 16:12:39 +09:00
|
|
|
const mBlocks = mempoolBlocks.getMempoolBlocks();
|
|
|
|
const mBlockDeltas = mempoolBlocks.getMempoolBlockDeltas();
|
2020-02-26 17:49:53 +07:00
|
|
|
const mempoolInfo = memPool.getMempoolInfo();
|
|
|
|
const vBytesPerSecond = memPool.getVBytesPerSecond();
|
2020-06-08 18:55:53 +07:00
|
|
|
const rbfTransactions = Common.findRbfTransactions(newTransactions, deletedTransactions);
|
2022-03-12 14:55:42 +01:00
|
|
|
const da = difficultyAdjustment.getDifficultyAdjustment();
|
2022-03-08 14:49:25 +01:00
|
|
|
memPool.handleRbfTransactions(rbfTransactions);
|
2022-12-14 08:49:35 -06:00
|
|
|
const rbfChanges = rbfCache.getRbfChanges();
|
2022-12-14 16:51:53 -06:00
|
|
|
let rbfReplacements;
|
|
|
|
let fullRbfReplacements;
|
2023-07-14 16:08:57 +09:00
|
|
|
let rbfSummary;
|
2022-12-17 09:39:06 -06:00
|
|
|
if (Object.keys(rbfChanges.trees).length) {
|
|
|
|
rbfReplacements = rbfCache.getRbfTrees(false);
|
|
|
|
fullRbfReplacements = rbfCache.getRbfTrees(true);
|
2023-07-14 16:08:57 +09:00
|
|
|
rbfSummary = rbfCache.getLatestRbfSummary();
|
2022-12-14 16:51:53 -06:00
|
|
|
}
|
2023-07-14 16:08:57 +09:00
|
|
|
|
2023-05-04 19:10:53 -04:00
|
|
|
for (const deletedTx of deletedTransactions) {
|
|
|
|
rbfCache.evict(deletedTx.txid);
|
|
|
|
}
|
2023-05-18 09:51:41 -04:00
|
|
|
memPool.removeFromSpendMap(deletedTransactions);
|
|
|
|
memPool.addToSpendMap(newTransactions);
|
2022-05-27 12:52:40 +02:00
|
|
|
const recommendedFees = feeApi.getRecommendedFee();
|
2021-03-21 06:06:03 +07:00
|
|
|
|
2023-07-17 11:02:28 +09:00
|
|
|
const latestTransactions = memPool.getLatestTransactions();
|
2023-06-12 15:31:47 -04:00
|
|
|
|
2023-05-09 19:48:02 -06:00
|
|
|
// update init data
|
2023-07-14 16:08:57 +09:00
|
|
|
const socketDataFields = {
|
2023-06-12 15:31:47 -04:00
|
|
|
'mempoolInfo': mempoolInfo,
|
|
|
|
'vBytesPerSecond': vBytesPerSecond,
|
|
|
|
'mempool-blocks': mBlocks,
|
|
|
|
'transactions': latestTransactions,
|
|
|
|
'loadingIndicators': loadingIndicators.getLoadingIndicators(),
|
|
|
|
'da': da?.previousTime ? da : undefined,
|
|
|
|
'fees': recommendedFees,
|
2023-07-14 16:08:57 +09:00
|
|
|
};
|
|
|
|
if (rbfSummary) {
|
|
|
|
socketDataFields['rbfSummary'] = rbfSummary;
|
|
|
|
}
|
|
|
|
this.updateSocketDataFields(socketDataFields);
|
2023-05-09 19:48:02 -06:00
|
|
|
|
2023-05-09 17:44:38 -06:00
|
|
|
// cache serialized objects to avoid stringify-ing the same thing for every client
|
2023-06-12 15:31:47 -04:00
|
|
|
const responseCache = { ...this.socketData };
|
2023-05-09 17:44:38 -06:00
|
|
|
function getCachedResponse(key: string, data): string {
|
|
|
|
if (!responseCache[key]) {
|
|
|
|
responseCache[key] = JSON.stringify(data);
|
|
|
|
}
|
|
|
|
return responseCache[key];
|
|
|
|
}
|
|
|
|
|
|
|
|
// pre-compute new tracked outspends
|
|
|
|
const outspendCache: { [txid: string]: { [vout: number]: { vin: number, txid: string } } } = {};
|
|
|
|
const trackedTxs = new Set<string>();
|
|
|
|
this.wss.clients.forEach((client) => {
|
|
|
|
if (client['track-tx']) {
|
|
|
|
trackedTxs.add(client['track-tx']);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (trackedTxs.size > 0) {
|
|
|
|
for (const tx of newTransactions) {
|
|
|
|
for (let i = 0; i < tx.vin.length; i++) {
|
|
|
|
const vin = tx.vin[i];
|
|
|
|
if (trackedTxs.has(vin.txid)) {
|
|
|
|
if (!outspendCache[vin.txid]) {
|
|
|
|
outspendCache[vin.txid] = { [vin.vout]: { vin: i, txid: tx.txid }};
|
|
|
|
} else {
|
|
|
|
outspendCache[vin.txid][vin.vout] = { vin: i, txid: tx.txid };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-04 08:37:36 -07:00
|
|
|
this.wss.clients.forEach(async (client) => {
|
2020-02-26 17:49:53 +07:00
|
|
|
if (client.readyState !== WebSocket.OPEN) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const response = {};
|
|
|
|
|
|
|
|
if (client['want-stats']) {
|
2023-05-09 17:44:38 -06:00
|
|
|
response['mempoolInfo'] = getCachedResponse('mempoolInfo', mempoolInfo);
|
|
|
|
response['vBytesPerSecond'] = getCachedResponse('vBytesPerSecond', vBytesPerSecond);
|
2023-05-09 19:48:02 -06:00
|
|
|
response['transactions'] = getCachedResponse('transactions', latestTransactions);
|
2023-03-26 09:05:41 +09:00
|
|
|
if (da?.previousTime) {
|
2023-05-09 17:44:38 -06:00
|
|
|
response['da'] = getCachedResponse('da', da);
|
2023-03-26 09:05:41 +09:00
|
|
|
}
|
2023-05-09 17:44:38 -06:00
|
|
|
response['fees'] = getCachedResponse('fees', recommendedFees);
|
2020-02-26 17:49:53 +07:00
|
|
|
}
|
|
|
|
|
2022-11-20 16:12:39 +09:00
|
|
|
if (client['want-mempool-blocks']) {
|
2023-05-09 17:44:38 -06:00
|
|
|
response['mempool-blocks'] = getCachedResponse('mempool-blocks', mBlocks);
|
2020-02-26 17:49:53 +07:00
|
|
|
}
|
|
|
|
|
2020-04-13 02:06:10 +07:00
|
|
|
if (client['track-mempool-tx']) {
|
|
|
|
const tx = newTransactions.find((t) => t.txid === client['track-mempool-tx']);
|
2020-04-13 01:26:53 +07:00
|
|
|
if (tx) {
|
2021-01-10 17:38:59 +07:00
|
|
|
if (config.MEMPOOL.BACKEND !== 'esplora') {
|
2021-01-24 02:51:22 +07:00
|
|
|
try {
|
2023-05-29 15:56:29 -04:00
|
|
|
const fullTx = await transactionUtils.$getMempoolTransactionExtended(tx.txid, true);
|
2023-05-09 17:44:38 -06:00
|
|
|
response['tx'] = JSON.stringify(fullTx);
|
2021-01-24 02:51:22 +07:00
|
|
|
} catch (e) {
|
2021-08-31 15:09:33 +03:00
|
|
|
logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e));
|
2021-01-10 17:38:59 +07:00
|
|
|
}
|
|
|
|
} else {
|
2023-05-09 17:44:38 -06:00
|
|
|
response['tx'] = JSON.stringify(tx);
|
2021-01-10 17:38:59 +07:00
|
|
|
}
|
2020-04-13 02:06:10 +07:00
|
|
|
client['track-mempool-tx'] = null;
|
2020-04-13 01:26:53 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-20 17:00:50 +07:00
|
|
|
if (client['track-address']) {
|
|
|
|
const foundTransactions: TransactionExtended[] = [];
|
|
|
|
|
2021-01-10 17:38:59 +07:00
|
|
|
for (const tx of newTransactions) {
|
2020-05-20 17:00:50 +07:00
|
|
|
const someVin = tx.vin.some((vin) => !!vin.prevout && vin.prevout.scriptpubkey_address === client['track-address']);
|
|
|
|
if (someVin) {
|
2021-01-10 17:38:59 +07:00
|
|
|
if (config.MEMPOOL.BACKEND !== 'esplora') {
|
2021-01-24 02:51:22 +07:00
|
|
|
try {
|
2023-05-29 15:56:29 -04:00
|
|
|
const fullTx = await transactionUtils.$getMempoolTransactionExtended(tx.txid, true);
|
2021-01-10 17:38:59 +07:00
|
|
|
foundTransactions.push(fullTx);
|
2021-01-24 02:51:22 +07:00
|
|
|
} catch (e) {
|
2021-08-31 15:09:33 +03:00
|
|
|
logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e));
|
2021-01-10 17:38:59 +07:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
foundTransactions.push(tx);
|
|
|
|
}
|
2020-05-20 17:00:50 +07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
const someVout = tx.vout.some((vout) => vout.scriptpubkey_address === client['track-address']);
|
|
|
|
if (someVout) {
|
2021-01-10 17:38:59 +07:00
|
|
|
if (config.MEMPOOL.BACKEND !== 'esplora') {
|
2021-01-24 02:51:22 +07:00
|
|
|
try {
|
2023-05-29 15:56:29 -04:00
|
|
|
const fullTx = await transactionUtils.$getMempoolTransactionExtended(tx.txid, true);
|
2021-01-10 17:38:59 +07:00
|
|
|
foundTransactions.push(fullTx);
|
2021-01-24 02:51:22 +07:00
|
|
|
} catch (e) {
|
2021-08-31 15:09:33 +03:00
|
|
|
logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e));
|
2021-01-10 17:38:59 +07:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
foundTransactions.push(tx);
|
|
|
|
}
|
2020-05-20 17:00:50 +07:00
|
|
|
}
|
2021-01-10 17:38:59 +07:00
|
|
|
}
|
2020-05-20 17:00:50 +07:00
|
|
|
|
|
|
|
if (foundTransactions.length) {
|
2023-05-09 17:44:38 -06:00
|
|
|
response['address-transactions'] = JSON.stringify(foundTransactions);
|
2020-05-20 17:00:50 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-28 16:04:03 +09:00
|
|
|
if (client['track-scriptpubkey']) {
|
2023-07-26 10:47:59 +09:00
|
|
|
const foundTransactions: TransactionExtended[] = [];
|
|
|
|
|
|
|
|
for (const tx of newTransactions) {
|
2023-07-28 16:04:03 +09:00
|
|
|
const someVin = tx.vin.some((vin) => !!vin.prevout && vin.prevout.scriptpubkey_type === 'p2pk' && vin.prevout.scriptpubkey === client['track-scriptpubkey']);
|
2023-07-26 10:47:59 +09:00
|
|
|
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;
|
|
|
|
}
|
2023-07-28 16:04:03 +09:00
|
|
|
const someVout = tx.vout.some((vout) => vout.scriptpubkey_type === 'p2pk' && vout.scriptpubkey === client['track-scriptpubkey']);
|
2023-07-26 10:47:59 +09:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-05 15:26:23 +07:00
|
|
|
if (client['track-asset']) {
|
2020-02-26 17:49:53 +07:00
|
|
|
const foundTransactions: TransactionExtended[] = [];
|
|
|
|
|
|
|
|
newTransactions.forEach((tx) => {
|
2020-05-10 01:34:28 +07:00
|
|
|
|
2021-09-18 13:37:25 +04:00
|
|
|
if (client['track-asset'] === Common.nativeAssetId) {
|
2020-05-10 01:34:28 +07:00
|
|
|
if (tx.vin.some((vin) => !!vin.is_pegin)) {
|
|
|
|
foundTransactions.push(tx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (tx.vout.some((vout) => !!vout.pegout)) {
|
|
|
|
foundTransactions.push(tx);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (tx.vin.some((vin) => !!vin.issuance && vin.issuance.asset_id === client['track-asset'])) {
|
|
|
|
foundTransactions.push(tx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (tx.vout.some((vout) => !!vout.asset && vout.asset === client['track-asset'])) {
|
|
|
|
foundTransactions.push(tx);
|
|
|
|
}
|
2020-02-26 17:49:53 +07:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (foundTransactions.length) {
|
2023-05-09 17:44:38 -06:00
|
|
|
response['address-transactions'] = JSON.stringify(foundTransactions);
|
2020-02-26 17:49:53 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-06 18:27:13 +01:00
|
|
|
if (client['track-tx']) {
|
2023-04-21 08:40:21 +09:00
|
|
|
const trackTxid = client['track-tx'];
|
2023-05-09 17:44:38 -06:00
|
|
|
const outspends = outspendCache[trackTxid];
|
2022-03-07 19:45:09 +01:00
|
|
|
|
2023-05-09 17:44:38 -06:00
|
|
|
if (outspends && Object.keys(outspends).length) {
|
|
|
|
response['utxoSpent'] = JSON.stringify(outspends);
|
2022-03-06 18:27:13 +01:00
|
|
|
}
|
|
|
|
|
2022-12-17 09:39:06 -06:00
|
|
|
const rbfReplacedBy = rbfCache.getReplacedBy(client['track-tx']);
|
|
|
|
if (rbfReplacedBy) {
|
2023-05-09 17:44:38 -06:00
|
|
|
response['rbfTransaction'] = JSON.stringify({
|
2022-12-17 09:39:06 -06:00
|
|
|
txid: rbfReplacedBy,
|
2023-06-12 15:31:47 -04:00
|
|
|
});
|
2020-06-08 18:55:53 +07:00
|
|
|
}
|
2022-12-14 08:49:35 -06:00
|
|
|
|
|
|
|
const rbfChange = rbfChanges.map[client['track-tx']];
|
|
|
|
if (rbfChange) {
|
2023-05-09 17:44:38 -06:00
|
|
|
response['rbfInfo'] = JSON.stringify(rbfChanges.trees[rbfChange]);
|
2022-12-14 08:49:35 -06:00
|
|
|
}
|
2023-04-21 08:40:21 +09:00
|
|
|
|
|
|
|
const mempoolTx = newMempool[trackTxid];
|
|
|
|
if (mempoolTx && mempoolTx.position) {
|
2023-05-09 17:44:38 -06:00
|
|
|
response['txPosition'] = JSON.stringify({
|
2023-04-21 08:40:21 +09:00
|
|
|
txid: trackTxid,
|
|
|
|
position: mempoolTx.position,
|
2023-05-09 17:44:38 -06:00
|
|
|
});
|
2023-04-21 08:40:21 +09:00
|
|
|
}
|
2020-06-08 18:55:53 +07:00
|
|
|
}
|
|
|
|
|
2023-07-24 17:22:38 +09:00
|
|
|
if (client['track-mempool-block'] >= 0 && memPool.isInSync()) {
|
2022-05-30 17:29:30 +00:00
|
|
|
const index = client['track-mempool-block'];
|
2022-05-31 21:36:42 +00:00
|
|
|
if (mBlockDeltas[index]) {
|
2023-05-09 17:44:38 -06:00
|
|
|
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-${index}`, {
|
2022-05-30 17:29:30 +00:00
|
|
|
index: index,
|
2022-05-31 21:36:42 +00:00
|
|
|
delta: mBlockDeltas[index],
|
2023-05-09 17:44:38 -06:00
|
|
|
});
|
2022-05-30 17:29:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-14 16:51:53 -06:00
|
|
|
if (client['track-rbf'] === 'all' && rbfReplacements) {
|
2023-05-09 17:44:38 -06:00
|
|
|
response['rbfLatest'] = getCachedResponse('rbfLatest', rbfReplacements);
|
2022-12-14 16:51:53 -06:00
|
|
|
} else if (client['track-rbf'] === 'fullRbf' && fullRbfReplacements) {
|
2023-05-09 17:44:38 -06:00
|
|
|
response['rbfLatest'] = getCachedResponse('fullrbfLatest', fullRbfReplacements);
|
2022-12-14 16:51:53 -06:00
|
|
|
}
|
|
|
|
|
2023-07-14 16:08:57 +09:00
|
|
|
if (client['track-rbf-summary'] && rbfSummary) {
|
|
|
|
response['rbfLatestSummary'] = getCachedResponse('rbfLatestSummary', rbfSummary);
|
|
|
|
}
|
|
|
|
|
2020-02-26 17:49:53 +07:00
|
|
|
if (Object.keys(response).length) {
|
2023-06-12 15:31:47 -04:00
|
|
|
const serializedResponse = this.serializeResponse(response);
|
2023-05-09 17:44:38 -06:00
|
|
|
client.send(serializedResponse);
|
2020-02-26 17:49:53 +07:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2022-11-20 16:12:39 +09:00
|
|
|
|
2023-06-25 20:37:42 -04:00
|
|
|
async handleNewBlock(block: BlockExtended, txIds: string[], transactions: MempoolTransactionExtended[]): Promise<void> {
|
2022-11-16 18:18:59 -06:00
|
|
|
if (!this.wss) {
|
|
|
|
throw new Error('WebSocket.Server is not set');
|
|
|
|
}
|
|
|
|
|
2023-05-01 13:08:29 -06:00
|
|
|
this.printLogs();
|
|
|
|
|
2022-11-16 18:18:59 -06:00
|
|
|
const _memPool = memPool.getMempool();
|
|
|
|
|
2023-06-19 18:14:09 -04:00
|
|
|
const rbfTransactions = Common.findMinedRbfTransactions(transactions, memPool.getSpendMap());
|
|
|
|
memPool.handleMinedRbfTransactions(rbfTransactions);
|
|
|
|
memPool.removeFromSpendMap(transactions);
|
|
|
|
|
2023-07-24 16:19:19 +09:00
|
|
|
if (config.MEMPOOL.AUDIT && memPool.isInSync()) {
|
2023-02-12 21:43:12 -06:00
|
|
|
let projectedBlocks;
|
2023-05-02 16:57:10 -06:00
|
|
|
let auditMempool = _memPool;
|
2023-02-12 21:43:12 -06:00
|
|
|
// template calculation functions have mempool side effects, so calculate audits using
|
|
|
|
// a cloned copy of the mempool if we're running a different algorithm for mempool updates
|
2023-05-02 16:57:10 -06:00
|
|
|
const separateAudit = config.MEMPOOL.ADVANCED_GBT_AUDIT !== config.MEMPOOL.ADVANCED_GBT_MEMPOOL;
|
|
|
|
if (separateAudit) {
|
|
|
|
auditMempool = deepClone(_memPool);
|
|
|
|
if (config.MEMPOOL.ADVANCED_GBT_AUDIT) {
|
2023-06-25 20:35:41 -04:00
|
|
|
if (config.MEMPOOL.RUST_GBT) {
|
2023-06-25 20:37:42 -04:00
|
|
|
projectedBlocks = await mempoolBlocks.$oneOffRustBlockTemplates(auditMempool);
|
2023-06-25 20:35:41 -04:00
|
|
|
} else {
|
|
|
|
projectedBlocks = await mempoolBlocks.$makeBlockTemplates(auditMempool, false);
|
|
|
|
}
|
2023-05-02 16:57:10 -06:00
|
|
|
} else {
|
|
|
|
projectedBlocks = mempoolBlocks.updateMempoolBlocks(auditMempool, false);
|
|
|
|
}
|
2023-02-12 21:43:12 -06:00
|
|
|
} else {
|
2023-05-02 16:57:10 -06:00
|
|
|
projectedBlocks = mempoolBlocks.getMempoolBlocksWithTransactions();
|
2023-02-12 21:43:12 -06:00
|
|
|
}
|
2020-02-26 17:49:53 +07:00
|
|
|
|
2023-07-24 16:19:19 +09:00
|
|
|
if (Common.indexingEnabled()) {
|
2023-06-19 18:14:09 -04:00
|
|
|
const { censored, added, fresh, sigop, fullrbf, score, similarity } = Audit.auditBlock(transactions, projectedBlocks, auditMempool);
|
2023-02-12 21:43:12 -06:00
|
|
|
const matchRate = Math.round(score * 100 * 100) / 100;
|
2020-06-08 02:08:51 +07:00
|
|
|
|
2023-06-29 19:24:19 -04:00
|
|
|
const stripped = projectedBlocks[0]?.transactions ? projectedBlocks[0].transactions : [];
|
2022-07-07 19:11:42 +02:00
|
|
|
|
2023-06-09 13:46:14 -04:00
|
|
|
let totalFees = 0;
|
|
|
|
let totalWeight = 0;
|
|
|
|
for (const tx of stripped) {
|
|
|
|
totalFees += tx.fee;
|
|
|
|
totalWeight += (tx.vsize * 4);
|
|
|
|
}
|
2023-06-05 13:20:46 +02:00
|
|
|
|
2023-02-12 21:43:12 -06:00
|
|
|
BlocksSummariesRepository.$saveTemplate({
|
|
|
|
height: block.height,
|
|
|
|
template: {
|
|
|
|
id: block.id,
|
2023-06-05 13:20:46 +02:00
|
|
|
transactions: stripped,
|
2023-02-12 21:43:12 -06:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
BlocksAuditsRepository.$saveAudit({
|
|
|
|
time: block.timestamp,
|
|
|
|
height: block.height,
|
|
|
|
hash: block.id,
|
|
|
|
addedTxs: added,
|
|
|
|
missingTxs: censored,
|
|
|
|
freshTxs: fresh,
|
2023-05-17 11:46:50 -04:00
|
|
|
sigopTxs: sigop,
|
2023-06-19 18:14:09 -04:00
|
|
|
fullrbfTxs: fullrbf,
|
2023-02-12 21:43:12 -06:00
|
|
|
matchRate: matchRate,
|
2023-06-09 13:46:14 -04:00
|
|
|
expectedFees: totalFees,
|
|
|
|
expectedWeight: totalWeight,
|
2023-02-12 21:43:12 -06:00
|
|
|
});
|
|
|
|
|
|
|
|
if (block.extras) {
|
|
|
|
block.extras.matchRate = matchRate;
|
2023-06-06 08:52:29 +02:00
|
|
|
block.extras.expectedFees = totalFees;
|
2023-06-10 12:09:06 -04:00
|
|
|
block.extras.expectedWeight = totalWeight;
|
2023-03-14 10:19:32 +09:00
|
|
|
block.extras.similarity = similarity;
|
2023-02-12 21:43:12 -06:00
|
|
|
}
|
2022-07-06 22:27:45 +02:00
|
|
|
}
|
2023-03-14 14:52:34 +09:00
|
|
|
} else if (block.extras) {
|
|
|
|
const mBlocks = mempoolBlocks.getMempoolBlocksWithTransactions();
|
2023-03-16 22:15:20 +09:00
|
|
|
if (mBlocks?.length && mBlocks[0].transactions) {
|
|
|
|
block.extras.similarity = Common.getSimilarity(mBlocks[0], transactions);
|
|
|
|
}
|
2020-06-08 02:08:51 +07:00
|
|
|
}
|
|
|
|
|
2022-10-28 15:16:03 -06:00
|
|
|
// Update mempool to remove transactions included in the new block
|
|
|
|
for (const txId of txIds) {
|
|
|
|
delete _memPool[txId];
|
2022-12-14 16:51:53 -06:00
|
|
|
rbfCache.mined(txId);
|
2022-02-04 12:51:45 +09:00
|
|
|
}
|
2020-06-08 02:08:51 +07:00
|
|
|
|
2022-11-30 17:56:53 +09:00
|
|
|
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
|
2023-06-25 20:35:41 -04:00
|
|
|
if (config.MEMPOOL.RUST_GBT) {
|
2023-06-28 19:51:52 -04:00
|
|
|
await mempoolBlocks.$rustUpdateBlockTemplates(_memPool, Object.keys(_memPool).length, [], transactions);
|
2023-06-25 20:35:41 -04:00
|
|
|
} else {
|
|
|
|
await mempoolBlocks.$makeBlockTemplates(_memPool, true);
|
|
|
|
}
|
2022-11-20 16:12:39 +09:00
|
|
|
} else {
|
2023-01-30 16:26:37 -06:00
|
|
|
mempoolBlocks.updateMempoolBlocks(_memPool, true);
|
2022-11-16 18:18:59 -06:00
|
|
|
}
|
2022-11-20 16:12:39 +09:00
|
|
|
const mBlocks = mempoolBlocks.getMempoolBlocks();
|
|
|
|
const mBlockDeltas = mempoolBlocks.getMempoolBlockDeltas();
|
2022-10-28 15:16:03 -06:00
|
|
|
|
2022-06-01 00:03:25 +04:00
|
|
|
const da = difficultyAdjustment.getDifficultyAdjustment();
|
|
|
|
const fees = feeApi.getRecommendedFee();
|
2023-06-12 15:31:47 -04:00
|
|
|
const mempoolInfo = memPool.getMempoolInfo();
|
2022-06-01 00:03:25 +04:00
|
|
|
|
2023-05-09 19:48:02 -06:00
|
|
|
// update init data
|
2023-06-12 15:31:47 -04:00
|
|
|
this.updateSocketDataFields({
|
|
|
|
'mempoolInfo': mempoolInfo,
|
|
|
|
'blocks': [...blocks.getBlocks(), block].slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT),
|
|
|
|
'mempool-blocks': mBlocks,
|
|
|
|
'loadingIndicators': loadingIndicators.getLoadingIndicators(),
|
|
|
|
'da': da?.previousTime ? da : undefined,
|
|
|
|
'fees': fees,
|
|
|
|
});
|
2023-05-09 19:48:02 -06:00
|
|
|
|
2023-06-12 15:31:47 -04:00
|
|
|
const responseCache = { ...this.socketData };
|
2023-05-09 19:48:02 -06:00
|
|
|
function getCachedResponse(key, data): string {
|
2023-05-09 17:44:38 -06:00
|
|
|
if (!responseCache[key]) {
|
|
|
|
responseCache[key] = JSON.stringify(data);
|
|
|
|
}
|
|
|
|
return responseCache[key];
|
|
|
|
}
|
|
|
|
|
2020-02-26 17:49:53 +07:00
|
|
|
this.wss.clients.forEach((client) => {
|
|
|
|
if (client.readyState !== WebSocket.OPEN) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-06-12 15:31:47 -04:00
|
|
|
const response = {};
|
|
|
|
|
|
|
|
if (client['want-blocks']) {
|
|
|
|
response['block'] = getCachedResponse('block', block);
|
2020-02-26 17:49:53 +07:00
|
|
|
}
|
|
|
|
|
2023-06-12 15:31:47 -04:00
|
|
|
if (client['want-stats']) {
|
|
|
|
response['mempoolInfo'] = getCachedResponse('mempoolInfo', mempoolInfo);
|
|
|
|
response['vBytesPerSecond'] = getCachedResponse('vBytesPerSecond', memPool.getVBytesPerSecond());
|
|
|
|
response['fees'] = getCachedResponse('fees', fees);
|
|
|
|
|
|
|
|
if (da?.previousTime) {
|
|
|
|
response['da'] = getCachedResponse('da', da);
|
|
|
|
}
|
|
|
|
}
|
2020-02-26 17:49:53 +07:00
|
|
|
|
2022-11-20 16:12:39 +09:00
|
|
|
if (mBlocks && client['want-mempool-blocks']) {
|
2023-05-09 17:44:38 -06:00
|
|
|
response['mempool-blocks'] = getCachedResponse('mempool-blocks', mBlocks);
|
2020-06-08 02:08:51 +07:00
|
|
|
}
|
|
|
|
|
2023-04-21 08:40:21 +09:00
|
|
|
if (client['track-tx']) {
|
|
|
|
const trackTxid = client['track-tx'];
|
2023-05-30 16:36:49 -04:00
|
|
|
if (trackTxid && txIds.indexOf(trackTxid) > -1) {
|
|
|
|
response['txConfirmed'] = JSON.stringify(trackTxid);
|
2023-04-21 08:40:21 +09:00
|
|
|
} else {
|
|
|
|
const mempoolTx = _memPool[trackTxid];
|
|
|
|
if (mempoolTx && mempoolTx.position) {
|
2023-05-09 17:44:38 -06:00
|
|
|
response['txPosition'] = JSON.stringify({
|
2023-04-21 08:40:21 +09:00
|
|
|
txid: trackTxid,
|
|
|
|
position: mempoolTx.position,
|
2023-05-09 17:44:38 -06:00
|
|
|
});
|
2023-04-21 08:40:21 +09:00
|
|
|
}
|
|
|
|
}
|
2020-02-26 17:49:53 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (client['track-address']) {
|
|
|
|
const foundTransactions: TransactionExtended[] = [];
|
|
|
|
|
|
|
|
transactions.forEach((tx) => {
|
2020-03-09 17:53:54 +07:00
|
|
|
if (tx.vin && tx.vin.some((vin) => !!vin.prevout && vin.prevout.scriptpubkey_address === client['track-address'])) {
|
2020-02-26 17:49:53 +07:00
|
|
|
foundTransactions.push(tx);
|
|
|
|
return;
|
|
|
|
}
|
2020-05-20 17:00:50 +07:00
|
|
|
if (tx.vout && tx.vout.some((vout) => vout.scriptpubkey_address === client['track-address'])) {
|
2020-05-05 15:26:23 +07:00
|
|
|
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,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
2023-05-09 17:44:38 -06:00
|
|
|
response['block-transactions'] = JSON.stringify(foundTransactions);
|
2020-05-05 15:26:23 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-28 16:04:03 +09:00
|
|
|
if (client['track-scriptpubkey']) {
|
2023-07-26 10:47:59 +09:00
|
|
|
const foundTransactions: TransactionExtended[] = [];
|
|
|
|
|
|
|
|
transactions.forEach((tx) => {
|
2023-07-28 16:04:03 +09:00
|
|
|
if (tx.vin && tx.vin.some((vin) => !!vin.prevout && vin.prevout.scriptpubkey_type === 'p2pk' && vin.prevout.scriptpubkey === client['track-scriptpubkey'])) {
|
2023-07-26 10:47:59 +09:00
|
|
|
foundTransactions.push(tx);
|
|
|
|
return;
|
|
|
|
}
|
2023-07-28 16:04:03 +09:00
|
|
|
if (tx.vout && tx.vout.some((vout) => vout.scriptpubkey_type === 'p2pk' && vout.scriptpubkey === client['track-scriptpubkey'])) {
|
2023-07-26 10:47:59 +09:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-05 15:26:23 +07:00
|
|
|
if (client['track-asset']) {
|
|
|
|
const foundTransactions: TransactionExtended[] = [];
|
|
|
|
|
|
|
|
transactions.forEach((tx) => {
|
2021-09-18 13:37:25 +04:00
|
|
|
if (client['track-asset'] === Common.nativeAssetId) {
|
2020-05-10 01:34:28 +07:00
|
|
|
if (tx.vin && tx.vin.some((vin) => !!vin.is_pegin)) {
|
|
|
|
foundTransactions.push(tx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (tx.vout && tx.vout.some((vout) => !!vout.pegout)) {
|
|
|
|
foundTransactions.push(tx);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (tx.vin && tx.vin.some((vin) => !!vin.issuance && vin.issuance.asset_id === client['track-asset'])) {
|
|
|
|
foundTransactions.push(tx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (tx.vout && tx.vout.some((vout) => !!vout.asset && vout.asset === client['track-asset'])) {
|
|
|
|
foundTransactions.push(tx);
|
|
|
|
}
|
2020-02-26 17:49:53 +07:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (foundTransactions.length) {
|
2020-02-26 23:21:16 +07:00
|
|
|
foundTransactions.forEach((tx) => {
|
|
|
|
tx.status = {
|
|
|
|
confirmed: true,
|
|
|
|
block_height: block.height,
|
|
|
|
block_hash: block.id,
|
|
|
|
block_time: block.timestamp,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
2023-05-09 17:44:38 -06:00
|
|
|
response['block-transactions'] = JSON.stringify(foundTransactions);
|
2020-02-26 17:49:53 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-24 17:22:38 +09:00
|
|
|
if (client['track-mempool-block'] >= 0 && memPool.isInSync()) {
|
2022-05-31 21:36:42 +00:00
|
|
|
const index = client['track-mempool-block'];
|
|
|
|
if (mBlockDeltas && mBlockDeltas[index]) {
|
2023-05-09 17:44:38 -06:00
|
|
|
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-${index}`, {
|
2022-05-31 21:36:42 +00:00
|
|
|
index: index,
|
|
|
|
delta: mBlockDeltas[index],
|
2023-05-09 17:44:38 -06:00
|
|
|
});
|
2022-05-31 21:36:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-12 15:31:47 -04:00
|
|
|
if (Object.keys(response).length) {
|
|
|
|
const serializedResponse = this.serializeResponse(response);
|
|
|
|
client.send(serializedResponse);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// takes a dictionary of JSON serialized values
|
|
|
|
// and zips it together into a valid JSON object
|
|
|
|
private serializeResponse(response): string {
|
|
|
|
return '{'
|
2023-05-09 17:44:38 -06:00
|
|
|
+ Object.keys(response).map(key => `"${key}": ${response[key]}`).join(', ')
|
|
|
|
+ '}';
|
2020-02-26 17:49:53 +07:00
|
|
|
}
|
2023-05-01 13:08:29 -06:00
|
|
|
|
|
|
|
private printLogs(): void {
|
|
|
|
if (this.wss) {
|
|
|
|
const count = this.wss?.clients?.size || 0;
|
|
|
|
const diff = count - this.numClients;
|
|
|
|
this.numClients = count;
|
|
|
|
logger.debug(`${count} websocket clients | ${this.numConnected} connected | ${this.numDisconnected} disconnected | (${diff >= 0 ? '+' : ''}${diff})`);
|
|
|
|
this.numConnected = 0;
|
|
|
|
this.numDisconnected = 0;
|
|
|
|
}
|
|
|
|
}
|
2020-02-26 17:49:53 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
export default new WebsocketHandler();
|