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