Merge pull request #3937 from mempool/mononaut/replace-stale-blocks
Replace client-side stale blocks
This commit is contained in:
		
						commit
						cf13c43637
					
				| @ -25,6 +25,7 @@ import DifficultyAdjustmentsRepository from '../repositories/DifficultyAdjustmen | |||||||
| import PricesRepository from '../repositories/PricesRepository'; | import PricesRepository from '../repositories/PricesRepository'; | ||||||
| import priceUpdater from '../tasks/price-updater'; | import priceUpdater from '../tasks/price-updater'; | ||||||
| import chainTips from './chain-tips'; | import chainTips from './chain-tips'; | ||||||
|  | import websocketHandler from './websocket-handler'; | ||||||
| 
 | 
 | ||||||
| class Blocks { | class Blocks { | ||||||
|   private blocks: BlockExtended[] = []; |   private blocks: BlockExtended[] = []; | ||||||
| @ -686,6 +687,8 @@ class Blocks { | |||||||
|             this.updateTimerProgress(timer, `reindexed difficulty adjustments`); |             this.updateTimerProgress(timer, `reindexed difficulty adjustments`); | ||||||
|             logger.info(`Re-indexed 10 blocks and summaries. Also re-indexed the last difficulty adjustments. Will re-index latest hashrates in a few seconds.`, logger.tags.mining); |             logger.info(`Re-indexed 10 blocks and summaries. Also re-indexed the last difficulty adjustments. Will re-index latest hashrates in a few seconds.`, logger.tags.mining); | ||||||
|             indexer.reindex(); |             indexer.reindex(); | ||||||
|  | 
 | ||||||
|  |             websocketHandler.handleReorg(); | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -333,6 +333,40 @@ class WebsocketHandler { | |||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   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); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   async $handleMempoolChange(newMempool: { [txid: string]: MempoolTransactionExtended }, mempoolSize: number, |   async $handleMempoolChange(newMempool: { [txid: string]: MempoolTransactionExtended }, mempoolSize: number, | ||||||
|     newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[]): Promise<void> { |     newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[]): Promise<void> { | ||||||
|     if (!this.wss) { |     if (!this.wss) { | ||||||
|  | |||||||
| @ -112,7 +112,7 @@ export class BisqTransactionComponent implements OnInit, OnDestroy { | |||||||
|       this.error = error; |       this.error = error; | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     this.latestBlock$ = this.stateService.blocks$.pipe(map((([block]) => block))); |     this.latestBlock$ = this.stateService.blocks$.pipe(map((blocks) => blocks[0])); | ||||||
| 
 | 
 | ||||||
|     this.stateService.bsqPrice$ |     this.stateService.bsqPrice$ | ||||||
|       .subscribe((bsqPrice) => { |       .subscribe((bsqPrice) => { | ||||||
|  | |||||||
| @ -27,7 +27,7 @@ export class BisqTransfersComponent implements OnInit, OnChanges { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngOnInit() { |   ngOnInit() { | ||||||
|     this.latestBlock$ = this.stateService.blocks$.pipe(map(([block]) => block)); |     this.latestBlock$ = this.stateService.blocks$.pipe(map((blocks) => blocks[0])); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngOnChanges() { |   ngOnChanges() { | ||||||
|  | |||||||
| @ -14,6 +14,7 @@ import { ApiService } from '../../services/api.service'; | |||||||
| import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component'; | import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component'; | ||||||
| import { detectWebGL } from '../../shared/graphs.utils'; | import { detectWebGL } from '../../shared/graphs.utils'; | ||||||
| import { PriceService, Price } from '../../services/price.service'; | import { PriceService, Price } from '../../services/price.service'; | ||||||
|  | import { CacheService } from '../../services/cache.service'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-block', |   selector: 'app-block', | ||||||
| @ -72,6 +73,7 @@ export class BlockComponent implements OnInit, OnDestroy { | |||||||
|   auditSubscription: Subscription; |   auditSubscription: Subscription; | ||||||
|   keyNavigationSubscription: Subscription; |   keyNavigationSubscription: Subscription; | ||||||
|   blocksSubscription: Subscription; |   blocksSubscription: Subscription; | ||||||
|  |   cacheBlocksSubscription: Subscription; | ||||||
|   networkChangedSubscription: Subscription; |   networkChangedSubscription: Subscription; | ||||||
|   queryParamsSubscription: Subscription; |   queryParamsSubscription: Subscription; | ||||||
|   nextBlockSubscription: Subscription = undefined; |   nextBlockSubscription: Subscription = undefined; | ||||||
| @ -99,6 +101,7 @@ export class BlockComponent implements OnInit, OnDestroy { | |||||||
|     private relativeUrlPipe: RelativeUrlPipe, |     private relativeUrlPipe: RelativeUrlPipe, | ||||||
|     private apiService: ApiService, |     private apiService: ApiService, | ||||||
|     private priceService: PriceService, |     private priceService: PriceService, | ||||||
|  |     private cacheService: CacheService, | ||||||
|   ) { |   ) { | ||||||
|     this.webGlEnabled = detectWebGL(); |     this.webGlEnabled = detectWebGL(); | ||||||
|   } |   } | ||||||
| @ -128,19 +131,27 @@ export class BlockComponent implements OnInit, OnDestroy { | |||||||
|         map((indicators) => indicators['blocktxs-' + this.blockHash] !== undefined ? indicators['blocktxs-' + this.blockHash] : 0) |         map((indicators) => indicators['blocktxs-' + this.blockHash] !== undefined ? indicators['blocktxs-' + this.blockHash] : 0) | ||||||
|       ); |       ); | ||||||
| 
 | 
 | ||||||
|  |     this.cacheBlocksSubscription = this.cacheService.loadedBlocks$.subscribe((block) => { | ||||||
|  |       this.loadedCacheBlock(block); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|     this.blocksSubscription = this.stateService.blocks$ |     this.blocksSubscription = this.stateService.blocks$ | ||||||
|       .subscribe(([block]) => { |       .subscribe((blocks) => { | ||||||
|         this.latestBlock = block; |         this.latestBlock = blocks[0]; | ||||||
|         this.latestBlocks.unshift(block); |         this.latestBlocks = blocks; | ||||||
|         this.latestBlocks = this.latestBlocks.slice(0, this.stateService.env.KEEP_BLOCKS_AMOUNT); |  | ||||||
|         this.setNextAndPreviousBlockLink(); |         this.setNextAndPreviousBlockLink(); | ||||||
| 
 | 
 | ||||||
|         if (block.id === this.blockHash) { |         for (const block of blocks) { | ||||||
|           this.block = block; |           if (block.id === this.blockHash) { | ||||||
|           block.extras.minFee = this.getMinBlockFee(block); |             this.block = block; | ||||||
|           block.extras.maxFee = this.getMaxBlockFee(block); |             block.extras.minFee = this.getMinBlockFee(block); | ||||||
|           if (block?.extras?.reward != undefined) { |             block.extras.maxFee = this.getMaxBlockFee(block); | ||||||
|             this.fees = block.extras.reward / 100000000 - this.blockSubsidy; |             if (block?.extras?.reward != undefined) { | ||||||
|  |               this.fees = block.extras.reward / 100000000 - this.blockSubsidy; | ||||||
|  |             } | ||||||
|  |           } else if (block.height === this.block?.height) { | ||||||
|  |             this.block.stale = true; | ||||||
|  |             this.block.canonical = block.id; | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       }); |       }); | ||||||
| @ -254,6 +265,13 @@ export class BlockComponent implements OnInit, OnDestroy { | |||||||
|         this.transactionsError = null; |         this.transactionsError = null; | ||||||
|         this.isLoadingOverview = true; |         this.isLoadingOverview = true; | ||||||
|         this.overviewError = null; |         this.overviewError = null; | ||||||
|  | 
 | ||||||
|  |         const cachedBlock = this.cacheService.getCachedBlock(block.height); | ||||||
|  |         if (!cachedBlock) { | ||||||
|  |           this.cacheService.loadBlock(block.height); | ||||||
|  |         } else { | ||||||
|  |           this.loadedCacheBlock(cachedBlock); | ||||||
|  |         } | ||||||
|       }), |       }), | ||||||
|       throttleTime(300, asyncScheduler, { leading: true, trailing: true }), |       throttleTime(300, asyncScheduler, { leading: true, trailing: true }), | ||||||
|       shareReplay(1) |       shareReplay(1) | ||||||
| @ -459,6 +477,7 @@ export class BlockComponent implements OnInit, OnDestroy { | |||||||
|     this.auditSubscription?.unsubscribe(); |     this.auditSubscription?.unsubscribe(); | ||||||
|     this.keyNavigationSubscription?.unsubscribe(); |     this.keyNavigationSubscription?.unsubscribe(); | ||||||
|     this.blocksSubscription?.unsubscribe(); |     this.blocksSubscription?.unsubscribe(); | ||||||
|  |     this.cacheBlocksSubscription?.unsubscribe(); | ||||||
|     this.networkChangedSubscription?.unsubscribe(); |     this.networkChangedSubscription?.unsubscribe(); | ||||||
|     this.queryParamsSubscription?.unsubscribe(); |     this.queryParamsSubscription?.unsubscribe(); | ||||||
|     this.timeLtrSubscription?.unsubscribe(); |     this.timeLtrSubscription?.unsubscribe(); | ||||||
| @ -679,4 +698,11 @@ export class BlockComponent implements OnInit, OnDestroy { | |||||||
|     } |     } | ||||||
|     return 0; |     return 0; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   loadedCacheBlock(block: BlockExtended): void { | ||||||
|  |     if (this.block && block.height === this.block.height && block.id !== this.block.id) { | ||||||
|  |       this.block.stale = true; | ||||||
|  |       this.block.canonical = block.id; | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
| @ -36,11 +36,13 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy { | |||||||
|   emptyBlocks: BlockExtended[] = this.mountEmptyBlocks(); |   emptyBlocks: BlockExtended[] = this.mountEmptyBlocks(); | ||||||
|   markHeight: number; |   markHeight: number; | ||||||
|   chainTip: number; |   chainTip: number; | ||||||
|  |   pendingMarkBlock: { animate: boolean, newBlockFromLeft: boolean }; | ||||||
|   blocksSubscription: Subscription; |   blocksSubscription: Subscription; | ||||||
|   blockPageSubscription: Subscription; |   blockPageSubscription: Subscription; | ||||||
|   networkSubscription: Subscription; |   networkSubscription: Subscription; | ||||||
|   tabHiddenSubscription: Subscription; |   tabHiddenSubscription: Subscription; | ||||||
|   markBlockSubscription: Subscription; |   markBlockSubscription: Subscription; | ||||||
|  |   txConfirmedSubscription: Subscription; | ||||||
|   loadingBlocks$: Observable<boolean>; |   loadingBlocks$: Observable<boolean>; | ||||||
|   blockStyles = []; |   blockStyles = []; | ||||||
|   emptyBlockStyles = []; |   emptyBlockStyles = []; | ||||||
| @ -82,7 +84,6 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngOnInit() { |   ngOnInit() { | ||||||
|     this.chainTip = this.stateService.latestBlockHeight; |  | ||||||
|     this.dynamicBlocksAmount = Math.min(8, this.stateService.env.KEEP_BLOCKS_AMOUNT); |     this.dynamicBlocksAmount = Math.min(8, this.stateService.env.KEEP_BLOCKS_AMOUNT); | ||||||
| 
 | 
 | ||||||
|     if (['', 'testnet', 'signet'].includes(this.stateService.network)) { |     if (['', 'testnet', 'signet'].includes(this.stateService.network)) { | ||||||
| @ -104,31 +105,22 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy { | |||||||
|     this.tabHiddenSubscription = this.stateService.isTabHidden$.subscribe((tabHidden) => this.tabHidden = tabHidden); |     this.tabHiddenSubscription = this.stateService.isTabHidden$.subscribe((tabHidden) => this.tabHidden = tabHidden); | ||||||
|     if (!this.static) { |     if (!this.static) { | ||||||
|       this.blocksSubscription = this.stateService.blocks$ |       this.blocksSubscription = this.stateService.blocks$ | ||||||
|         .subscribe(([block, txConfirmed]) => { |         .subscribe((blocks) => { | ||||||
|           if (this.blocks.some((b) => b.height === block.height)) { |           if (!blocks?.length) { | ||||||
|             return; |             return; | ||||||
|           } |           } | ||||||
|  |           const latestHeight = blocks[0].height; | ||||||
|  |           const animate = this.chainTip != null && latestHeight > this.chainTip; | ||||||
| 
 | 
 | ||||||
|           if (this.blocks.length && block.height !== this.blocks[0].height + 1) { |           for (const block of blocks) { | ||||||
|             this.blocks = []; |             block.extras.minFee = this.getMinBlockFee(block); | ||||||
|             this.blocksFilled = false; |             block.extras.maxFee = this.getMaxBlockFee(block); | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|           block.extras.minFee = this.getMinBlockFee(block); |           this.blocks = blocks; | ||||||
|           block.extras.maxFee = this.getMaxBlockFee(block); |  | ||||||
| 
 |  | ||||||
|           this.blocks.unshift(block); |  | ||||||
|           this.blocks = this.blocks.slice(0, this.dynamicBlocksAmount); |  | ||||||
| 
 |  | ||||||
|           if (txConfirmed && block.height > this.chainTip) { |  | ||||||
|             this.markHeight = block.height; |  | ||||||
|             this.moveArrowToPosition(true, true); |  | ||||||
|           } else { |  | ||||||
|             this.moveArrowToPosition(true, false); |  | ||||||
|           } |  | ||||||
| 
 | 
 | ||||||
|           this.blockStyles = []; |           this.blockStyles = []; | ||||||
|           if (this.blocksFilled && block.height > this.chainTip) { |           if (animate) { | ||||||
|             this.blocks.forEach((b, i) => this.blockStyles.push(this.getStyleForBlock(b, i, i ? -this.blockOffset : -this.dividerBlockOffset))); |             this.blocks.forEach((b, i) => this.blockStyles.push(this.getStyleForBlock(b, i, i ? -this.blockOffset : -this.dividerBlockOffset))); | ||||||
|             setTimeout(() => { |             setTimeout(() => { | ||||||
|               this.blockStyles = []; |               this.blockStyles = []; | ||||||
| @ -139,13 +131,23 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy { | |||||||
|             this.blocks.forEach((b, i) => this.blockStyles.push(this.getStyleForBlock(b, i))); |             this.blocks.forEach((b, i) => this.blockStyles.push(this.getStyleForBlock(b, i))); | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|           if (this.blocks.length === this.dynamicBlocksAmount) { |           this.chainTip = latestHeight; | ||||||
|             this.blocksFilled = true; |  | ||||||
|           } |  | ||||||
| 
 | 
 | ||||||
|           this.chainTip = Math.max(this.chainTip, block.height); |           if (this.pendingMarkBlock) { | ||||||
|  |             this.moveArrowToPosition(this.pendingMarkBlock.animate, this.pendingMarkBlock.newBlockFromLeft); | ||||||
|  |             this.pendingMarkBlock = null; | ||||||
|  |           } | ||||||
|           this.cd.markForCheck(); |           this.cd.markForCheck(); | ||||||
|         }); |         }); | ||||||
|  | 
 | ||||||
|  |       this.txConfirmedSubscription = this.stateService.txConfirmed$.subscribe(([txid, block]) => { | ||||||
|  |         if (txid) { | ||||||
|  |           this.markHeight = block.height; | ||||||
|  |           this.moveArrowToPosition(true, true); | ||||||
|  |         } else { | ||||||
|  |           this.moveArrowToPosition(true, false); | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|     } else { |     } else { | ||||||
|       this.blockPageSubscription = this.cacheService.loadedBlocks$.subscribe((block) => { |       this.blockPageSubscription = this.cacheService.loadedBlocks$.subscribe((block) => { | ||||||
|         if (block.height <= this.height && block.height > this.height - this.count) { |         if (block.height <= this.height && block.height > this.height - this.count) { | ||||||
| @ -164,9 +166,9 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy { | |||||||
|         this.cd.markForCheck(); |         this.cd.markForCheck(); | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       if (this.static) { |     if (this.static) { | ||||||
|         this.updateStaticBlocks(); |       this.updateStaticBlocks(); | ||||||
|       } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngOnChanges(changes: SimpleChanges): void { |   ngOnChanges(changes: SimpleChanges): void { | ||||||
| @ -190,6 +192,9 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy { | |||||||
|     if (this.blockPageSubscription) { |     if (this.blockPageSubscription) { | ||||||
|       this.blockPageSubscription.unsubscribe(); |       this.blockPageSubscription.unsubscribe(); | ||||||
|     } |     } | ||||||
|  |     if (this.txConfirmedSubscription) { | ||||||
|  |       this.txConfirmedSubscription.unsubscribe(); | ||||||
|  |     } | ||||||
|     this.networkSubscription.unsubscribe(); |     this.networkSubscription.unsubscribe(); | ||||||
|     this.tabHiddenSubscription.unsubscribe(); |     this.tabHiddenSubscription.unsubscribe(); | ||||||
|     this.markBlockSubscription.unsubscribe(); |     this.markBlockSubscription.unsubscribe(); | ||||||
| @ -202,6 +207,9 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy { | |||||||
|       this.arrowVisible = false; |       this.arrowVisible = false; | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|  |     if (this.chainTip == null) { | ||||||
|  |       this.pendingMarkBlock = { animate, newBlockFromLeft }; | ||||||
|  |     } | ||||||
|     const blockindex = this.blocks.findIndex((b) => b.height === this.markHeight); |     const blockindex = this.blocks.findIndex((b) => b.height === this.markHeight); | ||||||
|     if (blockindex > -1) { |     if (blockindex > -1) { | ||||||
|       if (!animate) { |       if (!animate) { | ||||||
|  | |||||||
| @ -82,12 +82,12 @@ export class BlocksList implements OnInit { | |||||||
|       ), |       ), | ||||||
|       this.stateService.blocks$ |       this.stateService.blocks$ | ||||||
|         .pipe( |         .pipe( | ||||||
|           switchMap((block) => { |           switchMap((blocks) => { | ||||||
|             if (block[0].height <= this.lastBlockHeight) { |             if (blocks[0].height <= this.lastBlockHeight) { | ||||||
|               return [null]; // Return an empty stream so the last pipe is not executed
 |               return [null]; // Return an empty stream so the last pipe is not executed
 | ||||||
|             } |             } | ||||||
|             this.lastBlockHeight = block[0].height; |             this.lastBlockHeight = blocks[0].height; | ||||||
|             return [block]; |             return blocks; | ||||||
|           }) |           }) | ||||||
|         ) |         ) | ||||||
|     ]) |     ]) | ||||||
|  | |||||||
| @ -39,13 +39,10 @@ export class ClockFaceComponent implements OnInit, OnChanges, OnDestroy { | |||||||
|       }) |       }) | ||||||
|     ).subscribe(); |     ).subscribe(); | ||||||
|     this.blocksSubscription = this.stateService.blocks$ |     this.blocksSubscription = this.stateService.blocks$ | ||||||
|       .subscribe(([block]) => { |       .subscribe((blocks) => { | ||||||
|         if (block) { |         this.blockTimes = blocks.map(block => [block.height, new Date(block.timestamp * 1000)]); | ||||||
|           this.blockTimes.push([block.height, new Date(block.timestamp * 1000)]); |         this.blockTimes = this.blockTimes.sort((a, b) => a[1].getTime() - b[1].getTime()); | ||||||
|           // using block-reported times, so ensure they are sorted chronologically
 |         this.updateSegments(); | ||||||
|           this.blockTimes = this.blockTimes.sort((a, b) => a[1].getTime() - b[1].getTime()); |  | ||||||
|           this.updateSegments(); |  | ||||||
|         } |  | ||||||
|       }); |       }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -60,14 +60,11 @@ export class ClockComponent implements OnInit { | |||||||
|     this.websocketService.want(['blocks', 'stats', 'mempool-blocks']); |     this.websocketService.want(['blocks', 'stats', 'mempool-blocks']); | ||||||
| 
 | 
 | ||||||
|     this.blocksSubscription = this.stateService.blocks$ |     this.blocksSubscription = this.stateService.blocks$ | ||||||
|       .subscribe(([block]) => { |       .subscribe((blocks) => { | ||||||
|         if (block) { |         this.blocks = blocks.slice(0, 16); | ||||||
|           this.blocks.unshift(block); |         if (this.blocks[this.blockIndex]) { | ||||||
|           this.blocks = this.blocks.slice(0, 16); |           this.blockStyle = this.getStyleForBlock(this.blocks[this.blockIndex]); | ||||||
|           if (this.blocks[this.blockIndex]) { |           this.cd.markForCheck(); | ||||||
|             this.blockStyle = this.getStyleForBlock(this.blocks[this.blockIndex]); |  | ||||||
|             this.cd.markForCheck(); |  | ||||||
|           } |  | ||||||
|         } |         } | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -38,11 +38,12 @@ export class DifficultyMiningComponent implements OnInit { | |||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     this.isLoadingWebSocket$ = this.stateService.isLoadingWebSocket$; |     this.isLoadingWebSocket$ = this.stateService.isLoadingWebSocket$; | ||||||
|     this.difficultyEpoch$ = combineLatest([ |     this.difficultyEpoch$ = combineLatest([ | ||||||
|       this.stateService.blocks$.pipe(map(([block]) => block)), |       this.stateService.blocks$, | ||||||
|       this.stateService.difficultyAdjustment$, |       this.stateService.difficultyAdjustment$, | ||||||
|     ]) |     ]) | ||||||
|     .pipe( |     .pipe( | ||||||
|       map(([block, da]) => { |       map(([blocks, da]) => { | ||||||
|  |         const maxHeight = blocks.reduce((max, block) => Math.max(max, block.height), 0); | ||||||
|         let colorAdjustments = '#ffffff66'; |         let colorAdjustments = '#ffffff66'; | ||||||
|         if (da.difficultyChange > 0) { |         if (da.difficultyChange > 0) { | ||||||
|           colorAdjustments = '#3bcc49'; |           colorAdjustments = '#3bcc49'; | ||||||
| @ -63,7 +64,7 @@ export class DifficultyMiningComponent implements OnInit { | |||||||
|           colorPreviousAdjustments = '#ffffff66'; |           colorPreviousAdjustments = '#ffffff66'; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const blocksUntilHalving = 210000 - (block.height % 210000); |         const blocksUntilHalving = 210000 - (maxHeight % 210000); | ||||||
|         const timeUntilHalving = new Date().getTime() + (blocksUntilHalving * 600000); |         const timeUntilHalving = new Date().getTime() + (blocksUntilHalving * 600000); | ||||||
| 
 | 
 | ||||||
|         const data = { |         const data = { | ||||||
|  | |||||||
| @ -67,11 +67,12 @@ export class DifficultyComponent implements OnInit { | |||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     this.isLoadingWebSocket$ = this.stateService.isLoadingWebSocket$; |     this.isLoadingWebSocket$ = this.stateService.isLoadingWebSocket$; | ||||||
|     this.difficultyEpoch$ = combineLatest([ |     this.difficultyEpoch$ = combineLatest([ | ||||||
|       this.stateService.blocks$.pipe(map(([block]) => block)), |       this.stateService.blocks$, | ||||||
|       this.stateService.difficultyAdjustment$, |       this.stateService.difficultyAdjustment$, | ||||||
|     ]) |     ]) | ||||||
|     .pipe( |     .pipe( | ||||||
|       map(([block, da]) => { |       map(([blocks, da]) => { | ||||||
|  |         const maxHeight = blocks.reduce((max, block) => Math.max(max, block.height), 0); | ||||||
|         let colorAdjustments = '#ffffff66'; |         let colorAdjustments = '#ffffff66'; | ||||||
|         if (da.difficultyChange > 0) { |         if (da.difficultyChange > 0) { | ||||||
|           colorAdjustments = '#3bcc49'; |           colorAdjustments = '#3bcc49'; | ||||||
| @ -92,7 +93,7 @@ export class DifficultyComponent implements OnInit { | |||||||
|           colorPreviousAdjustments = '#ffffff66'; |           colorPreviousAdjustments = '#ffffff66'; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const blocksUntilHalving = 210000 - (block.height % 210000); |         const blocksUntilHalving = 210000 - (maxHeight % 210000); | ||||||
|         const timeUntilHalving = new Date().getTime() + (blocksUntilHalving * 600000); |         const timeUntilHalving = new Date().getTime() + (blocksUntilHalving * 600000); | ||||||
|         const newEpochStart = Math.floor(this.stateService.latestBlockHeight / EPOCH_BLOCK_LENGTH) * EPOCH_BLOCK_LENGTH; |         const newEpochStart = Math.floor(this.stateService.latestBlockHeight / EPOCH_BLOCK_LENGTH) * EPOCH_BLOCK_LENGTH; | ||||||
|         const newExpectedHeight = Math.floor(newEpochStart + da.expectedBlocks); |         const newExpectedHeight = Math.floor(newEpochStart + da.expectedBlocks); | ||||||
|  | |||||||
| @ -124,7 +124,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { | |||||||
|     ) |     ) | ||||||
|     .pipe( |     .pipe( | ||||||
|       switchMap(() => combineLatest([ |       switchMap(() => combineLatest([ | ||||||
|         this.stateService.blocks$.pipe(map(([block]) => block)), |         this.stateService.blocks$.pipe(map((blocks) => blocks[0])), | ||||||
|         this.stateService.mempoolBlocks$ |         this.stateService.mempoolBlocks$ | ||||||
|           .pipe( |           .pipe( | ||||||
|             map((mempoolBlocks) => { |             map((mempoolBlocks) => { | ||||||
| @ -186,8 +186,11 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { | |||||||
|         this.cd.markForCheck(); |         this.cd.markForCheck(); | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|     this.blockSubscription = this.stateService.blocks$ |     this.blockSubscription = this.stateService.blocks$.pipe(map((blocks) => blocks[0])) | ||||||
|       .subscribe(([block]) => { |       .subscribe((block) => { | ||||||
|  |         if (!block) { | ||||||
|  |           return; | ||||||
|  |         } | ||||||
|         if (this.chainTip === -1) { |         if (this.chainTip === -1) { | ||||||
|           this.animateEntry = block.height === this.stateService.latestBlockHeight; |           this.animateEntry = block.height === this.stateService.latestBlockHeight; | ||||||
|         } else { |         } else { | ||||||
| @ -221,8 +224,8 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { | |||||||
|           this.router.navigate([this.relativeUrlPipe.transform('mempool-block/'), this.markIndex - 1]); |           this.router.navigate([this.relativeUrlPipe.transform('mempool-block/'), this.markIndex - 1]); | ||||||
|         } else { |         } else { | ||||||
|           this.stateService.blocks$ |           this.stateService.blocks$ | ||||||
|             .pipe(take(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT)) |             .pipe(map((blocks) => blocks[0])) | ||||||
|             .subscribe(([block]) => { |             .subscribe((block) => { | ||||||
|               if (this.stateService.latestBlockHeight === block.height) { |               if (this.stateService.latestBlockHeight === block.height) { | ||||||
|                 this.router.navigate([this.relativeUrlPipe.transform('/block/'), block.id], { state: { data: { block } }}); |                 this.router.navigate([this.relativeUrlPipe.transform('/block/'), block.id], { state: { data: { block } }}); | ||||||
|               } |               } | ||||||
| @ -297,7 +300,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { | |||||||
|     while (blocks.length > blocksAmount) { |     while (blocks.length > blocksAmount) { | ||||||
|       const block = blocks.pop(); |       const block = blocks.pop(); | ||||||
|       if (!this.count) { |       if (!this.count) { | ||||||
|         const lastBlock = blocks[blocks.length - 1]; |         const lastBlock = blocks[0]; | ||||||
|         lastBlock.blockSize += block.blockSize; |         lastBlock.blockSize += block.blockSize; | ||||||
|         lastBlock.blockVSize += block.blockVSize; |         lastBlock.blockVSize += block.blockVSize; | ||||||
|         lastBlock.nTx += block.nTx; |         lastBlock.nTx += block.nTx; | ||||||
| @ -308,7 +311,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     if (blocks.length) { |     if (blocks.length) { | ||||||
|       blocks[blocks.length - 1].isStack = blocks[blocks.length - 1].blockVSize > this.stateService.blockVSize; |       blocks[0].isStack = blocks[0].blockVSize > this.stateService.blockVSize; | ||||||
|     } |     } | ||||||
|     return blocks; |     return blocks; | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -37,7 +37,7 @@ export class PoolComponent implements OnInit { | |||||||
| 
 | 
 | ||||||
|   auditAvailable = false; |   auditAvailable = false; | ||||||
| 
 | 
 | ||||||
|   loadMoreSubject: BehaviorSubject<number> = new BehaviorSubject(this.blocks[this.blocks.length - 1]?.height); |   loadMoreSubject: BehaviorSubject<number> = new BehaviorSubject(this.blocks[0]?.height); | ||||||
| 
 | 
 | ||||||
|   constructor( |   constructor( | ||||||
|     @Inject(LOCALE_ID) public locale: string, |     @Inject(LOCALE_ID) public locale: string, | ||||||
| @ -68,7 +68,7 @@ export class PoolComponent implements OnInit { | |||||||
|           return this.apiService.getPoolStats$(slug); |           return this.apiService.getPoolStats$(slug); | ||||||
|         }), |         }), | ||||||
|         tap(() => { |         tap(() => { | ||||||
|           this.loadMoreSubject.next(this.blocks[this.blocks.length - 1]?.height); |           this.loadMoreSubject.next(this.blocks[0]?.height); | ||||||
|         }), |         }), | ||||||
|         map((poolStats) => { |         map((poolStats) => { | ||||||
|           this.seoService.setTitle(poolStats.pool.name); |           this.seoService.setTitle(poolStats.pool.name); | ||||||
| @ -91,7 +91,7 @@ export class PoolComponent implements OnInit { | |||||||
|           if (this.slug === undefined) { |           if (this.slug === undefined) { | ||||||
|             return []; |             return []; | ||||||
|           } |           } | ||||||
|           return this.apiService.getPoolBlocks$(this.slug, this.blocks[this.blocks.length - 1]?.height); |           return this.apiService.getPoolBlocks$(this.slug, this.blocks[0]?.height); | ||||||
|         }), |         }), | ||||||
|         tap((newBlocks) => { |         tap((newBlocks) => { | ||||||
|           this.blocks = this.blocks.concat(newBlocks); |           this.blocks = this.blocks.concat(newBlocks); | ||||||
| @ -237,7 +237,7 @@ export class PoolComponent implements OnInit { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   loadMore() { |   loadMore() { | ||||||
|     this.loadMoreSubject.next(this.blocks[this.blocks.length - 1]?.height); |     this.loadMoreSubject.next(this.blocks[0]?.height); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   trackByBlock(index: number, block: BlockExtended) { |   trackByBlock(index: number, block: BlockExtended) { | ||||||
|  | |||||||
| @ -29,11 +29,12 @@ export class RewardStatsComponent implements OnInit { | |||||||
|       // Or when we receive a newer block, newer than the latest reward stats api call
 |       // Or when we receive a newer block, newer than the latest reward stats api call
 | ||||||
|       this.stateService.blocks$ |       this.stateService.blocks$ | ||||||
|         .pipe( |         .pipe( | ||||||
|           switchMap((block) => { |           switchMap((blocks) => { | ||||||
|             if (block[0].height <= this.lastBlockHeight) { |             const maxHeight = blocks.reduce((max, block) => Math.max(max, block.height), 0); | ||||||
|  |             if (maxHeight <= this.lastBlockHeight) { | ||||||
|               return []; // Return an empty stream so the last pipe is not executed
 |               return []; // Return an empty stream so the last pipe is not executed
 | ||||||
|             } |             } | ||||||
|             this.lastBlockHeight = block[0].height; |             this.lastBlockHeight = maxHeight; | ||||||
|             return this.apiService.getRewardStats$(); |             return this.apiService.getRewardStats$(); | ||||||
|           }) |           }) | ||||||
|         ) |         ) | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ import { Component, ElementRef, HostListener, OnInit, OnDestroy, ViewChild, Inpu | |||||||
| import { Subscription } from 'rxjs'; | import { Subscription } from 'rxjs'; | ||||||
| import { MarkBlockState, StateService } from '../../services/state.service'; | import { MarkBlockState, StateService } from '../../services/state.service'; | ||||||
| import { specialBlocks } from '../../app.constants'; | import { specialBlocks } from '../../app.constants'; | ||||||
|  | import { BlockExtended } from '../../interfaces/node-api.interface'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-start', |   selector: 'app-start', | ||||||
| @ -55,8 +56,8 @@ export class StartComponent implements OnInit, OnDestroy { | |||||||
| 
 | 
 | ||||||
|   ngOnInit() { |   ngOnInit() { | ||||||
|     this.firstPageWidth = 40 + (this.blockWidth * this.dynamicBlocksAmount); |     this.firstPageWidth = 40 + (this.blockWidth * this.dynamicBlocksAmount); | ||||||
|     this.blockCounterSubscription = this.stateService.blocks$.subscribe(() => { |     this.blockCounterSubscription = this.stateService.blocks$.subscribe((blocks) => { | ||||||
|       this.blockCount++; |       this.blockCount = blocks.length; | ||||||
|       this.dynamicBlocksAmount = Math.min(this.blockCount, this.stateService.env.KEEP_BLOCKS_AMOUNT, 8); |       this.dynamicBlocksAmount = Math.min(this.blockCount, this.stateService.env.KEEP_BLOCKS_AMOUNT, 8); | ||||||
|       this.firstPageWidth = 40 + (this.blockWidth * this.dynamicBlocksAmount); |       this.firstPageWidth = 40 + (this.blockWidth * this.dynamicBlocksAmount); | ||||||
|       if (this.blockCount <= Math.min(8, this.stateService.env.KEEP_BLOCKS_AMOUNT)) { |       if (this.blockCount <= Math.min(8, this.stateService.env.KEEP_BLOCKS_AMOUNT)) { | ||||||
| @ -110,9 +111,12 @@ export class StartComponent implements OnInit, OnDestroy { | |||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|     this.stateService.blocks$ |     this.stateService.blocks$ | ||||||
|       .subscribe((blocks: any) => { |       .subscribe((blocks: BlockExtended[]) => { | ||||||
|         this.countdown = 0; |         this.countdown = 0; | ||||||
|         const block = blocks[0]; |         const block = blocks[0]; | ||||||
|  |         if (!block) { | ||||||
|  |           return; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         for (const sb in specialBlocks) { |         for (const sb in specialBlocks) { | ||||||
|           if (specialBlocks[sb].networks.includes(this.stateService.network || 'mainnet')) { |           if (specialBlocks[sb].networks.includes(this.stateService.network || 'mainnet')) { | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ import { | |||||||
|   tap |   tap | ||||||
| } from 'rxjs/operators'; | } from 'rxjs/operators'; | ||||||
| import { Transaction } from '../../interfaces/electrs.interface'; | import { Transaction } from '../../interfaces/electrs.interface'; | ||||||
| import { of, merge, Subscription, Observable, Subject, timer, from, throwError } from 'rxjs'; | import { of, merge, Subscription, Observable, Subject, from, throwError } from 'rxjs'; | ||||||
| import { StateService } from '../../services/state.service'; | import { StateService } from '../../services/state.service'; | ||||||
| import { CacheService } from '../../services/cache.service'; | import { CacheService } from '../../services/cache.service'; | ||||||
| import { WebsocketService } from '../../services/websocket.service'; | import { WebsocketService } from '../../services/websocket.service'; | ||||||
| @ -49,10 +49,10 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | |||||||
|   txReplacedSubscription: Subscription; |   txReplacedSubscription: Subscription; | ||||||
|   txRbfInfoSubscription: Subscription; |   txRbfInfoSubscription: Subscription; | ||||||
|   mempoolPositionSubscription: Subscription; |   mempoolPositionSubscription: Subscription; | ||||||
|   blocksSubscription: Subscription; |  | ||||||
|   queryParamsSubscription: Subscription; |   queryParamsSubscription: Subscription; | ||||||
|   urlFragmentSubscription: Subscription; |   urlFragmentSubscription: Subscription; | ||||||
|   mempoolBlocksSubscription: Subscription; |   mempoolBlocksSubscription: Subscription; | ||||||
|  |   blocksSubscription: Subscription; | ||||||
|   fragmentParams: URLSearchParams; |   fragmentParams: URLSearchParams; | ||||||
|   rbfTransaction: undefined | Transaction; |   rbfTransaction: undefined | Transaction; | ||||||
|   replaced: boolean = false; |   replaced: boolean = false; | ||||||
| @ -131,6 +131,10 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | |||||||
|       this.outputIndex = (!isNaN(vout) && vout >= 0) ? vout : null; |       this.outputIndex = (!isNaN(vout) && vout >= 0) ? vout : null; | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  |     this.blocksSubscription = this.stateService.blocks$.subscribe((blocks) => { | ||||||
|  |       this.latestBlock = blocks[0]; | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|     this.fetchCpfpSubscription = this.fetchCpfp$ |     this.fetchCpfpSubscription = this.fetchCpfp$ | ||||||
|       .pipe( |       .pipe( | ||||||
|         switchMap((txId) => |         switchMap((txId) => | ||||||
| @ -391,9 +395,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | |||||||
|         } |         } | ||||||
|       ); |       ); | ||||||
| 
 | 
 | ||||||
|     this.blocksSubscription = this.stateService.blocks$.subscribe(([block, txConfirmed]) => { |     this.stateService.txConfirmed$.subscribe(([txConfirmed, block]) => { | ||||||
|       this.latestBlock = block; |  | ||||||
| 
 |  | ||||||
|       if (txConfirmed && this.tx && !this.tx.status.confirmed && txConfirmed === this.tx.txid) { |       if (txConfirmed && this.tx && !this.tx.status.confirmed && txConfirmed === this.tx.txid) { | ||||||
|         this.tx.status = { |         this.tx.status = { | ||||||
|           confirmed: true, |           confirmed: true, | ||||||
| @ -593,13 +595,13 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | |||||||
|     this.fetchCachedTxSubscription.unsubscribe(); |     this.fetchCachedTxSubscription.unsubscribe(); | ||||||
|     this.txReplacedSubscription.unsubscribe(); |     this.txReplacedSubscription.unsubscribe(); | ||||||
|     this.txRbfInfoSubscription.unsubscribe(); |     this.txRbfInfoSubscription.unsubscribe(); | ||||||
|     this.blocksSubscription.unsubscribe(); |  | ||||||
|     this.queryParamsSubscription.unsubscribe(); |     this.queryParamsSubscription.unsubscribe(); | ||||||
|     this.flowPrefSubscription.unsubscribe(); |     this.flowPrefSubscription.unsubscribe(); | ||||||
|     this.urlFragmentSubscription.unsubscribe(); |     this.urlFragmentSubscription.unsubscribe(); | ||||||
|     this.mempoolBlocksSubscription.unsubscribe(); |     this.mempoolBlocksSubscription.unsubscribe(); | ||||||
|     this.mempoolPositionSubscription.unsubscribe(); |     this.mempoolPositionSubscription.unsubscribe(); | ||||||
|     this.mempoolBlocksSubscription.unsubscribe(); |     this.mempoolBlocksSubscription.unsubscribe(); | ||||||
|  |     this.blocksSubscription.unsubscribe(); | ||||||
|     this.leaveTransaction(); |     this.leaveTransaction(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -56,7 +56,7 @@ export class TransactionsListComponent implements OnInit, OnChanges { | |||||||
|   ) { } |   ) { } | ||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     this.latestBlock$ = this.stateService.blocks$.pipe(map(([block]) => block)); |     this.latestBlock$ = this.stateService.blocks$.pipe(map((blocks) => blocks[0])); | ||||||
|     this.stateService.networkChanged$.subscribe((network) => this.network = network); |     this.stateService.networkChanged$.subscribe((network) => this.network = network); | ||||||
| 
 | 
 | ||||||
|     if (this.network === 'liquid' || this.network === 'liquidtestnet') { |     if (this.network === 'liquid' || this.network === 'liquidtestnet') { | ||||||
|  | |||||||
| @ -132,26 +132,19 @@ export class DashboardComponent implements OnInit, OnDestroy { | |||||||
| 
 | 
 | ||||||
|     this.blocks$ = this.stateService.blocks$ |     this.blocks$ = this.stateService.blocks$ | ||||||
|       .pipe( |       .pipe( | ||||||
|         tap(([block]) => { |         tap((blocks) => { | ||||||
|           this.latestBlockHeight = block.height; |           this.latestBlockHeight = blocks[0].height; | ||||||
|         }), |         }), | ||||||
|         scan((acc, [block]) => { |         switchMap((blocks) => { | ||||||
|           if (acc.find((b) => b.height == block.height)) { |  | ||||||
|             return acc; |  | ||||||
|           } |  | ||||||
|           acc.unshift(block); |  | ||||||
|           acc = acc.slice(0, 6); |  | ||||||
| 
 |  | ||||||
|           if (this.stateService.env.MINING_DASHBOARD === true) { |           if (this.stateService.env.MINING_DASHBOARD === true) { | ||||||
|             for (const block of acc) { |             for (const block of blocks) { | ||||||
|               // @ts-ignore: Need to add an extra field for the template
 |               // @ts-ignore: Need to add an extra field for the template
 | ||||||
|               block.extras.pool.logo = `/resources/mining-pools/` + |               block.extras.pool.logo = `/resources/mining-pools/` + | ||||||
|                 block.extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg'; |                 block.extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg'; | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
| 
 |           return of(blocks.slice(0, 6)); | ||||||
|           return acc; |         }) | ||||||
|         }, []), |  | ||||||
|       ); |       ); | ||||||
| 
 | 
 | ||||||
|     this.transactions$ = this.stateService.transactions$ |     this.transactions$ = this.stateService.transactions$ | ||||||
|  | |||||||
| @ -18,6 +18,7 @@ export class CacheService { | |||||||
|   txCache: { [txid: string]: Transaction } = {}; |   txCache: { [txid: string]: Transaction } = {}; | ||||||
| 
 | 
 | ||||||
|   network: string; |   network: string; | ||||||
|  |   blockHashCache: { [hash: string]: BlockExtended } = {}; | ||||||
|   blockCache: { [height: number]: BlockExtended } = {}; |   blockCache: { [height: number]: BlockExtended } = {}; | ||||||
|   blockLoading: { [height: number]: boolean } = {}; |   blockLoading: { [height: number]: boolean } = {}; | ||||||
|   copiesInBlockQueue: { [height: number]: number } = {}; |   copiesInBlockQueue: { [height: number]: number } = {}; | ||||||
| @ -27,8 +28,10 @@ export class CacheService { | |||||||
|     private stateService: StateService, |     private stateService: StateService, | ||||||
|     private apiService: ApiService, |     private apiService: ApiService, | ||||||
|   ) { |   ) { | ||||||
|     this.stateService.blocks$.subscribe(([block]) => { |     this.stateService.blocks$.subscribe((blocks) => { | ||||||
|       this.addBlockToCache(block); |       for (const block of blocks) { | ||||||
|  |         this.addBlockToCache(block); | ||||||
|  |       } | ||||||
|       this.clearBlocks(); |       this.clearBlocks(); | ||||||
|     }); |     }); | ||||||
|     this.stateService.chainTip$.subscribe((height) => { |     this.stateService.chainTip$.subscribe((height) => { | ||||||
| @ -56,8 +59,11 @@ export class CacheService { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   addBlockToCache(block: BlockExtended) { |   addBlockToCache(block: BlockExtended) { | ||||||
|     this.blockCache[block.height] = block; |     if (!this.blockHashCache[block.id]) { | ||||||
|     this.bumpBlockPriority(block.height); |       this.blockHashCache[block.id] = block; | ||||||
|  |       this.blockCache[block.height] = block; | ||||||
|  |       this.bumpBlockPriority(block.height); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async loadBlock(height) { |   async loadBlock(height) { | ||||||
| @ -105,7 +111,9 @@ export class CacheService { | |||||||
|       } else if ((this.tip - height) < KEEP_RECENT_BLOCKS) { |       } else if ((this.tip - height) < KEEP_RECENT_BLOCKS) { | ||||||
|         this.bumpBlockPriority(height); |         this.bumpBlockPriority(height); | ||||||
|       } else { |       } else { | ||||||
|  |         const block = this.blockCache[height]; | ||||||
|         delete this.blockCache[height]; |         delete this.blockCache[height]; | ||||||
|  |         delete this.blockHashCache[block.id]; | ||||||
|         delete this.copiesInBlockQueue[height]; |         delete this.copiesInBlockQueue[height]; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| @ -113,6 +121,7 @@ export class CacheService { | |||||||
| 
 | 
 | ||||||
|   // remove all blocks from the cache
 |   // remove all blocks from the cache
 | ||||||
|   resetBlockCache() { |   resetBlockCache() { | ||||||
|  |     this.blockHashCache = {}; | ||||||
|     this.blockCache = {}; |     this.blockCache = {}; | ||||||
|     this.blockLoading = {}; |     this.blockLoading = {}; | ||||||
|     this.copiesInBlockQueue = {}; |     this.copiesInBlockQueue = {}; | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ import { IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommended | |||||||
| import { BlockExtended, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface'; | import { BlockExtended, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface'; | ||||||
| import { Router, NavigationStart } from '@angular/router'; | import { Router, NavigationStart } from '@angular/router'; | ||||||
| import { isPlatformBrowser } from '@angular/common'; | import { isPlatformBrowser } from '@angular/common'; | ||||||
| import { map, scan, shareReplay } from 'rxjs/operators'; | import { filter, map, scan, shareReplay } from 'rxjs/operators'; | ||||||
| import { StorageService } from './storage.service'; | import { StorageService } from './storage.service'; | ||||||
| 
 | 
 | ||||||
| export interface MarkBlockState { | export interface MarkBlockState { | ||||||
| @ -90,10 +90,12 @@ export class StateService { | |||||||
|   blockVSize: number; |   blockVSize: number; | ||||||
|   env: Env; |   env: Env; | ||||||
|   latestBlockHeight = -1; |   latestBlockHeight = -1; | ||||||
|  |   blocks: BlockExtended[] = []; | ||||||
| 
 | 
 | ||||||
|   networkChanged$ = new ReplaySubject<string>(1); |   networkChanged$ = new ReplaySubject<string>(1); | ||||||
|   lightningChanged$ = new ReplaySubject<boolean>(1); |   lightningChanged$ = new ReplaySubject<boolean>(1); | ||||||
|   blocks$: ReplaySubject<[BlockExtended, string]>; |   blocksSubject$ = new BehaviorSubject<BlockExtended[]>([]); | ||||||
|  |   blocks$: Observable<BlockExtended[]>; | ||||||
|   transactions$ = new ReplaySubject<TransactionStripped>(6); |   transactions$ = new ReplaySubject<TransactionStripped>(6); | ||||||
|   conversions$ = new ReplaySubject<any>(1); |   conversions$ = new ReplaySubject<any>(1); | ||||||
|   bsqPrice$ = new ReplaySubject<number>(1); |   bsqPrice$ = new ReplaySubject<number>(1); | ||||||
| @ -102,6 +104,7 @@ export class StateService { | |||||||
|   mempoolBlockTransactions$ = new Subject<TransactionStripped[]>(); |   mempoolBlockTransactions$ = new Subject<TransactionStripped[]>(); | ||||||
|   mempoolBlockDelta$ = new Subject<MempoolBlockDelta>(); |   mempoolBlockDelta$ = new Subject<MempoolBlockDelta>(); | ||||||
|   liveMempoolBlockTransactions$: Observable<{ [txid: string]: TransactionStripped}>; |   liveMempoolBlockTransactions$: Observable<{ [txid: string]: TransactionStripped}>; | ||||||
|  |   txConfirmed$ = new Subject<[string, BlockExtended]>(); | ||||||
|   txReplaced$ = new Subject<ReplacedTransaction>(); |   txReplaced$ = new Subject<ReplacedTransaction>(); | ||||||
|   txRbfInfo$ = new Subject<RbfTree>(); |   txRbfInfo$ = new Subject<RbfTree>(); | ||||||
|   rbfLatest$ = new Subject<RbfTree[]>(); |   rbfLatest$ = new Subject<RbfTree[]>(); | ||||||
| @ -167,8 +170,6 @@ export class StateService { | |||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     this.blocks$ = new ReplaySubject<[BlockExtended, string]>(this.env.KEEP_BLOCKS_AMOUNT); |  | ||||||
| 
 |  | ||||||
|     this.liveMempoolBlockTransactions$ = merge( |     this.liveMempoolBlockTransactions$ = merge( | ||||||
|       this.mempoolBlockTransactions$.pipe(map(transactions => { return { transactions }; })), |       this.mempoolBlockTransactions$.pipe(map(transactions => { return { transactions }; })), | ||||||
|       this.mempoolBlockDelta$.pipe(map(delta => { return { delta }; })), |       this.mempoolBlockDelta$.pipe(map(delta => { return { delta }; })), | ||||||
| @ -200,11 +201,13 @@ export class StateService { | |||||||
| 
 | 
 | ||||||
|     this.networkChanged$.subscribe((network) => { |     this.networkChanged$.subscribe((network) => { | ||||||
|       this.transactions$ = new ReplaySubject<TransactionStripped>(6); |       this.transactions$ = new ReplaySubject<TransactionStripped>(6); | ||||||
|       this.blocks$ = new ReplaySubject<[BlockExtended, string]>(this.env.KEEP_BLOCKS_AMOUNT); |       this.blocksSubject$.next([]); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     this.blockVSize = this.env.BLOCK_WEIGHT_UNITS / 4; |     this.blockVSize = this.env.BLOCK_WEIGHT_UNITS / 4; | ||||||
| 
 | 
 | ||||||
|  |     this.blocks$ = this.blocksSubject$.pipe(filter(blocks => blocks != null && blocks.length > 0)); | ||||||
|  | 
 | ||||||
|     const savedTimePreference = this.storageService.getValue('time-preference-ltr'); |     const savedTimePreference = this.storageService.getValue('time-preference-ltr'); | ||||||
|     const rtlLanguage = (this.locale.startsWith('ar') || this.locale.startsWith('fa') || this.locale.startsWith('he')); |     const rtlLanguage = (this.locale.startsWith('ar') || this.locale.startsWith('fa') || this.locale.startsWith('he')); | ||||||
|     // default time direction is right-to-left, unless locale is a RTL language
 |     // default time direction is right-to-left, unless locale is a RTL language
 | ||||||
| @ -341,4 +344,15 @@ export class StateService { | |||||||
|       this.chainTip$.next(height); |       this.chainTip$.next(height); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   resetBlocks(blocks: BlockExtended[]): void { | ||||||
|  |     this.blocks = blocks.reverse(); | ||||||
|  |     this.blocksSubject$.next(blocks); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   addBlock(block: BlockExtended): void { | ||||||
|  |     this.blocks.unshift(block); | ||||||
|  |     this.blocks = this.blocks.slice(0, this.env.KEEP_BLOCKS_AMOUNT); | ||||||
|  |     this.blocksSubject$.next(this.blocks); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,13 +1,13 @@ | |||||||
| import { Injectable } from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||||
| import { webSocket, WebSocketSubject } from 'rxjs/webSocket'; | import { webSocket, WebSocketSubject } from 'rxjs/webSocket'; | ||||||
| import { WebsocketResponse, IBackendInfo } from '../interfaces/websocket.interface'; | import { WebsocketResponse } from '../interfaces/websocket.interface'; | ||||||
| import { StateService } from './state.service'; | import { StateService } from './state.service'; | ||||||
| import { Transaction } from '../interfaces/electrs.interface'; | import { Transaction } from '../interfaces/electrs.interface'; | ||||||
| import { Subscription } from 'rxjs'; | import { Subscription } from 'rxjs'; | ||||||
| import { ApiService } from './api.service'; | import { ApiService } from './api.service'; | ||||||
| import { take } from 'rxjs/operators'; | import { take } from 'rxjs/operators'; | ||||||
| import { TransferState, makeStateKey } from '@angular/platform-browser'; | import { TransferState, makeStateKey } from '@angular/platform-browser'; | ||||||
| import { BlockExtended } from '../interfaces/node-api.interface'; | import { CacheService } from './cache.service'; | ||||||
| 
 | 
 | ||||||
| const OFFLINE_RETRY_AFTER_MS = 2000; | const OFFLINE_RETRY_AFTER_MS = 2000; | ||||||
| const OFFLINE_PING_CHECK_AFTER_MS = 30000; | const OFFLINE_PING_CHECK_AFTER_MS = 30000; | ||||||
| @ -40,6 +40,7 @@ export class WebsocketService { | |||||||
|     private stateService: StateService, |     private stateService: StateService, | ||||||
|     private apiService: ApiService, |     private apiService: ApiService, | ||||||
|     private transferState: TransferState, |     private transferState: TransferState, | ||||||
|  |     private cacheService: CacheService, | ||||||
|   ) { |   ) { | ||||||
|     if (!this.stateService.isBrowser) { |     if (!this.stateService.isBrowser) { | ||||||
|       // @ts-ignore
 |       // @ts-ignore
 | ||||||
| @ -239,13 +240,8 @@ export class WebsocketService { | |||||||
| 
 | 
 | ||||||
|     if (response.blocks && response.blocks.length) { |     if (response.blocks && response.blocks.length) { | ||||||
|       const blocks = response.blocks; |       const blocks = response.blocks; | ||||||
|       let maxHeight = 0; |       this.stateService.resetBlocks(blocks); | ||||||
|       blocks.forEach((block: BlockExtended) => { |       const maxHeight = blocks.reduce((max, block) => Math.max(max, block.height), this.stateService.latestBlockHeight); | ||||||
|         if (block.height > this.stateService.latestBlockHeight) { |  | ||||||
|           maxHeight = Math.max(maxHeight, block.height); |  | ||||||
|           this.stateService.blocks$.next([block, '']); |  | ||||||
|         } |  | ||||||
|       }); |  | ||||||
|       this.stateService.updateChainTip(maxHeight); |       this.stateService.updateChainTip(maxHeight); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -260,7 +256,8 @@ export class WebsocketService { | |||||||
|     if (response.block) { |     if (response.block) { | ||||||
|       if (response.block.height === this.stateService.latestBlockHeight + 1) { |       if (response.block.height === this.stateService.latestBlockHeight + 1) { | ||||||
|         this.stateService.updateChainTip(response.block.height); |         this.stateService.updateChainTip(response.block.height); | ||||||
|         this.stateService.blocks$.next([response.block, response.txConfirmed || '']); |         this.stateService.addBlock(response.block); | ||||||
|  |         this.stateService.txConfirmed$.next([response.txConfirmed, response.block]); | ||||||
|       } else if (response.block.height > this.stateService.latestBlockHeight + 1) { |       } else if (response.block.height > this.stateService.latestBlockHeight + 1) { | ||||||
|         reinitBlocks = true; |         reinitBlocks = true; | ||||||
|       } |       } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user