optimize batch client websocket updates
This commit is contained in:
parent
3b4dd7e633
commit
f8636d20c2
@ -210,11 +210,12 @@ class WebsocketHandler {
|
|||||||
throw new Error('WebSocket.Server is not set');
|
throw new Error('WebSocket.Server is not set');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const response = JSON.stringify({ loadingIndicators: indicators });
|
||||||
this.wss.clients.forEach((client) => {
|
this.wss.clients.forEach((client) => {
|
||||||
if (client.readyState !== WebSocket.OPEN) {
|
if (client.readyState !== WebSocket.OPEN) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
client.send(JSON.stringify({ loadingIndicators: indicators }));
|
client.send(response);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,11 +224,12 @@ class WebsocketHandler {
|
|||||||
throw new Error('WebSocket.Server is not set');
|
throw new Error('WebSocket.Server is not set');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const response = JSON.stringify({ conversions: conversionRates });
|
||||||
this.wss.clients.forEach((client) => {
|
this.wss.clients.forEach((client) => {
|
||||||
if (client.readyState !== WebSocket.OPEN) {
|
if (client.readyState !== WebSocket.OPEN) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
client.send(JSON.stringify({ conversions: conversionRates }));
|
client.send(response);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,6 +260,10 @@ class WebsocketHandler {
|
|||||||
|
|
||||||
this.printLogs();
|
this.printLogs();
|
||||||
|
|
||||||
|
const response = JSON.stringify({
|
||||||
|
'live-2h-chart': stats
|
||||||
|
});
|
||||||
|
|
||||||
this.wss.clients.forEach((client) => {
|
this.wss.clients.forEach((client) => {
|
||||||
if (client.readyState !== WebSocket.OPEN) {
|
if (client.readyState !== WebSocket.OPEN) {
|
||||||
return;
|
return;
|
||||||
@ -267,9 +273,7 @@ class WebsocketHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
client.send(JSON.stringify({
|
client.send(response);
|
||||||
'live-2h-chart': stats
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,6 +310,38 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
const recommendedFees = feeApi.getRecommendedFee();
|
const recommendedFees = feeApi.getRecommendedFee();
|
||||||
|
|
||||||
|
// cache serialized objects to avoid stringify-ing the same thing for every client
|
||||||
|
const responseCache = {};
|
||||||
|
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 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.wss.clients.forEach(async (client) => {
|
this.wss.clients.forEach(async (client) => {
|
||||||
if (client.readyState !== WebSocket.OPEN) {
|
if (client.readyState !== WebSocket.OPEN) {
|
||||||
return;
|
return;
|
||||||
@ -314,17 +350,17 @@ class WebsocketHandler {
|
|||||||
const response = {};
|
const response = {};
|
||||||
|
|
||||||
if (client['want-stats']) {
|
if (client['want-stats']) {
|
||||||
response['mempoolInfo'] = mempoolInfo;
|
response['mempoolInfo'] = getCachedResponse('mempoolInfo', mempoolInfo);
|
||||||
response['vBytesPerSecond'] = vBytesPerSecond;
|
response['vBytesPerSecond'] = getCachedResponse('vBytesPerSecond', vBytesPerSecond);
|
||||||
response['transactions'] = newTransactions.slice(0, 6).map((tx) => Common.stripTransaction(tx));
|
response['transactions'] = getCachedResponse('transactions', newTransactions.slice(0, 6).map((tx) => Common.stripTransaction(tx)));
|
||||||
if (da?.previousTime) {
|
if (da?.previousTime) {
|
||||||
response['da'] = da;
|
response['da'] = getCachedResponse('da', da);
|
||||||
}
|
}
|
||||||
response['fees'] = recommendedFees;
|
response['fees'] = getCachedResponse('fees', recommendedFees);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client['want-mempool-blocks']) {
|
if (client['want-mempool-blocks']) {
|
||||||
response['mempool-blocks'] = mBlocks;
|
response['mempool-blocks'] = getCachedResponse('mempool-blocks', mBlocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client['track-mempool-tx']) {
|
if (client['track-mempool-tx']) {
|
||||||
@ -333,12 +369,12 @@ class WebsocketHandler {
|
|||||||
if (config.MEMPOOL.BACKEND !== 'esplora') {
|
if (config.MEMPOOL.BACKEND !== 'esplora') {
|
||||||
try {
|
try {
|
||||||
const fullTx = await transactionUtils.$getTransactionExtended(tx.txid, true);
|
const fullTx = await transactionUtils.$getTransactionExtended(tx.txid, true);
|
||||||
response['tx'] = fullTx;
|
response['tx'] = JSON.stringify(fullTx);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e));
|
logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
response['tx'] = tx;
|
response['tx'] = JSON.stringify(tx);
|
||||||
}
|
}
|
||||||
client['track-mempool-tx'] = null;
|
client['track-mempool-tx'] = null;
|
||||||
}
|
}
|
||||||
@ -378,7 +414,7 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (foundTransactions.length) {
|
if (foundTransactions.length) {
|
||||||
response['address-transactions'] = foundTransactions;
|
response['address-transactions'] = JSON.stringify(foundTransactions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -407,65 +443,60 @@ class WebsocketHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (foundTransactions.length) {
|
if (foundTransactions.length) {
|
||||||
response['address-transactions'] = foundTransactions;
|
response['address-transactions'] = JSON.stringify(foundTransactions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client['track-tx']) {
|
if (client['track-tx']) {
|
||||||
const trackTxid = client['track-tx'];
|
const trackTxid = client['track-tx'];
|
||||||
const outspends: object = {};
|
const outspends = outspendCache[trackTxid];
|
||||||
newTransactions.forEach((tx) => tx.vin.forEach((vin, i) => {
|
|
||||||
if (vin.txid === trackTxid) {
|
|
||||||
outspends[vin.vout] = {
|
|
||||||
vin: i,
|
|
||||||
txid: tx.txid,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (Object.keys(outspends).length) {
|
if (outspends && Object.keys(outspends).length) {
|
||||||
response['utxoSpent'] = outspends;
|
response['utxoSpent'] = JSON.stringify(outspends);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rbfReplacedBy = rbfCache.getReplacedBy(client['track-tx']);
|
const rbfReplacedBy = rbfCache.getReplacedBy(client['track-tx']);
|
||||||
if (rbfReplacedBy) {
|
if (rbfReplacedBy) {
|
||||||
response['rbfTransaction'] = {
|
response['rbfTransaction'] = JSON.stringify({
|
||||||
txid: rbfReplacedBy,
|
txid: rbfReplacedBy,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const rbfChange = rbfChanges.map[client['track-tx']];
|
const rbfChange = rbfChanges.map[client['track-tx']];
|
||||||
if (rbfChange) {
|
if (rbfChange) {
|
||||||
response['rbfInfo'] = rbfChanges.trees[rbfChange];
|
response['rbfInfo'] = JSON.stringify(rbfChanges.trees[rbfChange]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mempoolTx = newMempool[trackTxid];
|
const mempoolTx = newMempool[trackTxid];
|
||||||
if (mempoolTx && mempoolTx.position) {
|
if (mempoolTx && mempoolTx.position) {
|
||||||
response['txPosition'] = {
|
response['txPosition'] = JSON.stringify({
|
||||||
txid: trackTxid,
|
txid: trackTxid,
|
||||||
position: mempoolTx.position,
|
position: mempoolTx.position,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client['track-mempool-block'] >= 0) {
|
if (client['track-mempool-block'] >= 0) {
|
||||||
const index = client['track-mempool-block'];
|
const index = client['track-mempool-block'];
|
||||||
if (mBlockDeltas[index]) {
|
if (mBlockDeltas[index]) {
|
||||||
response['projected-block-transactions'] = {
|
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-${index}`, {
|
||||||
index: index,
|
index: index,
|
||||||
delta: mBlockDeltas[index],
|
delta: mBlockDeltas[index],
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client['track-rbf'] === 'all' && rbfReplacements) {
|
if (client['track-rbf'] === 'all' && rbfReplacements) {
|
||||||
response['rbfLatest'] = rbfReplacements;
|
response['rbfLatest'] = getCachedResponse('rbfLatest', rbfReplacements);
|
||||||
} else if (client['track-rbf'] === 'fullRbf' && fullRbfReplacements) {
|
} else if (client['track-rbf'] === 'fullRbf' && fullRbfReplacements) {
|
||||||
response['rbfLatest'] = fullRbfReplacements;
|
response['rbfLatest'] = getCachedResponse('fullrbfLatest', fullRbfReplacements);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.keys(response).length) {
|
if (Object.keys(response).length) {
|
||||||
client.send(JSON.stringify(response));
|
const serializedResponse = '{'
|
||||||
|
+ Object.keys(response).map(key => `"${key}": ${response[key]}`).join(', ')
|
||||||
|
+ '}';
|
||||||
|
client.send(serializedResponse);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -556,6 +587,14 @@ class WebsocketHandler {
|
|||||||
const da = difficultyAdjustment.getDifficultyAdjustment();
|
const da = difficultyAdjustment.getDifficultyAdjustment();
|
||||||
const fees = feeApi.getRecommendedFee();
|
const fees = feeApi.getRecommendedFee();
|
||||||
|
|
||||||
|
const responseCache = {};
|
||||||
|
function getCachedResponse(key, data) {
|
||||||
|
if (!responseCache[key]) {
|
||||||
|
responseCache[key] = JSON.stringify(data);
|
||||||
|
}
|
||||||
|
return responseCache[key];
|
||||||
|
}
|
||||||
|
|
||||||
this.wss.clients.forEach((client) => {
|
this.wss.clients.forEach((client) => {
|
||||||
if (client.readyState !== WebSocket.OPEN) {
|
if (client.readyState !== WebSocket.OPEN) {
|
||||||
return;
|
return;
|
||||||
@ -565,28 +604,27 @@ class WebsocketHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = {
|
const response = {};
|
||||||
'block': block,
|
response['block'] = getCachedResponse('block', block);
|
||||||
'mempoolInfo': memPool.getMempoolInfo(),
|
response['mempoolInfo'] = getCachedResponse('mempoolInfo', memPool.getMempoolInfo(),);
|
||||||
'da': da?.previousTime ? da : undefined,
|
response['da'] = getCachedResponse('da', da?.previousTime ? da : undefined);
|
||||||
'fees': fees,
|
response['fees'] = getCachedResponse('fees', fees);
|
||||||
};
|
|
||||||
|
|
||||||
if (mBlocks && client['want-mempool-blocks']) {
|
if (mBlocks && client['want-mempool-blocks']) {
|
||||||
response['mempool-blocks'] = mBlocks;
|
response['mempool-blocks'] = getCachedResponse('mempool-blocks', mBlocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client['track-tx']) {
|
if (client['track-tx']) {
|
||||||
const trackTxid = client['track-tx'];
|
const trackTxid = client['track-tx'];
|
||||||
if (txIds.indexOf(trackTxid) > -1) {
|
if (txIds.indexOf(trackTxid) > -1) {
|
||||||
response['txConfirmed'] = true;
|
response['txConfirmed'] = 'true';
|
||||||
} else {
|
} else {
|
||||||
const mempoolTx = _memPool[trackTxid];
|
const mempoolTx = _memPool[trackTxid];
|
||||||
if (mempoolTx && mempoolTx.position) {
|
if (mempoolTx && mempoolTx.position) {
|
||||||
response['txPosition'] = {
|
response['txPosition'] = JSON.stringify({
|
||||||
txid: trackTxid,
|
txid: trackTxid,
|
||||||
position: mempoolTx.position,
|
position: mempoolTx.position,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -614,7 +652,7 @@ class WebsocketHandler {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
response['block-transactions'] = foundTransactions;
|
response['block-transactions'] = JSON.stringify(foundTransactions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -651,21 +689,24 @@ class WebsocketHandler {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
response['block-transactions'] = foundTransactions;
|
response['block-transactions'] = JSON.stringify(foundTransactions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client['track-mempool-block'] >= 0) {
|
if (client['track-mempool-block'] >= 0) {
|
||||||
const index = client['track-mempool-block'];
|
const index = client['track-mempool-block'];
|
||||||
if (mBlockDeltas && mBlockDeltas[index]) {
|
if (mBlockDeltas && mBlockDeltas[index]) {
|
||||||
response['projected-block-transactions'] = {
|
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-${index}`, {
|
||||||
index: index,
|
index: index,
|
||||||
delta: mBlockDeltas[index],
|
delta: mBlockDeltas[index],
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
client.send(JSON.stringify(response));
|
const serializedResponse = '{'
|
||||||
|
+ Object.keys(response).map(key => `"${key}": ${response[key]}`).join(', ')
|
||||||
|
+ '}';
|
||||||
|
client.send(serializedResponse);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user