Merge branch 'master' into mononaut/sharper-blocks
This commit is contained in:
		
						commit
						8680e5f06e
					
				
							
								
								
									
										4
									
								
								backend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								backend/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,12 +1,12 @@ | ||||
| { | ||||
|   "name": "mempool-backend", | ||||
|   "version": "2.6.0-dev", | ||||
|   "version": "3.0.0-dev", | ||||
|   "lockfileVersion": 2, | ||||
|   "requires": true, | ||||
|   "packages": { | ||||
|     "": { | ||||
|       "name": "mempool-backend", | ||||
|       "version": "2.6.0-dev", | ||||
|       "version": "3.0.0-dev", | ||||
|       "license": "GNU Affero General Public License v3.0", | ||||
|       "dependencies": { | ||||
|         "@babel/core": "^7.21.3", | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "mempool-backend", | ||||
|   "version": "2.6.0-dev", | ||||
|   "version": "3.0.0-dev", | ||||
|   "description": "Bitcoin mempool visualizer and blockchain explorer backend", | ||||
|   "license": "GNU Affero General Public License v3.0", | ||||
|   "homepage": "https://mempool.space", | ||||
|  | ||||
							
								
								
									
										4
									
								
								backend/rust-gbt/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								backend/rust-gbt/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,12 +1,12 @@ | ||||
| { | ||||
|   "name": "gbt", | ||||
|   "version": "0.1.0", | ||||
|   "version": "3.0.0-dev", | ||||
|   "lockfileVersion": 3, | ||||
|   "requires": true, | ||||
|   "packages": { | ||||
|     "": { | ||||
|       "name": "gbt", | ||||
|       "version": "0.1.0", | ||||
|       "version": "3.0.0-dev", | ||||
|       "hasInstallScript": true, | ||||
|       "dependencies": { | ||||
|         "@napi-rs/cli": "^2.16.1" | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "gbt", | ||||
|   "version": "0.1.0", | ||||
|   "version": "3.0.0-dev", | ||||
|   "description": "An inefficient re-implementation of the getBlockTemplate algorithm in Rust", | ||||
|   "main": "index.js", | ||||
|   "types": "index.d.ts", | ||||
| @ -30,4 +30,4 @@ | ||||
|   "engines": { | ||||
|     "node": ">= 12" | ||||
|   } | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -6,6 +6,7 @@ import { Common } from "./common"; | ||||
| interface RbfTransaction extends TransactionStripped { | ||||
|   rbf?: boolean; | ||||
|   mined?: boolean; | ||||
|   fullRbf?: boolean; | ||||
| } | ||||
| 
 | ||||
| interface RbfTree { | ||||
| @ -17,6 +18,16 @@ interface RbfTree { | ||||
|   replaces: RbfTree[]; | ||||
| } | ||||
| 
 | ||||
| export interface ReplacementInfo { | ||||
|   mined: boolean; | ||||
|   fullRbf: boolean; | ||||
|   txid: string; | ||||
|   oldFee: number; | ||||
|   oldVsize: number; | ||||
|   newFee: number; | ||||
|   newVsize: number; | ||||
| } | ||||
| 
 | ||||
| class RbfCache { | ||||
|   private replacedBy: Map<string, string> = new Map(); | ||||
|   private replaces: Map<string, string[]> = new Map(); | ||||
| @ -41,11 +52,15 @@ class RbfCache { | ||||
|     this.txs.set(newTx.txid, newTxExtended); | ||||
| 
 | ||||
|     // maintain rbf trees
 | ||||
|     let fullRbf = false; | ||||
|     let txFullRbf = false; | ||||
|     let treeFullRbf = false; | ||||
|     const replacedTrees: RbfTree[] = []; | ||||
|     for (const replacedTxExtended of replaced) { | ||||
|       const replacedTx = Common.stripTransaction(replacedTxExtended) as RbfTransaction; | ||||
|       replacedTx.rbf = replacedTxExtended.vin.some((v) => v.sequence < 0xfffffffe); | ||||
|       if (!replacedTx.rbf) { | ||||
|         txFullRbf = true; | ||||
|       } | ||||
|       this.replacedBy.set(replacedTx.txid, newTx.txid); | ||||
|       if (this.treeMap.has(replacedTx.txid)) { | ||||
|         const treeId = this.treeMap.get(replacedTx.txid); | ||||
| @ -55,7 +70,7 @@ class RbfCache { | ||||
|           if (tree) { | ||||
|             tree.interval = newTime - tree?.time; | ||||
|             replacedTrees.push(tree); | ||||
|             fullRbf = fullRbf || tree.fullRbf || !tree.tx.rbf; | ||||
|             treeFullRbf = treeFullRbf || tree.fullRbf || !tree.tx.rbf; | ||||
|           } | ||||
|         } | ||||
|       } else { | ||||
| @ -67,15 +82,16 @@ class RbfCache { | ||||
|           fullRbf: !replacedTx.rbf, | ||||
|           replaces: [], | ||||
|         }); | ||||
|         fullRbf = fullRbf || !replacedTx.rbf; | ||||
|         treeFullRbf = treeFullRbf || !replacedTx.rbf; | ||||
|         this.txs.set(replacedTx.txid, replacedTxExtended); | ||||
|       } | ||||
|     } | ||||
|     newTx.fullRbf = txFullRbf; | ||||
|     const treeId = replacedTrees[0].tx.txid; | ||||
|     const newTree = { | ||||
|       tx: newTx, | ||||
|       time: newTime, | ||||
|       fullRbf, | ||||
|       fullRbf: treeFullRbf, | ||||
|       replaces: replacedTrees | ||||
|     }; | ||||
|     this.rbfTrees.set(treeId, newTree); | ||||
| @ -349,6 +365,27 @@ class RbfCache { | ||||
|     } | ||||
|     return tree; | ||||
|   } | ||||
| 
 | ||||
|   public getLatestRbfSummary(): ReplacementInfo[] { | ||||
|     const rbfList = this.getRbfTrees(false); | ||||
|     return rbfList.slice(0, 6).map(rbfTree => { | ||||
|       let oldFee = 0; | ||||
|       let oldVsize = 0; | ||||
|       for (const replaced of rbfTree.replaces) { | ||||
|         oldFee += replaced.tx.fee; | ||||
|         oldVsize += replaced.tx.vsize; | ||||
|       } | ||||
|       return { | ||||
|         txid: rbfTree.tx.txid, | ||||
|         mined: !!rbfTree.tx.mined, | ||||
|         fullRbf: !!rbfTree.tx.fullRbf, | ||||
|         oldFee, | ||||
|         oldVsize, | ||||
|         newFee: rbfTree.tx.fee, | ||||
|         newVsize: rbfTree.tx.vsize, | ||||
|       }; | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export default new RbfCache(); | ||||
|  | ||||
| @ -12,7 +12,7 @@ import { Common } from './common'; | ||||
| import loadingIndicators from './loading-indicators'; | ||||
| import config from '../config'; | ||||
| import transactionUtils from './transaction-utils'; | ||||
| import rbfCache from './rbf-cache'; | ||||
| import rbfCache, { ReplacementInfo } from './rbf-cache'; | ||||
| import difficultyAdjustment from './difficulty-adjustment'; | ||||
| import feeApi from './fee-api'; | ||||
| import BlocksAuditsRepository from '../repositories/BlocksAuditsRepository'; | ||||
| @ -40,6 +40,7 @@ class WebsocketHandler { | ||||
| 
 | ||||
|   private socketData: { [key: string]: string } = {}; | ||||
|   private serializedInitData: string = '{}'; | ||||
|   private lastRbfSummary: ReplacementInfo | null = null; | ||||
| 
 | ||||
|   constructor() { } | ||||
| 
 | ||||
| @ -225,6 +226,15 @@ class WebsocketHandler { | ||||
|             } | ||||
|           } | ||||
| 
 | ||||
|           if (parsedMessage && parsedMessage['track-rbf-summary'] != null) { | ||||
|             if (parsedMessage['track-rbf-summary']) { | ||||
|               client['track-rbf-summary'] = true; | ||||
|               response['rbfLatestSummary'] = this.socketData['rbfSummary']; | ||||
|             } else { | ||||
|               client['track-rbf-summary'] = false; | ||||
|             } | ||||
|           } | ||||
| 
 | ||||
|           if (parsedMessage.action === 'init') { | ||||
|             if (!this.socketData['blocks']?.length || !this.socketData['da']) { | ||||
|               this.updateSocketData(); | ||||
| @ -395,10 +405,13 @@ class WebsocketHandler { | ||||
|     const rbfChanges = rbfCache.getRbfChanges(); | ||||
|     let rbfReplacements; | ||||
|     let fullRbfReplacements; | ||||
|     let rbfSummary; | ||||
|     if (Object.keys(rbfChanges.trees).length) { | ||||
|       rbfReplacements = rbfCache.getRbfTrees(false); | ||||
|       fullRbfReplacements = rbfCache.getRbfTrees(true); | ||||
|       rbfSummary = rbfCache.getLatestRbfSummary(); | ||||
|     } | ||||
| 
 | ||||
|     for (const deletedTx of deletedTransactions) { | ||||
|       rbfCache.evict(deletedTx.txid); | ||||
|     } | ||||
| @ -409,7 +422,7 @@ class WebsocketHandler { | ||||
|     const latestTransactions = newTransactions.slice(0, 6).map((tx) => Common.stripTransaction(tx)); | ||||
| 
 | ||||
|     // update init data
 | ||||
|     this.updateSocketDataFields({ | ||||
|     const socketDataFields = { | ||||
|       'mempoolInfo': mempoolInfo, | ||||
|       'vBytesPerSecond': vBytesPerSecond, | ||||
|       'mempool-blocks': mBlocks, | ||||
| @ -417,7 +430,11 @@ class WebsocketHandler { | ||||
|       'loadingIndicators': loadingIndicators.getLoadingIndicators(), | ||||
|       'da': da?.previousTime ? da : undefined, | ||||
|       'fees': recommendedFees, | ||||
|     }); | ||||
|     }; | ||||
|     if (rbfSummary) { | ||||
|       socketDataFields['rbfSummary'] = rbfSummary; | ||||
|     } | ||||
|     this.updateSocketDataFields(socketDataFields); | ||||
| 
 | ||||
|     // cache serialized objects to avoid stringify-ing the same thing for every client
 | ||||
|     const responseCache = { ...this.socketData }; | ||||
| @ -601,6 +618,10 @@ class WebsocketHandler { | ||||
|         response['rbfLatest'] = getCachedResponse('fullrbfLatest', fullRbfReplacements); | ||||
|       } | ||||
| 
 | ||||
|       if (client['track-rbf-summary'] && rbfSummary) { | ||||
|         response['rbfLatestSummary'] = getCachedResponse('rbfLatestSummary', rbfSummary); | ||||
|       } | ||||
| 
 | ||||
|       if (Object.keys(response).length) { | ||||
|         const serializedResponse = this.serializeResponse(response); | ||||
|         client.send(serializedResponse); | ||||
|  | ||||
							
								
								
									
										4
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,12 +1,12 @@ | ||||
| { | ||||
|   "name": "mempool-frontend", | ||||
|   "version": "2.6.0-dev", | ||||
|   "version": "3.0.0-dev", | ||||
|   "lockfileVersion": 2, | ||||
|   "requires": true, | ||||
|   "packages": { | ||||
|     "": { | ||||
|       "name": "mempool-frontend", | ||||
|       "version": "2.6.0-dev", | ||||
|       "version": "3.0.0-dev", | ||||
|       "license": "GNU Affero General Public License v3.0", | ||||
|       "dependencies": { | ||||
|         "@angular-devkit/build-angular": "^14.2.10", | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "mempool-frontend", | ||||
|   "version": "2.6.0-dev", | ||||
|   "version": "3.0.0-dev", | ||||
|   "description": "Bitcoin mempool visualizer and blockchain explorer backend", | ||||
|   "license": "GNU Affero General Public License v3.0", | ||||
|   "homepage": "https://mempool.space", | ||||
| @ -119,4 +119,4 @@ | ||||
|   "scarfSettings": { | ||||
|     "enabled": false | ||||
|   } | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -75,36 +75,31 @@ | ||||
|     <div class="col" style="max-height: 410px"> | ||||
|       <div class="card"> | ||||
|         <div class="card-body"> | ||||
|           <a class="title-link" href="" [routerLink]="['/blocks' | relativeUrl]"> | ||||
|             <h5 class="card-title d-inline" i18n="dashboard.latest-blocks">Latest blocks</h5> | ||||
|           <a class="title-link" href="" [routerLink]="['/rbf' | relativeUrl]"> | ||||
|             <h5 class="card-title d-inline" i18n="dashboard.latest-rbf-replacements">Latest replacements</h5> | ||||
|             <span> </span> | ||||
|             <fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: '#4a68b9'"></fa-icon> | ||||
|           </a> | ||||
|           <table class="table lastest-blocks-table"> | ||||
|           <table class="table lastest-replacements-table"> | ||||
|             <thead> | ||||
|               <th class="table-cell-height" i18n="dashboard.latest-blocks.height">Height</th> | ||||
|               <th *ngIf="!stateService.env.MINING_DASHBOARD" class="table-cell-mined" i18n="dashboard.latest-blocks.mined">Mined</th> | ||||
|               <th *ngIf="stateService.env.MINING_DASHBOARD" class="table-cell-mined pl-lg-4" i18n="mining.pool-name">Pool</th> | ||||
|               <th class="table-cell-transaction-count" i18n="dashboard.latest-blocks.transaction-count">TXs</th> | ||||
|               <th class="table-cell-size" i18n="dashboard.latest-blocks.size">Size</th> | ||||
|               <th class="table-cell-txid" i18n="dashboard.latest-transactions.txid">TXID</th> | ||||
|               <th class="table-cell-old-fee" i18n="dashboard.previous-transaction-fee">Previous fee</th> | ||||
|               <th class="table-cell-new-fee" i18n="dashboard.new-transaction-fee">New fee</th> | ||||
|               <th class="table-cell-badges" i18n="transaction.status|Transaction Status">Status</th> | ||||
|             </thead> | ||||
|             <tbody> | ||||
|               <tr *ngFor="let block of blocks$ | async; let i = index; trackBy: trackByBlock"> | ||||
|                 <td class="table-cell-height" ><a [routerLink]="['/block' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a></td> | ||||
|                 <td *ngIf="!stateService.env.MINING_DASHBOARD" class="table-cell-mined" ><app-time kind="since" [time]="block.timestamp" [fastRender]="true"></app-time></td> | ||||
|                 <td *ngIf="stateService.env.MINING_DASHBOARD" class="table-cell-mined pl-lg-4"> | ||||
|                   <a class="clear-link" [routerLink]="[('/mining/pool/' + block.extras.pool.slug) | relativeUrl]"> | ||||
|                     <img width="22" height="22" src="{{ block.extras.pool['logo'] }}" | ||||
|                       onError="this.src = '/resources/mining-pools/default.svg'"> | ||||
|                     <span class="pool-name">{{ block.extras.pool.name }}</span> | ||||
|               <tr *ngFor="let replacement of replacements$ | async;"> | ||||
|                 <td class="table-cell-txid"> | ||||
|                   <a [routerLink]="['/tx' | relativeUrl, replacement.txid]"> | ||||
|                     <app-truncate [text]="replacement.txid" [lastChars]="5"></app-truncate> | ||||
|                   </a> | ||||
|                 </td> | ||||
|                 <td class="table-cell-transaction-count">{{ block.tx_count | number }}</td> | ||||
|                 <td class="table-cell-size"> | ||||
|                   <div class="progress"> | ||||
|                     <div class="progress-bar progress-mempool {{ network$ | async }}" role="progressbar" [ngStyle]="{'width': (block.weight / stateService.env.BLOCK_WEIGHT_UNITS)*100 + '%' }"> </div> | ||||
|                     <div class="progress-text" [innerHTML]="block.size | bytes: 2"></div> | ||||
|                   </div> | ||||
|                 <td class="table-cell-old-fee"><app-fee-rate [fee]="replacement.oldFee" [weight]="replacement.oldVsize * 4"></app-fee-rate></td> | ||||
|                 <td class="table-cell-new-fee"><app-fee-rate [fee]="replacement.newFee" [weight]="replacement.newVsize * 4"></app-fee-rate></td> | ||||
|                 <td class="table-cell-badges"> | ||||
|                   <span *ngIf="replacement.mined" class="badge badge-success" i18n="transaction.rbf.mined">Mined</span> | ||||
|                   <span *ngIf="replacement.fullRbf" class="badge badge-info" i18n="transaction.full-rbf">Full RBF</span> | ||||
|                   <span *ngIf="!replacement.fullRbf" class="badge badge-success" i18n="transaction.rbf">RBF</span> | ||||
|                 </td> | ||||
|               </tr> | ||||
|             </tbody> | ||||
|  | ||||
| @ -175,39 +175,43 @@ | ||||
|   height: 18px; | ||||
| } | ||||
| 
 | ||||
| .lastest-blocks-table { | ||||
| .lastest-replacements-table { | ||||
|   width: 100%; | ||||
|   text-align: left; | ||||
|   table-layout:fixed; | ||||
|   tr, td, th { | ||||
|     border: 0px; | ||||
|     padding-top: 0.65rem !important; | ||||
|     padding-bottom: 0.7rem !important; | ||||
|     padding-top: 0.71rem !important; | ||||
|     padding-bottom: 0.75rem !important; | ||||
|   } | ||||
|   .table-cell-height { | ||||
|     width: 15%; | ||||
|   td { | ||||
|     overflow:hidden; | ||||
|     width: 25%; | ||||
|   } | ||||
|   .table-cell-mined { | ||||
|     width: 35%; | ||||
|     text-align: left; | ||||
|   .table-cell-txid { | ||||
|     width: 25%; | ||||
|     text-align: start; | ||||
|   } | ||||
|   .table-cell-transaction-count { | ||||
|     display: none; | ||||
|     text-align: right; | ||||
|     width: 20%; | ||||
|     display: table-cell; | ||||
|   } | ||||
|   .table-cell-size { | ||||
|     display: none; | ||||
|     text-align: center; | ||||
|     width: 30%; | ||||
|     @media (min-width: 485px) { | ||||
|       display: table-cell; | ||||
|     } | ||||
|     @media (min-width: 768px) { | ||||
|   .table-cell-old-fee { | ||||
|     width: 25%; | ||||
|     text-align: end; | ||||
| 
 | ||||
|     @media(max-width: 1080px) { | ||||
|       display: none; | ||||
|     } | ||||
|     @media (min-width: 992px) { | ||||
|       display: table-cell; | ||||
|   } | ||||
|   .table-cell-new-fee { | ||||
|     width: 20%; | ||||
|     text-align: end; | ||||
|   } | ||||
|   .table-cell-badges { | ||||
|     width: 23%; | ||||
|     padding-right: 0; | ||||
|     padding-left: 5px; | ||||
|     text-align: end; | ||||
| 
 | ||||
|     .badge { | ||||
|       margin-left: 5px; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -1,8 +1,8 @@ | ||||
| import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; | ||||
| import { combineLatest, merge, Observable, of, Subscription } from 'rxjs'; | ||||
| import { filter, map, scan, share, switchMap, tap } from 'rxjs/operators'; | ||||
| import { BlockExtended, OptimizedMempoolStats } from '../interfaces/node-api.interface'; | ||||
| import { MempoolInfo, TransactionStripped } from '../interfaces/websocket.interface'; | ||||
| import { filter, map, scan, share, switchMap } from 'rxjs/operators'; | ||||
| import { BlockExtended, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface'; | ||||
| import { MempoolInfo, TransactionStripped, ReplacementInfo } from '../interfaces/websocket.interface'; | ||||
| import { ApiService } from '../services/api.service'; | ||||
| import { StateService } from '../services/state.service'; | ||||
| import { WebsocketService } from '../services/websocket.service'; | ||||
| @ -38,8 +38,8 @@ export class DashboardComponent implements OnInit, OnDestroy { | ||||
|   mempoolInfoData$: Observable<MempoolInfoData>; | ||||
|   mempoolLoadingStatus$: Observable<number>; | ||||
|   vBytesPerSecondLimit = 1667; | ||||
|   blocks$: Observable<BlockExtended[]>; | ||||
|   transactions$: Observable<TransactionStripped[]>; | ||||
|   replacements$: Observable<ReplacementInfo[]>; | ||||
|   latestBlockHeight: number; | ||||
|   mempoolTransactionsWeightPerSecondData: any; | ||||
|   mempoolStats$: Observable<MempoolStatsData>; | ||||
| @ -58,12 +58,14 @@ export class DashboardComponent implements OnInit, OnDestroy { | ||||
| 
 | ||||
|   ngOnDestroy(): void { | ||||
|     this.currencySubscription.unsubscribe(); | ||||
|     this.websocketService.stopTrackRbfSummary(); | ||||
|   } | ||||
| 
 | ||||
|   ngOnInit(): void { | ||||
|     this.isLoadingWebSocket$ = this.stateService.isLoadingWebSocket$; | ||||
|     this.seoService.resetTitle(); | ||||
|     this.websocketService.want(['blocks', 'stats', 'mempool-blocks', 'live-2h-chart']); | ||||
|     this.websocketService.startTrackRbfSummary(); | ||||
|     this.network$ = merge(of(''), this.stateService.networkChanged$); | ||||
|     this.mempoolLoadingStatus$ = this.stateService.loadingIndicators$ | ||||
|       .pipe( | ||||
| @ -130,23 +132,6 @@ export class DashboardComponent implements OnInit, OnDestroy { | ||||
|         }), | ||||
|       ); | ||||
| 
 | ||||
|     this.blocks$ = this.stateService.blocks$ | ||||
|       .pipe( | ||||
|         tap((blocks) => { | ||||
|           this.latestBlockHeight = blocks[0].height; | ||||
|         }), | ||||
|         switchMap((blocks) => { | ||||
|           if (this.stateService.env.MINING_DASHBOARD === true) { | ||||
|             for (const block of blocks) { | ||||
|               // @ts-ignore: Need to add an extra field for the template
 | ||||
|               block.extras.pool.logo = `/resources/mining-pools/` + | ||||
|                 block.extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg'; | ||||
|             } | ||||
|           } | ||||
|           return of(blocks.slice(0, 6)); | ||||
|         }) | ||||
|       ); | ||||
| 
 | ||||
|     this.transactions$ = this.stateService.transactions$ | ||||
|       .pipe( | ||||
|         scan((acc, tx) => { | ||||
| @ -159,6 +144,8 @@ export class DashboardComponent implements OnInit, OnDestroy { | ||||
|         }, []), | ||||
|       ); | ||||
| 
 | ||||
|     this.replacements$ = this.stateService.rbfLatestSummary$; | ||||
| 
 | ||||
|     this.mempoolStats$ = this.stateService.connectionState$ | ||||
|       .pipe( | ||||
|         filter((state) => state === 2), | ||||
| @ -219,4 +206,16 @@ export class DashboardComponent implements OnInit, OnDestroy { | ||||
|   trackByBlock(index: number, block: BlockExtended) { | ||||
|     return block.height; | ||||
|   } | ||||
| 
 | ||||
|   checkFullRbf(tree: RbfTree): void { | ||||
|     let fullRbf = false; | ||||
|     for (const replaced of tree.replaces) { | ||||
|       if (!replaced.tx.rbf) { | ||||
|         fullRbf = true; | ||||
|       } | ||||
|       replaced.replacedBy = tree.tx; | ||||
|       this.checkFullRbf(replaced); | ||||
|     } | ||||
|     tree.tx.fullRbf = fullRbf; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -18,6 +18,7 @@ export interface WebsocketResponse { | ||||
|   txReplaced?: ReplacedTransaction; | ||||
|   rbfInfo?: RbfTree; | ||||
|   rbfLatest?: RbfTree[]; | ||||
|   rbfLatestSummary?: ReplacementInfo[]; | ||||
|   utxoSpent?: object; | ||||
|   transactions?: TransactionStripped[]; | ||||
|   loadingIndicators?: ILoadingIndicators; | ||||
| @ -29,6 +30,7 @@ export interface WebsocketResponse { | ||||
|   'track-asset'?: string; | ||||
|   'track-mempool-block'?: number; | ||||
|   'track-rbf'?: string; | ||||
|   'track-rbf-summary'?: boolean; | ||||
|   'watch-mempool'?: boolean; | ||||
|   'track-bisq-market'?: string; | ||||
|   'refresh-blocks'?: boolean; | ||||
| @ -37,6 +39,16 @@ export interface WebsocketResponse { | ||||
| export interface ReplacedTransaction extends Transaction { | ||||
|   txid: string; | ||||
| } | ||||
| 
 | ||||
| export interface ReplacementInfo { | ||||
|   mined: boolean; | ||||
|   fullRbf: boolean; | ||||
|   txid: string; | ||||
|   oldFee: number; | ||||
|   oldVsize: number; | ||||
|   newFee: number; | ||||
|   newVsize: number; | ||||
| } | ||||
| export interface MempoolBlock { | ||||
|   blink?: boolean; | ||||
|   height?: number; | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core'; | ||||
| import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable, merge } from 'rxjs'; | ||||
| import { Transaction } from '../interfaces/electrs.interface'; | ||||
| import { IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, TransactionStripped } from '../interfaces/websocket.interface'; | ||||
| import { IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, TransactionStripped } from '../interfaces/websocket.interface'; | ||||
| import { BlockExtended, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface'; | ||||
| import { Router, NavigationStart } from '@angular/router'; | ||||
| import { isPlatformBrowser } from '@angular/common'; | ||||
| @ -108,6 +108,7 @@ export class StateService { | ||||
|   txReplaced$ = new Subject<ReplacedTransaction>(); | ||||
|   txRbfInfo$ = new Subject<RbfTree>(); | ||||
|   rbfLatest$ = new Subject<RbfTree[]>(); | ||||
|   rbfLatestSummary$ = new Subject<ReplacementInfo[]>(); | ||||
|   utxoSpent$ = new Subject<object>(); | ||||
|   difficultyAdjustment$ = new ReplaySubject<DifficultyAdjustment>(1); | ||||
|   mempoolTransactions$ = new Subject<Transaction>(); | ||||
|  | ||||
| @ -29,6 +29,7 @@ export class WebsocketService { | ||||
|   private trackingTxId: string; | ||||
|   private isTrackingMempoolBlock = false; | ||||
|   private isTrackingRbf = false; | ||||
|   private isTrackingRbfSummary = false; | ||||
|   private trackingMempoolBlock: number; | ||||
|   private latestGitCommit = ''; | ||||
|   private onlineCheckTimeout: number; | ||||
| @ -185,6 +186,16 @@ export class WebsocketService { | ||||
|     this.isTrackingRbf = false; | ||||
|   } | ||||
| 
 | ||||
|   startTrackRbfSummary() { | ||||
|     this.websocketSubject.next({ 'track-rbf-summary': true }); | ||||
|     this.isTrackingRbfSummary = true; | ||||
|   } | ||||
| 
 | ||||
|   stopTrackRbfSummary() { | ||||
|     this.websocketSubject.next({ 'track-rbf-summary': false }); | ||||
|     this.isTrackingRbfSummary = false; | ||||
|   } | ||||
| 
 | ||||
|   startTrackBisqMarket(market: string) { | ||||
|     this.websocketSubject.next({ 'track-bisq-market': market }); | ||||
|   } | ||||
| @ -283,6 +294,10 @@ export class WebsocketService { | ||||
|       this.stateService.rbfLatest$.next(response.rbfLatest); | ||||
|     } | ||||
| 
 | ||||
|     if (response.rbfLatestSummary) { | ||||
|       this.stateService.rbfLatestSummary$.next(response.rbfLatestSummary); | ||||
|     } | ||||
| 
 | ||||
|     if (response.txReplaced) { | ||||
|       this.stateService.txReplaced$.next(response.txReplaced); | ||||
|     } | ||||
|  | ||||
							
								
								
									
										4
									
								
								unfurler/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								unfurler/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,12 +1,12 @@ | ||||
| { | ||||
|   "name": "mempool-unfurl", | ||||
|   "version": "0.1.0", | ||||
|   "version": "3.0.0-dev", | ||||
|   "lockfileVersion": 2, | ||||
|   "requires": true, | ||||
|   "packages": { | ||||
|     "": { | ||||
|       "name": "mempool-unfurl", | ||||
|       "version": "0.1.0", | ||||
|       "version": "3.0.0-dev", | ||||
|       "dependencies": { | ||||
|         "@types/node": "^16.11.41", | ||||
|         "express": "^4.18.0", | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "mempool-unfurl", | ||||
|   "version": "0.1.0", | ||||
|   "version": "3.0.0-dev", | ||||
|   "description": "Renderer for mempool open graph link preview images", | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user