parent
							
								
									f0b0fc3f4b
								
							
						
					
					
						commit
						8b6a681614
					
				@ -25,4 +25,26 @@ export class Common {
 | 
			
		||||
    arr.push(transactions[lastindex].feePerVsize);
 | 
			
		||||
    return arr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static findRbfTransactions(added: TransactionExtended[], deleted: TransactionExtended[]): { [txid: string]: TransactionExtended } {
 | 
			
		||||
    const matches: { [txid: string]: TransactionExtended } = {};
 | 
			
		||||
    deleted
 | 
			
		||||
      // The replaced tx must have at least one input with nSequence < maxint-1 (That’s the opt-in)
 | 
			
		||||
      .filter((tx) => tx.vin.some((vin) => vin.sequence < 0xfffffffe))
 | 
			
		||||
      .forEach((deletedTx) => {
 | 
			
		||||
        const foundMatches = added.find((addedTx) => {
 | 
			
		||||
          // The new tx must, absolutely speaking, pay at least as much fee as the replaced tx.
 | 
			
		||||
          return addedTx.fee > deletedTx.fee
 | 
			
		||||
            // The new transaction must pay more fee per kB than the replaced tx.
 | 
			
		||||
            && addedTx.feePerVsize > deletedTx.feePerVsize
 | 
			
		||||
            // Spends one or more of the same inputs
 | 
			
		||||
            && deletedTx.vin.some((deletedVin) =>
 | 
			
		||||
              addedTx.vin.some((vin) => vin.txid === deletedVin.txid));
 | 
			
		||||
            });
 | 
			
		||||
        if (foundMatches) {
 | 
			
		||||
          matches[deletedTx.txid] = foundMatches;
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    return matches;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -49,9 +49,9 @@ class MempoolBlocks {
 | 
			
		||||
        transactions.push(tx);
 | 
			
		||||
      } else {
 | 
			
		||||
        mempoolBlocks.push(this.dataToMempoolBlocks(transactions, blockSize, blockVSize, mempoolBlocks.length));
 | 
			
		||||
        blockVSize = 0;
 | 
			
		||||
        blockSize = 0;
 | 
			
		||||
        transactions = [];
 | 
			
		||||
        blockVSize = tx.vsize;
 | 
			
		||||
        blockSize = tx.size;
 | 
			
		||||
        transactions = [tx];
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    if (transactions.length) {
 | 
			
		||||
 | 
			
		||||
@ -123,31 +123,30 @@ class Mempool {
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Replace mempool to clear already confirmed transactions
 | 
			
		||||
      // Replace mempool to clear deleted transactions
 | 
			
		||||
      const newMempool = {};
 | 
			
		||||
      transactions.forEach((tx) => {
 | 
			
		||||
        if (this.mempoolCache[tx]) {
 | 
			
		||||
      const deletedTransactions: Transaction[] = [];
 | 
			
		||||
      for (const tx in this.mempoolCache) {
 | 
			
		||||
        if (transactions.indexOf(tx) > -1) {
 | 
			
		||||
          newMempool[tx] = this.mempoolCache[tx];
 | 
			
		||||
        } else {
 | 
			
		||||
          hasChange = true;
 | 
			
		||||
          deletedTransactions.push(this.mempoolCache[tx]);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (!this.inSync && transactions.length === Object.keys(newMempool).length) {
 | 
			
		||||
        this.inSync = true;
 | 
			
		||||
        console.log('The mempool is now in sync!');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      console.log(`New mempool size: ${Object.keys(newMempool).length} Change: ${diff}`);
 | 
			
		||||
 | 
			
		||||
      this.mempoolCache = newMempool;
 | 
			
		||||
 | 
			
		||||
      if (hasChange && this.mempoolChangedCallback) {
 | 
			
		||||
        this.mempoolChangedCallback(this.mempoolCache, newTransactions);
 | 
			
		||||
      if (this.mempoolChangedCallback && (hasChange || deletedTransactions.length)) {
 | 
			
		||||
        this.mempoolCache = newMempool;
 | 
			
		||||
        this.mempoolChangedCallback(this.mempoolCache, newTransactions, deletedTransactions);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const end = new Date().getTime();
 | 
			
		||||
      const time = end - start;
 | 
			
		||||
      console.log(`New mempool size: ${Object.keys(newMempool).length} Change: ${diff}`);
 | 
			
		||||
      console.log('Mempool updated in ' + time / 1000 + ' seconds');
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      console.log('getRawMempool error.', err);
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ import backendInfo from './backend-info';
 | 
			
		||||
import mempoolBlocks from './mempool-blocks';
 | 
			
		||||
import fiatConversion from './fiat-conversion';
 | 
			
		||||
import * as os from 'os';
 | 
			
		||||
import { Common } from './common';
 | 
			
		||||
 | 
			
		||||
class WebsocketHandler {
 | 
			
		||||
  private wss: WebSocket.Server | undefined;
 | 
			
		||||
@ -121,7 +122,8 @@ class WebsocketHandler {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleMempoolChange(newMempool: { [txid: string]: TransactionExtended }, newTransactions: TransactionExtended[]) {
 | 
			
		||||
  handleMempoolChange(newMempool: { [txid: string]: TransactionExtended },
 | 
			
		||||
    newTransactions: TransactionExtended[], deletedTransactions: TransactionExtended[]) {
 | 
			
		||||
    if (!this.wss) {
 | 
			
		||||
      throw new Error('WebSocket.Server is not set');
 | 
			
		||||
    }
 | 
			
		||||
@ -130,6 +132,7 @@ class WebsocketHandler {
 | 
			
		||||
    const mBlocks = mempoolBlocks.getMempoolBlocks();
 | 
			
		||||
    const mempoolInfo = memPool.getMempoolInfo();
 | 
			
		||||
    const vBytesPerSecond = memPool.getVBytesPerSecond();
 | 
			
		||||
    const rbfTransactions = Common.findRbfTransactions(newTransactions, deletedTransactions);
 | 
			
		||||
 | 
			
		||||
    this.wss.clients.forEach((client: WebSocket) => {
 | 
			
		||||
      if (client.readyState !== WebSocket.OPEN) {
 | 
			
		||||
@ -204,6 +207,15 @@ class WebsocketHandler {
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (client['track-tx'] && rbfTransactions[client['track-tx']]) {
 | 
			
		||||
        for (const rbfTransaction in rbfTransactions) {
 | 
			
		||||
          if (client['track-tx'] === rbfTransaction) {
 | 
			
		||||
            response['rbfTransaction'] = rbfTransactions[rbfTransaction];
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (Object.keys(response).length) {
 | 
			
		||||
        client.send(JSON.stringify(response));
 | 
			
		||||
      }
 | 
			
		||||
@ -228,7 +240,7 @@ class WebsocketHandler {
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      matchRate = Math.ceil((matches.length / txIds.length) * 100);
 | 
			
		||||
      matchRate = Math.round((matches.length / (txIds.length - 1)) * 100);
 | 
			
		||||
      if (matchRate > 0) {
 | 
			
		||||
        const currentMemPool = memPool.getMempool();
 | 
			
		||||
        for (const txId of matches) {
 | 
			
		||||
 | 
			
		||||
@ -35,9 +35,6 @@ export interface Transaction {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface TransactionExtended extends Transaction {
 | 
			
		||||
  txid: string;
 | 
			
		||||
  fee: number;
 | 
			
		||||
  size: number;
 | 
			
		||||
  vsize: number;
 | 
			
		||||
  feePerVsize: number;
 | 
			
		||||
  firstSeen: number;
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,14 @@
 | 
			
		||||
<div class="container-xl">
 | 
			
		||||
 | 
			
		||||
  <div class="title-block">
 | 
			
		||||
    <div *ngIf="rbfTransaction" class="alert alert-mempool" role="alert">
 | 
			
		||||
      This transaction has been replaced by:
 | 
			
		||||
      <a class="alert-link" [routerLink]="['/tx/' | relativeUrl, rbfTransaction.txid]" [state]="{ data: rbfTransaction }">
 | 
			
		||||
        <span class="d-inline d-lg-none">{{ rbfTransaction.txid | shortenString : 24 }}</span>
 | 
			
		||||
        <span class="d-none d-lg-inline">{{ rbfTransaction.txid }}</span>
 | 
			
		||||
      </a>
 | 
			
		||||
    </div>
 | 
			
		||||
  
 | 
			
		||||
    <h1 class="float-left mr-3 mb-md-3">Transaction</h1>
 | 
			
		||||
 | 
			
		||||
    <ng-template [ngIf]="tx?.status?.confirmed">
 | 
			
		||||
 | 
			
		||||
@ -36,6 +36,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
 | 
			
		||||
    potentialP2shGains: 0,
 | 
			
		||||
  };
 | 
			
		||||
  isRbfTransaction: boolean;
 | 
			
		||||
  rbfTransaction: undefined | Transaction;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private route: ActivatedRoute,
 | 
			
		||||
@ -126,6 +127,9 @@ export class TransactionComponent implements OnInit, OnDestroy {
 | 
			
		||||
        this.audioService.playSound('magic');
 | 
			
		||||
        this.findBlockAndSetFeeRating();
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    this.stateService.txReplaced$
 | 
			
		||||
      .subscribe((rbfTransaction) => this.rbfTransaction = rbfTransaction);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleLoadElectrsTransactionError(error: any): Observable<any> {
 | 
			
		||||
@ -198,6 +202,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
 | 
			
		||||
    this.feeRating = undefined;
 | 
			
		||||
    this.waitingForTransaction = false;
 | 
			
		||||
    this.isLoadingTx = true;
 | 
			
		||||
    this.rbfTransaction = undefined;
 | 
			
		||||
    this.transactionTime = -1;
 | 
			
		||||
    document.body.scrollTo(0, 0);
 | 
			
		||||
    this.leaveTransaction();
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,7 @@ export interface WebsocketResponse {
 | 
			
		||||
  action?: string;
 | 
			
		||||
  data?: string[];
 | 
			
		||||
  tx?: Transaction;
 | 
			
		||||
  rbfTransaction?: Transaction;
 | 
			
		||||
  'track-tx'?: string;
 | 
			
		||||
  'track-address'?: string;
 | 
			
		||||
  'track-asset'?: string;
 | 
			
		||||
 | 
			
		||||
@ -24,6 +24,7 @@ export class StateService {
 | 
			
		||||
  mempoolStats$ = new ReplaySubject<MemPoolState>(1);
 | 
			
		||||
  mempoolBlocks$ = new ReplaySubject<MempoolBlock[]>(1);
 | 
			
		||||
  txConfirmed$ = new Subject<Block>();
 | 
			
		||||
  txReplaced$ = new Subject<Transaction>();
 | 
			
		||||
  mempoolTransactions$ = new Subject<Transaction>();
 | 
			
		||||
  blockTransactions$ = new Subject<Transaction>();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -89,6 +89,10 @@ export class WebsocketService {
 | 
			
		||||
          this.stateService.conversions$.next(response.conversions);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (response.rbfTransaction) {
 | 
			
		||||
          this.stateService.txReplaced$.next(response.rbfTransaction);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (response['mempool-blocks']) {
 | 
			
		||||
          this.stateService.mempoolBlocks$.next(response['mempool-blocks']);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -410,3 +410,9 @@ h1, h2, h3 {
 | 
			
		||||
.tooltip-inner {
 | 
			
		||||
  max-width: inherit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.alert-mempool {
 | 
			
		||||
  color: #ffffff;
 | 
			
		||||
  background-color: #653b9c;
 | 
			
		||||
  border-color: #3a1c61;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user