Transition new blocks from the mempool onto the blockchain.
Chime on new blocks. fixes #47 fixes #84
This commit is contained in:
		
							parent
							
								
									ea708de9fb
								
							
						
					
					
						commit
						36e46249b5
					
				@ -1,6 +1,6 @@
 | 
				
			|||||||
const config = require('../../mempool-config.json');
 | 
					const config = require('../../mempool-config.json');
 | 
				
			||||||
import bitcoinApi from './bitcoin/electrs-api';
 | 
					import bitcoinApi from './bitcoin/electrs-api';
 | 
				
			||||||
import { MempoolInfo, TransactionExtended, Transaction } from '../interfaces';
 | 
					import { MempoolInfo, TransactionExtended, Transaction, VbytesPerSecond } from '../interfaces';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Mempool {
 | 
					class Mempool {
 | 
				
			||||||
  private inSync: boolean = false;
 | 
					  private inSync: boolean = false;
 | 
				
			||||||
@ -12,7 +12,7 @@ class Mempool {
 | 
				
			|||||||
  private txPerSecondArray: number[] = [];
 | 
					  private txPerSecondArray: number[] = [];
 | 
				
			||||||
  private txPerSecond: number = 0;
 | 
					  private txPerSecond: number = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private vBytesPerSecondArray: any[] = [];
 | 
					  private vBytesPerSecondArray: VbytesPerSecond[] = [];
 | 
				
			||||||
  private vBytesPerSecond: number = 0;
 | 
					  private vBytesPerSecond: number = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor() {
 | 
					  constructor() {
 | 
				
			||||||
 | 
				
			|||||||
@ -222,3 +222,8 @@ export interface WebsocketResponse {
 | 
				
			|||||||
  'track-address': string;
 | 
					  'track-address': string;
 | 
				
			||||||
  'watch-mempool': boolean;
 | 
					  'watch-mempool': boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface VbytesPerSecond {
 | 
				
			||||||
 | 
					  unixTime: number;
 | 
				
			||||||
 | 
					  vSize: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -35,3 +35,4 @@ export const feeLevels = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 7
 | 
				
			|||||||
  250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000];
 | 
					  250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const ELCTRS_ITEMS_PER_PAGE = 25;
 | 
					export const ELCTRS_ITEMS_PER_PAGE = 25;
 | 
				
			||||||
 | 
					export const KEEP_BLOCKS_AMOUNT = 8;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,2 +1,2 @@
 | 
				
			|||||||
<span *ngIf="multisig" class="badge badge-pill badge-warning">multisig {{ multisigM }} of {{ multisigN }}</span>
 | 
					<span *ngIf="multisig" class="badge badge-pill badge-warning">multisig {{ multisigM }} of {{ multisigN }}</span>
 | 
				
			||||||
<span *ngIf="secondLayerClose" class="badge badge-pill badge-warning">Layer2 Peg-out</span>
 | 
					<span *ngIf="secondLayerClose" class="badge badge-pill badge-warning">Layer{{ network === 'liquid' ? '3' : '2' }} Peg-out</span>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core';
 | 
					import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core';
 | 
				
			||||||
import { Vin, Vout } from '../../interfaces/electrs.interface';
 | 
					import { Vin, Vout } from '../../interfaces/electrs.interface';
 | 
				
			||||||
 | 
					import { StateService } from 'src/app/services/state.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component({
 | 
					@Component({
 | 
				
			||||||
  selector: 'app-address-labels',
 | 
					  selector: 'app-address-labels',
 | 
				
			||||||
@ -8,6 +9,7 @@ import { Vin, Vout } from '../../interfaces/electrs.interface';
 | 
				
			|||||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
					  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
export class AddressLabelsComponent implements OnInit {
 | 
					export class AddressLabelsComponent implements OnInit {
 | 
				
			||||||
 | 
					  network = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Input() vin: Vin;
 | 
					  @Input() vin: Vin;
 | 
				
			||||||
  @Input() vout: Vout;
 | 
					  @Input() vout: Vout;
 | 
				
			||||||
@ -18,7 +20,11 @@ export class AddressLabelsComponent implements OnInit {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  secondLayerClose = false;
 | 
					  secondLayerClose = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor() { }
 | 
					  constructor(
 | 
				
			||||||
 | 
					    stateService: StateService,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    this.network = stateService.network;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ngOnInit() {
 | 
					  ngOnInit() {
 | 
				
			||||||
    if (this.vin) {
 | 
					    if (this.vin) {
 | 
				
			||||||
 | 
				
			|||||||
@ -120,7 +120,7 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.stateService.blocks$
 | 
					    this.stateService.blocks$
 | 
				
			||||||
      .subscribe((block) => this.latestBlock = block);
 | 
					      .subscribe(([block]) => this.latestBlock = block);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.stateService.networkChanged$
 | 
					    this.stateService.networkChanged$
 | 
				
			||||||
      .subscribe((network) => this.network = network);
 | 
					      .subscribe((network) => this.network = network);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
<div class="blocks-container" *ngIf="blocks.length">
 | 
					<div class="blocks-container" *ngIf="blocks.length">
 | 
				
			||||||
  <div *ngFor="let block of blocks; let i = index; trackBy: trackByBlocksFn" >
 | 
					  <div *ngFor="let block of blocks; let i = index; trackBy: trackByBlocksFn" >
 | 
				
			||||||
    <div class="text-center bitcoin-block mined-block" id="bitcoin-block-{{ block.height }}" [ngStyle]="getStyleForBlock(block)">
 | 
					    <div class="text-center bitcoin-block mined-block" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]">
 | 
				
			||||||
      <a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }" class="blockLink"> </a>
 | 
					      <a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }" class="blockLink"> </a>
 | 
				
			||||||
      <div class="block-height">
 | 
					      <div class="block-height">
 | 
				
			||||||
        <a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a>
 | 
					        <a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a>
 | 
				
			||||||
 | 
				
			|||||||
@ -14,7 +14,7 @@
 | 
				
			|||||||
.mined-block {
 | 
					.mined-block {
 | 
				
			||||||
  position: absolute;
 | 
					  position: absolute;
 | 
				
			||||||
  top: 0px;
 | 
					  top: 0px;
 | 
				
			||||||
  transition: 1s;
 | 
					  transition: 2s;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.block-size {
 | 
					.block-size {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,10 @@
 | 
				
			|||||||
import { Component, OnInit, OnDestroy, HostListener } from '@angular/core';
 | 
					import { Component, OnInit, OnDestroy } from '@angular/core';
 | 
				
			||||||
import { Subscription } from 'rxjs';
 | 
					import { Subscription } from 'rxjs';
 | 
				
			||||||
import { Block } from 'src/app/interfaces/electrs.interface';
 | 
					import { Block } from 'src/app/interfaces/electrs.interface';
 | 
				
			||||||
import { StateService } from 'src/app/services/state.service';
 | 
					import { StateService } from 'src/app/services/state.service';
 | 
				
			||||||
import { Router } from '@angular/router';
 | 
					import { Router } from '@angular/router';
 | 
				
			||||||
 | 
					import { AudioService } from 'src/app/services/audio.service';
 | 
				
			||||||
 | 
					import { KEEP_BLOCKS_AMOUNT } from 'src/app/app.constants';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component({
 | 
					@Component({
 | 
				
			||||||
  selector: 'app-blockchain-blocks',
 | 
					  selector: 'app-blockchain-blocks',
 | 
				
			||||||
@ -14,6 +16,7 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
  blocks: Block[] = [];
 | 
					  blocks: Block[] = [];
 | 
				
			||||||
  markHeight: number;
 | 
					  markHeight: number;
 | 
				
			||||||
  blocksSubscription: Subscription;
 | 
					  blocksSubscription: Subscription;
 | 
				
			||||||
 | 
					  blockStyles = [];
 | 
				
			||||||
  interval: any;
 | 
					  interval: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  arrowVisible = false;
 | 
					  arrowVisible = false;
 | 
				
			||||||
@ -30,20 +33,40 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    private stateService: StateService,
 | 
					    private stateService: StateService,
 | 
				
			||||||
    private router: Router,
 | 
					    private router: Router,
 | 
				
			||||||
 | 
					    private audioService: AudioService,
 | 
				
			||||||
  ) { }
 | 
					  ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ngOnInit() {
 | 
					  ngOnInit() {
 | 
				
			||||||
    this.stateService.networkChanged$.subscribe((network) => this.network = network);
 | 
					    this.stateService.networkChanged$.subscribe((network) => this.network = network);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.blocksSubscription = this.stateService.blocks$
 | 
					    this.blocksSubscription = this.stateService.blocks$
 | 
				
			||||||
      .subscribe((block) => {
 | 
					      .subscribe(([block, txConfirmed]) => {
 | 
				
			||||||
 | 
					        const currentBlocksAmount = this.blocks.length;
 | 
				
			||||||
        if (this.blocks.some((b) => b.height === block.height)) {
 | 
					        if (this.blocks.some((b) => b.height === block.height)) {
 | 
				
			||||||
          return;
 | 
					          return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this.blocks.unshift(block);
 | 
					        this.blocks.unshift(block);
 | 
				
			||||||
        this.blocks = this.blocks.slice(0, 8);
 | 
					        this.blocks = this.blocks.slice(0, 8);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.moveArrowToPosition(true);
 | 
					        if (currentBlocksAmount === KEEP_BLOCKS_AMOUNT) {
 | 
				
			||||||
 | 
					          setTimeout(() => this.audioService.playSound('bright-harmony'));
 | 
				
			||||||
 | 
					          block.stage = block.matchRate >= 80 ? 1 : 2;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (txConfirmed) {
 | 
				
			||||||
 | 
					          this.markHeight = block.height;
 | 
				
			||||||
 | 
					          this.moveArrowToPosition(true, true);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          this.moveArrowToPosition(true, false);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.blockStyles = [];
 | 
				
			||||||
 | 
					        this.blocks.forEach((b) => this.blockStyles.push(this.getStyleForBlock(b)));
 | 
				
			||||||
 | 
					        setTimeout(() => {
 | 
				
			||||||
 | 
					          this.blockStyles = [];
 | 
				
			||||||
 | 
					          this.blocks.forEach((b) => this.blockStyles.push(this.getStyleForBlock(b)));
 | 
				
			||||||
 | 
					        }, 50);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.stateService.markBlock$
 | 
					    this.stateService.markBlock$
 | 
				
			||||||
@ -83,20 +106,28 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
    clearInterval(this.interval);
 | 
					    clearInterval(this.interval);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  moveArrowToPosition(animate: boolean) {
 | 
					  moveArrowToPosition(animate: boolean, newBlockFromLeft = false) {
 | 
				
			||||||
    if (!this.markHeight) {
 | 
					    if (!this.markHeight) {
 | 
				
			||||||
      this.arrowVisible = false;
 | 
					      this.arrowVisible = false;
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    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) {
 | 
				
			||||||
        this.transition = 'inherit';
 | 
					        this.transition = 'inherit';
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this.arrowVisible = true;
 | 
					      this.arrowVisible = true;
 | 
				
			||||||
      this.arrowLeftPx = blockindex * 155 + 30;
 | 
					      if (newBlockFromLeft) {
 | 
				
			||||||
      if (!animate) {
 | 
					        this.arrowLeftPx = blockindex * 155 + 30 - 205;
 | 
				
			||||||
        setTimeout(() => this.transition = '1s');
 | 
					        setTimeout(() => {
 | 
				
			||||||
 | 
					          this.transition = '2s';
 | 
				
			||||||
 | 
					          this.arrowLeftPx = blockindex * 155 + 30;
 | 
				
			||||||
 | 
					        }, 50);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        this.arrowLeftPx = blockindex * 155 + 30;
 | 
				
			||||||
 | 
					        if (!animate) {
 | 
				
			||||||
 | 
					          setTimeout(() => this.transition = '2s');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -107,8 +138,15 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  getStyleForBlock(block: Block) {
 | 
					  getStyleForBlock(block: Block) {
 | 
				
			||||||
    const greenBackgroundHeight = 100 - (block.weight / 4000000) * 100;
 | 
					    const greenBackgroundHeight = 100 - (block.weight / 4000000) * 100;
 | 
				
			||||||
 | 
					    let addLeft = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (block.stage === 1) {
 | 
				
			||||||
 | 
					      block.stage = 2;
 | 
				
			||||||
 | 
					      addLeft = -205;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      left: 155 * this.blocks.indexOf(block) + 'px',
 | 
					      left: addLeft + 155 * this.blocks.indexOf(block) + 'px',
 | 
				
			||||||
      background: `repeating-linear-gradient(
 | 
					      background: `repeating-linear-gradient(
 | 
				
			||||||
        #2d3348,
 | 
					        #2d3348,
 | 
				
			||||||
        #2d3348 ${greenBackgroundHeight}%,
 | 
					        #2d3348 ${greenBackgroundHeight}%,
 | 
				
			||||||
 | 
				
			|||||||
@ -34,7 +34,7 @@ export class LatestBlocksComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
    this.stateService.networkChanged$.subscribe((network) => this.network = network);
 | 
					    this.stateService.networkChanged$.subscribe((network) => this.network = network);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.blockSubscription = this.stateService.blocks$
 | 
					    this.blockSubscription = this.stateService.blocks$
 | 
				
			||||||
      .subscribe((block) => {
 | 
					      .subscribe(([block]) => {
 | 
				
			||||||
        if (block === null || !this.blocks.length) {
 | 
					        if (block === null || !this.blocks.length) {
 | 
				
			||||||
          return;
 | 
					          return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import { Component, OnInit, OnDestroy } from '@angular/core';
 | 
					import { Component, OnInit, OnDestroy } from '@angular/core';
 | 
				
			||||||
import { StateService } from 'src/app/services/state.service';
 | 
					import { StateService } from 'src/app/services/state.service';
 | 
				
			||||||
import { ActivatedRoute, ParamMap } from '@angular/router';
 | 
					import { ActivatedRoute, ParamMap } from '@angular/router';
 | 
				
			||||||
import { switchMap, map, tap } from 'rxjs/operators';
 | 
					import { switchMap, map, tap, filter } from 'rxjs/operators';
 | 
				
			||||||
import { MempoolBlock } from 'src/app/interfaces/websocket.interface';
 | 
					import { MempoolBlock } from 'src/app/interfaces/websocket.interface';
 | 
				
			||||||
import { Observable } from 'rxjs';
 | 
					import { Observable } from 'rxjs';
 | 
				
			||||||
import { SeoService } from 'src/app/services/seo.service';
 | 
					import { SeoService } from 'src/app/services/seo.service';
 | 
				
			||||||
@ -29,6 +29,7 @@ export class MempoolBlockComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
          this.mempoolBlockIndex = parseInt(params.get('id'), 10) || 0;
 | 
					          this.mempoolBlockIndex = parseInt(params.get('id'), 10) || 0;
 | 
				
			||||||
          return this.stateService.mempoolBlocks$
 | 
					          return this.stateService.mempoolBlocks$
 | 
				
			||||||
          .pipe(
 | 
					          .pipe(
 | 
				
			||||||
 | 
					            filter((mempoolBlocks) => mempoolBlocks.length > 0),
 | 
				
			||||||
            map((mempoolBlocks) => {
 | 
					            map((mempoolBlocks) => {
 | 
				
			||||||
              while (!mempoolBlocks[this.mempoolBlockIndex]) {
 | 
					              while (!mempoolBlocks[this.mempoolBlockIndex]) {
 | 
				
			||||||
                this.mempoolBlockIndex--;
 | 
					                this.mempoolBlockIndex--;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
.bitcoin-block {
 | 
					.bitcoin-block {
 | 
				
			||||||
  width: 125px;
 | 
					  width: 125px;
 | 
				
			||||||
  height: 125px;
 | 
					  height: 125px;
 | 
				
			||||||
 | 
					  transition: 2s;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.block-size {
 | 
					.block-size {
 | 
				
			||||||
 | 
				
			|||||||
@ -23,13 +23,14 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
  arrowVisible = false;
 | 
					  arrowVisible = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  rightPosition = 0;
 | 
					  rightPosition = 0;
 | 
				
			||||||
  transition = '1s';
 | 
					  transition = '2s';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  markIndex: number;
 | 
					  markIndex: number;
 | 
				
			||||||
  txFeePerVSize: number;
 | 
					  txFeePerVSize: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  resetTransitionTimeout: number;
 | 
					  resetTransitionTimeout: number;
 | 
				
			||||||
  blocksLeftToHalving: number;
 | 
					
 | 
				
			||||||
 | 
					  blockIndex = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    private router: Router,
 | 
					    private router: Router,
 | 
				
			||||||
@ -37,14 +38,11 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
  ) { }
 | 
					  ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ngOnInit() {
 | 
					  ngOnInit() {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.stateService.blocks$
 | 
					 | 
				
			||||||
      .subscribe((block) => {
 | 
					 | 
				
			||||||
        this.blocksLeftToHalving = 630000 - block.height;
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.mempoolBlocksSubscription = this.stateService.mempoolBlocks$
 | 
					    this.mempoolBlocksSubscription = this.stateService.mempoolBlocks$
 | 
				
			||||||
      .subscribe((blocks) => {
 | 
					      .subscribe((blocks) => {
 | 
				
			||||||
 | 
					        blocks.forEach((block, i) => {
 | 
				
			||||||
 | 
					          block.index = this.blockIndex + i;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
        const stringifiedBlocks = JSON.stringify(blocks);
 | 
					        const stringifiedBlocks = JSON.stringify(blocks);
 | 
				
			||||||
        this.mempoolBlocksFull = JSON.parse(stringifiedBlocks);
 | 
					        this.mempoolBlocksFull = JSON.parse(stringifiedBlocks);
 | 
				
			||||||
        this.mempoolBlocks = this.reduceMempoolBlocksToFitScreen(JSON.parse(stringifiedBlocks));
 | 
					        this.mempoolBlocks = this.reduceMempoolBlocksToFitScreen(JSON.parse(stringifiedBlocks));
 | 
				
			||||||
@ -65,6 +63,13 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
        this.calculateTransactionPosition();
 | 
					        this.calculateTransactionPosition();
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.stateService.blocks$
 | 
				
			||||||
 | 
					      .subscribe(([block]) => {
 | 
				
			||||||
 | 
					        if (block.matchRate >= 80) {
 | 
				
			||||||
 | 
					          this.blockIndex++;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.stateService.networkChanged$
 | 
					    this.stateService.networkChanged$
 | 
				
			||||||
      .subscribe((network) => this.network = network);
 | 
					      .subscribe((network) => this.network = network);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -79,7 +84,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          this.stateService.blocks$
 | 
					          this.stateService.blocks$
 | 
				
			||||||
            .pipe(take(8))
 | 
					            .pipe(take(8))
 | 
				
			||||||
            .subscribe((block) => {
 | 
					            .subscribe(([block]) => {
 | 
				
			||||||
              if (this.stateService.latestBlockHeight === block.height) {
 | 
					              if (this.stateService.latestBlockHeight === block.height) {
 | 
				
			||||||
                this.router.navigate([(this.network ? '/' + this.network : '') + '/block/', block.id], { state: { data: { block } }});
 | 
					                this.router.navigate([(this.network ? '/' + this.network : '') + '/block/', block.id], { state: { data: { block } }});
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
@ -104,8 +109,8 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
    this.mempoolBlocksSubscription.unsubscribe();
 | 
					    this.mempoolBlocksSubscription.unsubscribe();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  trackByFn(index: number) {
 | 
					  trackByFn(index: number, block: MempoolBlock) {
 | 
				
			||||||
    return index;
 | 
					    return block.index;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  reduceMempoolBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] {
 | 
					  reduceMempoolBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] {
 | 
				
			||||||
@ -176,7 +181,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
      this.transition = 'inherit';
 | 
					      this.transition = 'inherit';
 | 
				
			||||||
      this.rightPosition = this.markIndex * (this.blockWidth + this.blockPadding) + 0.5 * this.blockWidth;
 | 
					      this.rightPosition = this.markIndex * (this.blockWidth + this.blockPadding) + 0.5 * this.blockWidth;
 | 
				
			||||||
      this.arrowVisible = true;
 | 
					      this.arrowVisible = true;
 | 
				
			||||||
      this.resetTransitionTimeout = window.setTimeout(() => this.transition = '1s', 100);
 | 
					      this.resetTransitionTimeout = window.setTimeout(() => this.transition = '2s', 100);
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -113,19 +113,20 @@ export class TransactionComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.stateService.blocks$
 | 
					    this.stateService.blocks$
 | 
				
			||||||
      .subscribe((block) => this.latestBlock = block);
 | 
					      .subscribe(([block, txConfirmed]) => {
 | 
				
			||||||
 | 
					        this.latestBlock = block;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.stateService.txConfirmed$
 | 
					        if (txConfirmed) {
 | 
				
			||||||
      .subscribe((block) => {
 | 
					          this.tx.status = {
 | 
				
			||||||
        this.tx.status = {
 | 
					            confirmed: true,
 | 
				
			||||||
          confirmed: true,
 | 
					            block_height: block.height,
 | 
				
			||||||
          block_height: block.height,
 | 
					            block_hash: block.id,
 | 
				
			||||||
          block_hash: block.id,
 | 
					            block_time: block.timestamp,
 | 
				
			||||||
          block_time: block.timestamp,
 | 
					          };
 | 
				
			||||||
        };
 | 
					          this.stateService.markBlock$.next({ blockHeight: block.height });
 | 
				
			||||||
        this.stateService.markBlock$.next({ blockHeight: block.height });
 | 
					          this.audioService.playSound('magic');
 | 
				
			||||||
        this.audioService.playSound('magic');
 | 
					          this.findBlockAndSetFeeRating();
 | 
				
			||||||
        this.findBlockAndSetFeeRating();
 | 
					        }
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.stateService.txReplaced$
 | 
					    this.stateService.txReplaced$
 | 
				
			||||||
@ -171,10 +172,10 @@ export class TransactionComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
  findBlockAndSetFeeRating() {
 | 
					  findBlockAndSetFeeRating() {
 | 
				
			||||||
    this.stateService.blocks$
 | 
					    this.stateService.blocks$
 | 
				
			||||||
      .pipe(
 | 
					      .pipe(
 | 
				
			||||||
        filter((block) => block.height === this.tx.status.block_height),
 | 
					        filter(([block]) => block.height === this.tx.status.block_height),
 | 
				
			||||||
        take(1)
 | 
					        take(1)
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
      .subscribe((block) => {
 | 
					      .subscribe(([block]) => {
 | 
				
			||||||
        const feePervByte = this.tx.fee / (this.tx.weight / 4);
 | 
					        const feePervByte = this.tx.fee / (this.tx.weight / 4);
 | 
				
			||||||
        this.medianFeeNeeded = Math.round(block.feeRange[Math.round(block.feeRange.length * 0.5)]);
 | 
					        this.medianFeeNeeded = Math.round(block.feeRange[Math.round(block.feeRange.length * 0.5)]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -125,7 +125,7 @@
 | 
				
			|||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <div class="float-right">
 | 
					      <div class="float-right">
 | 
				
			||||||
        <span *ngIf="showConfirmations && latestBlock$ | async as latestBlock">
 | 
					        <span *ngIf="showConfirmations && latestBlock">
 | 
				
			||||||
          <button *ngIf="tx.status.confirmed; else unconfirmedButton" type="button" class="btn btn-sm btn-success mt-2">{{ latestBlock.height - tx.status.block_height + 1 }} confirmation<ng-container *ngIf="latestBlock.height - tx.status.block_height + 1 > 1">s</ng-container></button>
 | 
					          <button *ngIf="tx.status.confirmed; else unconfirmedButton" type="button" class="btn btn-sm btn-success mt-2">{{ latestBlock.height - tx.status.block_height + 1 }} confirmation<ng-container *ngIf="latestBlock.height - tx.status.block_height + 1 > 1">s</ng-container></button>
 | 
				
			||||||
          <ng-template #unconfirmedButton>
 | 
					          <ng-template #unconfirmedButton>
 | 
				
			||||||
            <button type="button" class="btn btn-sm btn-danger mt-2">Unconfirmed</button>
 | 
					            <button type="button" class="btn btn-sm btn-danger mt-2">Unconfirmed</button>
 | 
				
			||||||
 | 
				
			|||||||
@ -22,7 +22,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @Output() loadMore = new EventEmitter();
 | 
					  @Output() loadMore = new EventEmitter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  latestBlock$: Observable<Block>;
 | 
					  latestBlock: Block;
 | 
				
			||||||
  outspends: Outspend[] = [];
 | 
					  outspends: Outspend[] = [];
 | 
				
			||||||
  assetsMinimal: any;
 | 
					  assetsMinimal: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -34,7 +34,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
 | 
				
			|||||||
  ) { }
 | 
					  ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ngOnInit() {
 | 
					  ngOnInit() {
 | 
				
			||||||
    this.latestBlock$ = this.stateService.blocks$;
 | 
					    this.stateService.blocks$.subscribe(([block]) => this.latestBlock = block);
 | 
				
			||||||
    this.stateService.networkChanged$.subscribe((network) => this.network = network);
 | 
					    this.stateService.networkChanged$.subscribe((network) => this.network = network);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (this.network === 'liquid') {
 | 
					    if (this.network === 'liquid') {
 | 
				
			||||||
 | 
				
			|||||||
@ -89,10 +89,13 @@ export interface Block {
 | 
				
			|||||||
  merkle_root: string;
 | 
					  merkle_root: string;
 | 
				
			||||||
  previousblockhash: string;
 | 
					  previousblockhash: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Custom properties
 | 
				
			||||||
  medianFee?: number;
 | 
					  medianFee?: number;
 | 
				
			||||||
  feeRange?: number[];
 | 
					  feeRange?: number[];
 | 
				
			||||||
  reward?: number;
 | 
					  reward?: number;
 | 
				
			||||||
  coinbaseTx?: Transaction;
 | 
					  coinbaseTx?: Transaction;
 | 
				
			||||||
 | 
					  matchRate: number;
 | 
				
			||||||
 | 
					  stage: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface Address {
 | 
					export interface Address {
 | 
				
			||||||
 | 
				
			|||||||
@ -25,6 +25,7 @@ export interface MempoolBlock {
 | 
				
			|||||||
  medianFee: number;
 | 
					  medianFee: number;
 | 
				
			||||||
  totalFees: number;
 | 
					  totalFees: number;
 | 
				
			||||||
  feeRange: number[];
 | 
					  feeRange: number[];
 | 
				
			||||||
 | 
					  index: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface MemPoolState {
 | 
					export interface MemPoolState {
 | 
				
			||||||
 | 
				
			|||||||
@ -5,17 +5,20 @@ import { Injectable } from '@angular/core';
 | 
				
			|||||||
})
 | 
					})
 | 
				
			||||||
export class AudioService {
 | 
					export class AudioService {
 | 
				
			||||||
  audio = new Audio();
 | 
					  audio = new Audio();
 | 
				
			||||||
 | 
					  isPlaying = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor() { }
 | 
					  constructor() { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public playSound(name: 'magic' | 'chime' | 'cha-ching') {
 | 
					  public playSound(name: 'magic' | 'chime' | 'cha-ching' | 'bright-harmony') {
 | 
				
			||||||
    try {
 | 
					    if (this.isPlaying) {
 | 
				
			||||||
      this.audio.src = '../../../resources/sounds/' + name + '.mp3';
 | 
					      return;
 | 
				
			||||||
      this.audio.load();
 | 
					 | 
				
			||||||
      this.audio.play();
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
      console.log('Play sound failed', e);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    this.isPlaying = true;
 | 
				
			||||||
 | 
					    this.audio.src = '../../../resources/sounds/' + name + '.mp3';
 | 
				
			||||||
 | 
					    this.audio.load();
 | 
				
			||||||
 | 
					    this.audio.play().catch((e) => {
 | 
				
			||||||
 | 
					      console.log('Play sound failed', e);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    setTimeout(() => this.isPlaying = false, 100);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,7 @@ import { Block, Transaction } from '../interfaces/electrs.interface';
 | 
				
			|||||||
import { MempoolBlock, MemPoolState } from '../interfaces/websocket.interface';
 | 
					import { MempoolBlock, MemPoolState } from '../interfaces/websocket.interface';
 | 
				
			||||||
import { OptimizedMempoolStats } from '../interfaces/node-api.interface';
 | 
					import { OptimizedMempoolStats } from '../interfaces/node-api.interface';
 | 
				
			||||||
import { Router, NavigationStart } from '@angular/router';
 | 
					import { Router, NavigationStart } from '@angular/router';
 | 
				
			||||||
 | 
					import { KEEP_BLOCKS_AMOUNT } from '../app.constants';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface MarkBlockState {
 | 
					interface MarkBlockState {
 | 
				
			||||||
  blockHeight?: number;
 | 
					  blockHeight?: number;
 | 
				
			||||||
@ -19,11 +20,10 @@ export class StateService {
 | 
				
			|||||||
  latestBlockHeight = 0;
 | 
					  latestBlockHeight = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  networkChanged$ = new ReplaySubject<string>(1);
 | 
					  networkChanged$ = new ReplaySubject<string>(1);
 | 
				
			||||||
  blocks$ = new ReplaySubject<Block>(8);
 | 
					  blocks$ = new ReplaySubject<[Block, boolean]>(KEEP_BLOCKS_AMOUNT);
 | 
				
			||||||
  conversions$ = new ReplaySubject<any>(1);
 | 
					  conversions$ = new ReplaySubject<any>(1);
 | 
				
			||||||
  mempoolStats$ = new ReplaySubject<MemPoolState>(1);
 | 
					  mempoolStats$ = new ReplaySubject<MemPoolState>(1);
 | 
				
			||||||
  mempoolBlocks$ = new ReplaySubject<MempoolBlock[]>(1);
 | 
					  mempoolBlocks$ = new ReplaySubject<MempoolBlock[]>(1);
 | 
				
			||||||
  txConfirmed$ = new Subject<Block>();
 | 
					 | 
				
			||||||
  txReplaced$ = new Subject<Transaction>();
 | 
					  txReplaced$ = new Subject<Transaction>();
 | 
				
			||||||
  mempoolTransactions$ = new Subject<Transaction>();
 | 
					  mempoolTransactions$ = new Subject<Transaction>();
 | 
				
			||||||
  blockTransactions$ = new Subject<Transaction>();
 | 
					  blockTransactions$ = new Subject<Transaction>();
 | 
				
			||||||
 | 
				
			|||||||
@ -64,7 +64,7 @@ export class WebsocketService {
 | 
				
			|||||||
          blocks.forEach((block: Block) => {
 | 
					          blocks.forEach((block: Block) => {
 | 
				
			||||||
            if (block.height > this.stateService.latestBlockHeight) {
 | 
					            if (block.height > this.stateService.latestBlockHeight) {
 | 
				
			||||||
              this.stateService.latestBlockHeight = block.height;
 | 
					              this.stateService.latestBlockHeight = block.height;
 | 
				
			||||||
              this.stateService.blocks$.next(block);
 | 
					              this.stateService.blocks$.next([block, false]);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -76,12 +76,11 @@ export class WebsocketService {
 | 
				
			|||||||
        if (response.block) {
 | 
					        if (response.block) {
 | 
				
			||||||
          if (response.block.height > this.stateService.latestBlockHeight) {
 | 
					          if (response.block.height > this.stateService.latestBlockHeight) {
 | 
				
			||||||
            this.stateService.latestBlockHeight = response.block.height;
 | 
					            this.stateService.latestBlockHeight = response.block.height;
 | 
				
			||||||
            this.stateService.blocks$.next(response.block);
 | 
					            this.stateService.blocks$.next([response.block, !!response.txConfirmed]);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          if (response.txConfirmed) {
 | 
					          if (response.txConfirmed) {
 | 
				
			||||||
            this.isTrackingTx = false;
 | 
					            this.isTrackingTx = false;
 | 
				
			||||||
            this.stateService.txConfirmed$.next(response.block);
 | 
					 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								frontend/src/resources/sounds/bright-harmony.mp3
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								frontend/src/resources/sounds/bright-harmony.mp3
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user