From 36e46249b5acb109ddf213b915cf212112ecc3c0 Mon Sep 17 00:00:00 2001 From: softsimon Date: Wed, 10 Jun 2020 23:52:14 +0700 Subject: [PATCH] Transition new blocks from the mempool onto the blockchain. Chime on new blocks. fixes #47 fixes #84 --- backend/src/api/mempool.ts | 4 +- backend/src/interfaces.ts | 5 ++ frontend/src/app/app.constants.ts | 1 + .../address-labels.component.html | 2 +- .../address-labels.component.ts | 8 ++- .../app/components/block/block.component.ts | 2 +- .../blockchain-blocks.component.html | 2 +- .../blockchain-blocks.component.scss | 2 +- .../blockchain-blocks.component.ts | 56 +++++++++++++++--- .../latest-blocks/latest-blocks.component.ts | 2 +- .../mempool-block/mempool-block.component.ts | 3 +- .../mempool-blocks.component.scss | 1 + .../mempool-blocks.component.ts | 29 +++++---- .../transaction/transaction.component.ts | 29 ++++----- .../transactions-list.component.html | 2 +- .../transactions-list.component.ts | 4 +- .../src/app/interfaces/electrs.interface.ts | 3 + .../src/app/interfaces/websocket.interface.ts | 1 + frontend/src/app/services/audio.service.ts | 19 +++--- frontend/src/app/services/state.service.ts | 4 +- .../src/app/services/websocket.service.ts | 5 +- .../src/resources/sounds/bright-harmony.mp3 | Bin 0 -> 24659 bytes 22 files changed, 124 insertions(+), 60 deletions(-) create mode 100644 frontend/src/resources/sounds/bright-harmony.mp3 diff --git a/backend/src/api/mempool.ts b/backend/src/api/mempool.ts index 4eedf5bca..9ca360457 100644 --- a/backend/src/api/mempool.ts +++ b/backend/src/api/mempool.ts @@ -1,6 +1,6 @@ const config = require('../../mempool-config.json'); import bitcoinApi from './bitcoin/electrs-api'; -import { MempoolInfo, TransactionExtended, Transaction } from '../interfaces'; +import { MempoolInfo, TransactionExtended, Transaction, VbytesPerSecond } from '../interfaces'; class Mempool { private inSync: boolean = false; @@ -12,7 +12,7 @@ class Mempool { private txPerSecondArray: number[] = []; private txPerSecond: number = 0; - private vBytesPerSecondArray: any[] = []; + private vBytesPerSecondArray: VbytesPerSecond[] = []; private vBytesPerSecond: number = 0; constructor() { diff --git a/backend/src/interfaces.ts b/backend/src/interfaces.ts index a9500d42b..d525f18c6 100644 --- a/backend/src/interfaces.ts +++ b/backend/src/interfaces.ts @@ -222,3 +222,8 @@ export interface WebsocketResponse { 'track-address': string; 'watch-mempool': boolean; } + +export interface VbytesPerSecond { + unixTime: number; + vSize: number; +} diff --git a/frontend/src/app/app.constants.ts b/frontend/src/app/app.constants.ts index bfd80a03e..de257f833 100644 --- a/frontend/src/app/app.constants.ts +++ b/frontend/src/app/app.constants.ts @@ -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]; export const ELCTRS_ITEMS_PER_PAGE = 25; +export const KEEP_BLOCKS_AMOUNT = 8; diff --git a/frontend/src/app/components/address-labels/address-labels.component.html b/frontend/src/app/components/address-labels/address-labels.component.html index b7ed241f1..16deecabf 100644 --- a/frontend/src/app/components/address-labels/address-labels.component.html +++ b/frontend/src/app/components/address-labels/address-labels.component.html @@ -1,2 +1,2 @@ multisig {{ multisigM }} of {{ multisigN }} -Layer2 Peg-out +Layer{{ network === 'liquid' ? '3' : '2' }} Peg-out diff --git a/frontend/src/app/components/address-labels/address-labels.component.ts b/frontend/src/app/components/address-labels/address-labels.component.ts index e90461cae..e7a48dc07 100644 --- a/frontend/src/app/components/address-labels/address-labels.component.ts +++ b/frontend/src/app/components/address-labels/address-labels.component.ts @@ -1,5 +1,6 @@ import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core'; import { Vin, Vout } from '../../interfaces/electrs.interface'; +import { StateService } from 'src/app/services/state.service'; @Component({ selector: 'app-address-labels', @@ -8,6 +9,7 @@ import { Vin, Vout } from '../../interfaces/electrs.interface'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class AddressLabelsComponent implements OnInit { + network = ''; @Input() vin: Vin; @Input() vout: Vout; @@ -18,7 +20,11 @@ export class AddressLabelsComponent implements OnInit { secondLayerClose = false; - constructor() { } + constructor( + stateService: StateService, + ) { + this.network = stateService.network; + } ngOnInit() { if (this.vin) { diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts index 473a3e43d..60624e250 100644 --- a/frontend/src/app/components/block/block.component.ts +++ b/frontend/src/app/components/block/block.component.ts @@ -120,7 +120,7 @@ export class BlockComponent implements OnInit, OnDestroy { }); this.stateService.blocks$ - .subscribe((block) => this.latestBlock = block); + .subscribe(([block]) => this.latestBlock = block); this.stateService.networkChanged$ .subscribe((network) => this.network = network); diff --git a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html index f7b0d223f..8da403914 100644 --- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html +++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html @@ -1,6 +1,6 @@
-
+
 
{{ block.height }} diff --git a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.scss b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.scss index 9a72ffebb..a36eeceb7 100644 --- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.scss +++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.scss @@ -14,7 +14,7 @@ .mined-block { position: absolute; top: 0px; - transition: 1s; + transition: 2s; } .block-size { diff --git a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts index 90e0d274d..730306003 100644 --- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts +++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts @@ -1,8 +1,10 @@ -import { Component, OnInit, OnDestroy, HostListener } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; import { Subscription } from 'rxjs'; import { Block } from 'src/app/interfaces/electrs.interface'; import { StateService } from 'src/app/services/state.service'; import { Router } from '@angular/router'; +import { AudioService } from 'src/app/services/audio.service'; +import { KEEP_BLOCKS_AMOUNT } from 'src/app/app.constants'; @Component({ selector: 'app-blockchain-blocks', @@ -14,6 +16,7 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy { blocks: Block[] = []; markHeight: number; blocksSubscription: Subscription; + blockStyles = []; interval: any; arrowVisible = false; @@ -30,20 +33,40 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy { constructor( private stateService: StateService, private router: Router, + private audioService: AudioService, ) { } ngOnInit() { this.stateService.networkChanged$.subscribe((network) => this.network = network); this.blocksSubscription = this.stateService.blocks$ - .subscribe((block) => { + .subscribe(([block, txConfirmed]) => { + const currentBlocksAmount = this.blocks.length; if (this.blocks.some((b) => b.height === block.height)) { return; } this.blocks.unshift(block); 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$ @@ -83,20 +106,28 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy { clearInterval(this.interval); } - moveArrowToPosition(animate: boolean) { + moveArrowToPosition(animate: boolean, newBlockFromLeft = false) { if (!this.markHeight) { this.arrowVisible = false; return; } const blockindex = this.blocks.findIndex((b) => b.height === this.markHeight); - if (blockindex !== -1) { + if (blockindex > -1) { if (!animate) { this.transition = 'inherit'; } this.arrowVisible = true; - this.arrowLeftPx = blockindex * 155 + 30; - if (!animate) { - setTimeout(() => this.transition = '1s'); + if (newBlockFromLeft) { + this.arrowLeftPx = blockindex * 155 + 30 - 205; + 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) { const greenBackgroundHeight = 100 - (block.weight / 4000000) * 100; + let addLeft = 0; + + if (block.stage === 1) { + block.stage = 2; + addLeft = -205; + } + return { - left: 155 * this.blocks.indexOf(block) + 'px', + left: addLeft + 155 * this.blocks.indexOf(block) + 'px', background: `repeating-linear-gradient( #2d3348, #2d3348 ${greenBackgroundHeight}%, diff --git a/frontend/src/app/components/latest-blocks/latest-blocks.component.ts b/frontend/src/app/components/latest-blocks/latest-blocks.component.ts index 893cce888..1c85e13f3 100644 --- a/frontend/src/app/components/latest-blocks/latest-blocks.component.ts +++ b/frontend/src/app/components/latest-blocks/latest-blocks.component.ts @@ -34,7 +34,7 @@ export class LatestBlocksComponent implements OnInit, OnDestroy { this.stateService.networkChanged$.subscribe((network) => this.network = network); this.blockSubscription = this.stateService.blocks$ - .subscribe((block) => { + .subscribe(([block]) => { if (block === null || !this.blocks.length) { return; } diff --git a/frontend/src/app/components/mempool-block/mempool-block.component.ts b/frontend/src/app/components/mempool-block/mempool-block.component.ts index b3639716a..897e091b3 100644 --- a/frontend/src/app/components/mempool-block/mempool-block.component.ts +++ b/frontend/src/app/components/mempool-block/mempool-block.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit, OnDestroy } from '@angular/core'; import { StateService } from 'src/app/services/state.service'; 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 { Observable } from 'rxjs'; 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; return this.stateService.mempoolBlocks$ .pipe( + filter((mempoolBlocks) => mempoolBlocks.length > 0), map((mempoolBlocks) => { while (!mempoolBlocks[this.mempoolBlockIndex]) { this.mempoolBlockIndex--; diff --git a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.scss b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.scss index 4fea06bc1..27fb4ca45 100644 --- a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.scss +++ b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.scss @@ -1,6 +1,7 @@ .bitcoin-block { width: 125px; height: 125px; + transition: 2s; } .block-size { diff --git a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts index 2ab086746..ca611dfe9 100644 --- a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts +++ b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts @@ -23,13 +23,14 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy { arrowVisible = false; rightPosition = 0; - transition = '1s'; + transition = '2s'; markIndex: number; txFeePerVSize: number; resetTransitionTimeout: number; - blocksLeftToHalving: number; + + blockIndex = 1; constructor( private router: Router, @@ -37,14 +38,11 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy { ) { } ngOnInit() { - - this.stateService.blocks$ - .subscribe((block) => { - this.blocksLeftToHalving = 630000 - block.height; - }); - this.mempoolBlocksSubscription = this.stateService.mempoolBlocks$ .subscribe((blocks) => { + blocks.forEach((block, i) => { + block.index = this.blockIndex + i; + }); const stringifiedBlocks = JSON.stringify(blocks); this.mempoolBlocksFull = JSON.parse(stringifiedBlocks); this.mempoolBlocks = this.reduceMempoolBlocksToFitScreen(JSON.parse(stringifiedBlocks)); @@ -65,6 +63,13 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy { this.calculateTransactionPosition(); }); + this.stateService.blocks$ + .subscribe(([block]) => { + if (block.matchRate >= 80) { + this.blockIndex++; + } + }); + this.stateService.networkChanged$ .subscribe((network) => this.network = network); @@ -79,7 +84,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy { } else { this.stateService.blocks$ .pipe(take(8)) - .subscribe((block) => { + .subscribe(([block]) => { if (this.stateService.latestBlockHeight === block.height) { 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(); } - trackByFn(index: number) { - return index; + trackByFn(index: number, block: MempoolBlock) { + return block.index; } reduceMempoolBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] { @@ -176,7 +181,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy { this.transition = 'inherit'; this.rightPosition = this.markIndex * (this.blockWidth + this.blockPadding) + 0.5 * this.blockWidth; this.arrowVisible = true; - this.resetTransitionTimeout = window.setTimeout(() => this.transition = '1s', 100); + this.resetTransitionTimeout = window.setTimeout(() => this.transition = '2s', 100); return; } diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index 4568e4cee..be349330e 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -113,19 +113,20 @@ export class TransactionComponent implements OnInit, OnDestroy { }); this.stateService.blocks$ - .subscribe((block) => this.latestBlock = block); + .subscribe(([block, txConfirmed]) => { + this.latestBlock = block; - this.stateService.txConfirmed$ - .subscribe((block) => { - this.tx.status = { - confirmed: true, - block_height: block.height, - block_hash: block.id, - block_time: block.timestamp, - }; - this.stateService.markBlock$.next({ blockHeight: block.height }); - this.audioService.playSound('magic'); - this.findBlockAndSetFeeRating(); + if (txConfirmed) { + this.tx.status = { + confirmed: true, + block_height: block.height, + block_hash: block.id, + block_time: block.timestamp, + }; + this.stateService.markBlock$.next({ blockHeight: block.height }); + this.audioService.playSound('magic'); + this.findBlockAndSetFeeRating(); + } }); this.stateService.txReplaced$ @@ -171,10 +172,10 @@ export class TransactionComponent implements OnInit, OnDestroy { findBlockAndSetFeeRating() { this.stateService.blocks$ .pipe( - filter((block) => block.height === this.tx.status.block_height), + filter(([block]) => block.height === this.tx.status.block_height), take(1) ) - .subscribe((block) => { + .subscribe(([block]) => { const feePervByte = this.tx.fee / (this.tx.weight / 4); this.medianFeeNeeded = Math.round(block.feeRange[Math.round(block.feeRange.length * 0.5)]); diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.html b/frontend/src/app/components/transactions-list/transactions-list.component.html index 6cb4e29dd..115de55c9 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.html +++ b/frontend/src/app/components/transactions-list/transactions-list.component.html @@ -125,7 +125,7 @@
- + diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.ts b/frontend/src/app/components/transactions-list/transactions-list.component.ts index b861920fe..2fc0fbe67 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.ts +++ b/frontend/src/app/components/transactions-list/transactions-list.component.ts @@ -22,7 +22,7 @@ export class TransactionsListComponent implements OnInit, OnChanges { @Output() loadMore = new EventEmitter(); - latestBlock$: Observable; + latestBlock: Block; outspends: Outspend[] = []; assetsMinimal: any; @@ -34,7 +34,7 @@ export class TransactionsListComponent implements OnInit, OnChanges { ) { } ngOnInit() { - this.latestBlock$ = this.stateService.blocks$; + this.stateService.blocks$.subscribe(([block]) => this.latestBlock = block); this.stateService.networkChanged$.subscribe((network) => this.network = network); if (this.network === 'liquid') { diff --git a/frontend/src/app/interfaces/electrs.interface.ts b/frontend/src/app/interfaces/electrs.interface.ts index f939ce091..20b64a2a5 100644 --- a/frontend/src/app/interfaces/electrs.interface.ts +++ b/frontend/src/app/interfaces/electrs.interface.ts @@ -89,10 +89,13 @@ export interface Block { merkle_root: string; previousblockhash: string; + // Custom properties medianFee?: number; feeRange?: number[]; reward?: number; coinbaseTx?: Transaction; + matchRate: number; + stage: number; } export interface Address { diff --git a/frontend/src/app/interfaces/websocket.interface.ts b/frontend/src/app/interfaces/websocket.interface.ts index 6c59a4b6b..abab7acf0 100644 --- a/frontend/src/app/interfaces/websocket.interface.ts +++ b/frontend/src/app/interfaces/websocket.interface.ts @@ -25,6 +25,7 @@ export interface MempoolBlock { medianFee: number; totalFees: number; feeRange: number[]; + index: number; } export interface MemPoolState { diff --git a/frontend/src/app/services/audio.service.ts b/frontend/src/app/services/audio.service.ts index 29c14819c..4b3060b24 100644 --- a/frontend/src/app/services/audio.service.ts +++ b/frontend/src/app/services/audio.service.ts @@ -5,17 +5,20 @@ import { Injectable } from '@angular/core'; }) export class AudioService { audio = new Audio(); + isPlaying = false; constructor() { } - public playSound(name: 'magic' | 'chime' | 'cha-ching') { - try { - this.audio.src = '../../../resources/sounds/' + name + '.mp3'; - this.audio.load(); - this.audio.play(); - } catch (e) { - console.log('Play sound failed', e); + public playSound(name: 'magic' | 'chime' | 'cha-ching' | 'bright-harmony') { + if (this.isPlaying) { + return; } + 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); } - } diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index 4cd6a72ea..46bb37f18 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -4,6 +4,7 @@ import { Block, Transaction } from '../interfaces/electrs.interface'; import { MempoolBlock, MemPoolState } from '../interfaces/websocket.interface'; import { OptimizedMempoolStats } from '../interfaces/node-api.interface'; import { Router, NavigationStart } from '@angular/router'; +import { KEEP_BLOCKS_AMOUNT } from '../app.constants'; interface MarkBlockState { blockHeight?: number; @@ -19,11 +20,10 @@ export class StateService { latestBlockHeight = 0; networkChanged$ = new ReplaySubject(1); - blocks$ = new ReplaySubject(8); + blocks$ = new ReplaySubject<[Block, boolean]>(KEEP_BLOCKS_AMOUNT); conversions$ = new ReplaySubject(1); mempoolStats$ = new ReplaySubject(1); mempoolBlocks$ = new ReplaySubject(1); - txConfirmed$ = new Subject(); txReplaced$ = new Subject(); mempoolTransactions$ = new Subject(); blockTransactions$ = new Subject(); diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts index 65846df7f..00d89ba44 100644 --- a/frontend/src/app/services/websocket.service.ts +++ b/frontend/src/app/services/websocket.service.ts @@ -64,7 +64,7 @@ export class WebsocketService { blocks.forEach((block: Block) => { if (block.height > this.stateService.latestBlockHeight) { 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.height > this.stateService.latestBlockHeight) { this.stateService.latestBlockHeight = response.block.height; - this.stateService.blocks$.next(response.block); + this.stateService.blocks$.next([response.block, !!response.txConfirmed]); } if (response.txConfirmed) { this.isTrackingTx = false; - this.stateService.txConfirmed$.next(response.block); } } diff --git a/frontend/src/resources/sounds/bright-harmony.mp3 b/frontend/src/resources/sounds/bright-harmony.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..5a0435e0af23452529f2c58a00e8983a85254c62 GIT binary patch literal 24659 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

8&e&wNkrs`6sW7$2!l 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 BpAi56 literal 0 HcmV?d00001