From 72658c19f6422c043fb3648612a47cd86a93e346 Mon Sep 17 00:00:00 2001 From: softsimon Date: Tue, 17 Mar 2020 21:53:20 +0700 Subject: [PATCH] Adding "mempool block" details. Work in progress! --- backend/src/api/mempool-blocks.ts | 1 + backend/src/interfaces.ts | 1 + frontend/src/app/app-routing.module.ts | 6 ++ frontend/src/app/app.module.ts | 4 ++ .../blockchain/blockchain.component.html | 2 +- .../blockchain/blockchain.component.ts | 1 + .../fee-distribution-graph.component.html | 13 ++++ .../fee-distribution-graph.component.scss | 0 .../fee-distribution-graph.component.ts | 66 +++++++++++++++++++ .../latest-blocks.component.scss | 12 ++-- .../mempool-block.component.html | 54 +++++++++++++++ .../mempool-block.component.scss | 25 +++++++ .../mempool-block.component.spec.ts | 25 +++++++ .../mempool-block/mempool-block.component.ts | 40 +++++++++++ .../mempool-blocks.component.html | 1 + .../mempool-blocks.component.scss | 8 +++ .../mempool-blocks.component.ts | 9 ++- .../src/app/interfaces/websocket.interface.ts | 1 + 18 files changed, 260 insertions(+), 9 deletions(-) create mode 100644 frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.html create mode 100644 frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.scss create mode 100644 frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.ts create mode 100644 frontend/src/app/components/mempool-block/mempool-block.component.html create mode 100644 frontend/src/app/components/mempool-block/mempool-block.component.scss create mode 100644 frontend/src/app/components/mempool-block/mempool-block.component.spec.ts create mode 100644 frontend/src/app/components/mempool-block/mempool-block.component.ts diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index 215671b0a..ec6fa3cb2 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -62,6 +62,7 @@ class MempoolBlocks { blockSize: blockSize, blockVSize: blockVSize, nTx: transactions.length, + totalFees: transactions.reduce((acc, cur) => acc + cur.fee, 0), medianFee: this.median(transactions.map((tx) => tx.feePerVsize)), feeRange: this.getFeesInRange(transactions, rangeLength), }; diff --git a/backend/src/interfaces.ts b/backend/src/interfaces.ts index 43d418846..7396efa6a 100644 --- a/backend/src/interfaces.ts +++ b/backend/src/interfaces.ts @@ -12,6 +12,7 @@ export interface MempoolBlock { blockVSize: number; nTx: number; medianFee: number; + totalFees: number; feeRange: number[]; } diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 79c9f27a8..5f0b1da37 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -8,6 +8,7 @@ import { MasterPageComponent } from './components/master-page/master-page.compon import { AboutComponent } from './components/about/about.component'; import { TelevisionComponent } from './components/television/television.component'; import { StatisticsComponent } from './components/statistics/statistics.component'; +import { MempoolBlockComponent } from './components/mempool-block/mempool-block.component'; const routes: Routes = [ { @@ -36,6 +37,11 @@ const routes: Routes = [ children: [], component: BlockComponent }, + { + path: 'mempool-block/:id', + children: [], + component: MempoolBlockComponent + }, { path: 'address/:id', children: [], diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index f8a833a3c..60b54361b 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -40,6 +40,8 @@ import { BlockchainComponent } from './components/blockchain/blockchain.componen import { FooterComponent } from './components/footer/footer.component'; import { AudioService } from './services/audio.service'; import { FiatComponent } from './fiat/fiat.component'; +import { MempoolBlockComponent } from './components/mempool-block/mempool-block.component'; +import { FeeDistributionGraphComponent } from './components/fee-distribution-graph/fee-distribution-graph.component'; @NgModule({ declarations: [ @@ -71,6 +73,8 @@ import { FiatComponent } from './fiat/fiat.component'; ChartistComponent, FooterComponent, FiatComponent, + MempoolBlockComponent, + FeeDistributionGraphComponent, ], imports: [ BrowserModule, diff --git a/frontend/src/app/components/blockchain/blockchain.component.html b/frontend/src/app/components/blockchain/blockchain.component.html index 78822a560..239013883 100644 --- a/frontend/src/app/components/blockchain/blockchain.component.html +++ b/frontend/src/app/components/blockchain/blockchain.component.html @@ -1,6 +1,6 @@
- +
diff --git a/frontend/src/app/components/blockchain/blockchain.component.ts b/frontend/src/app/components/blockchain/blockchain.component.ts index 93066801f..939045ecf 100644 --- a/frontend/src/app/components/blockchain/blockchain.component.ts +++ b/frontend/src/app/components/blockchain/blockchain.component.ts @@ -12,6 +12,7 @@ export class BlockchainComponent implements OnInit, OnDestroy { @Input() position: 'middle' | 'top' = 'middle'; @Input() markHeight: number; @Input() txFeePerVSize: number; + @Input() markMempoolBlockIndex = -1; txTrackingSubscription: Subscription; blocksSubscription: Subscription; diff --git a/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.html b/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.html new file mode 100644 index 000000000..5ae87fc39 --- /dev/null +++ b/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.html @@ -0,0 +1,13 @@ +
+ + +
+ + +
+
+
+
diff --git a/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.scss b/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.ts b/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.ts new file mode 100644 index 000000000..bc39b523a --- /dev/null +++ b/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.ts @@ -0,0 +1,66 @@ +import { Component, OnInit, Input, OnChanges } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import * as Chartist from 'chartist'; +import { VbytesPipe } from 'src/app/pipes/bytes-pipe/vbytes.pipe'; + +@Component({ + selector: 'app-fee-distribution-graph', + templateUrl: './fee-distribution-graph.component.html', + styleUrls: ['./fee-distribution-graph.component.scss'] +}) +export class FeeDistributionGraphComponent implements OnChanges { + @Input() feeRange; + + mempoolVsizeFeesData: any; + mempoolVsizeFeesOptions: any; + + mempoolVsizeFeesPieData: any; + mempoolVsizeFeesPieOptions: any; + + feeLevels = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200, + 250, 300, 350, 400, 500]; + + radioGroupForm: FormGroup; + + constructor( + private vbytesPipe: VbytesPipe, + ) { } + + ngOnChanges() { + this.mempoolVsizeFeesOptions = { + showArea: true, + showLine: true, + fullWidth: true, + showPoint: false, + low: 0, + axisY: { + labelInterpolationFnc: (value: number): any => { + return this.vbytesPipe.transform(value, 2); + }, + offset: 60 + }, + }; + + const fees = this.feeRange; + const series = []; + + for (let i = 0; i < this.feeLevels.length; i++) { + let total = 0; + for (let j = 0; j < fees.length; j++) { + if (i === this.feeLevels.length - 1) { + if (fees[j] >= this.feeLevels[i]) { + total += 1; + } + } else if (fees[j] >= this.feeLevels[i] && fees[j] < this.feeLevels[i + 1]) { + total += 1; + } + } + series.push(total); + } + + this.mempoolVsizeFeesData = { + series: [fees] + }; + } + +} diff --git a/frontend/src/app/components/latest-blocks/latest-blocks.component.scss b/frontend/src/app/components/latest-blocks/latest-blocks.component.scss index 240650330..24dcd20d1 100644 --- a/frontend/src/app/components/latest-blocks/latest-blocks.component.scss +++ b/frontend/src/app/components/latest-blocks/latest-blocks.component.scss @@ -6,6 +6,12 @@ background-color: #2d3348; } +.progress-text { + position: absolute; + width: 100%; + text-align: center; +} + @media (min-width: 768px) { .d-md-block { display: table-cell !important; @@ -16,9 +22,3 @@ display: table-cell !important; } } - -.progress-text { - position: absolute; - width: 100%; - text-align: center; -} \ No newline at end of file diff --git a/frontend/src/app/components/mempool-block/mempool-block.component.html b/frontend/src/app/components/mempool-block/mempool-block.component.html new file mode 100644 index 000000000..4840065d0 --- /dev/null +++ b/frontend/src/app/components/mempool-block/mempool-block.component.html @@ -0,0 +1,54 @@ +
+ +
+ +
+ +
+

Mempool block

+
+ +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Median fee~{{ mempoolBlock.medianFee | ceil }} sats/vB ()
Fee span{{ mempoolBlock.feeRange[0] | ceil }} - {{ mempoolBlock.feeRange[mempoolBlock.feeRange.length - 1] | ceil }} sat/vB
Total fees{{ mempoolBlock.totalFees / 100000000 | number : '1.2-2' }} BTC ()
Transactions{{ mempoolBlock.nTx }}
Filled +
+
+
{{ mempoolBlock.blockSize | bytes: 2 }}
+
+
+
+
+ +
+
+
+ +
+ +
\ No newline at end of file diff --git a/frontend/src/app/components/mempool-block/mempool-block.component.scss b/frontend/src/app/components/mempool-block/mempool-block.component.scss new file mode 100644 index 000000000..4893d9ccc --- /dev/null +++ b/frontend/src/app/components/mempool-block/mempool-block.component.scss @@ -0,0 +1,25 @@ +.progress-mempool { + background: repeating-linear-gradient(to right, #2d3348, #2d3348 0%, #105fb0 0%, #9339f4 100%); +} + +.progress { + background-color: #2d3348; +} + +.progress-text { + position: absolute; + width: 100%; + text-align: center; +} + +.title-block { + color: #FFF; + padding-left: 10px; + padding-top: 20px; + padding-bottom: 3px; + border-top: 5px solid #FFF; +} + +.title-block > h1 { + margin: 0; +} \ No newline at end of file diff --git a/frontend/src/app/components/mempool-block/mempool-block.component.spec.ts b/frontend/src/app/components/mempool-block/mempool-block.component.spec.ts new file mode 100644 index 000000000..d3d98a433 --- /dev/null +++ b/frontend/src/app/components/mempool-block/mempool-block.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MempoolBlockComponent } from './mempool-block.component'; + +describe('MempoolBlockComponent', () => { + let component: MempoolBlockComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MempoolBlockComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MempoolBlockComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/components/mempool-block/mempool-block.component.ts b/frontend/src/app/components/mempool-block/mempool-block.component.ts new file mode 100644 index 000000000..869ba0db0 --- /dev/null +++ b/frontend/src/app/components/mempool-block/mempool-block.component.ts @@ -0,0 +1,40 @@ +import { Component, OnInit } from '@angular/core'; +import { StateService } from 'src/app/services/state.service'; +import { ActivatedRoute, ParamMap } from '@angular/router'; +import { switchMap, map } from 'rxjs/operators'; +import { MempoolBlock } from 'src/app/interfaces/websocket.interface'; +import { WebsocketService } from 'src/app/services/websocket.service'; + +@Component({ + selector: 'app-mempool-block', + templateUrl: './mempool-block.component.html', + styleUrls: ['./mempool-block.component.scss'] +}) +export class MempoolBlockComponent implements OnInit { + mempoolBlockIndex: number; + mempoolBlock: MempoolBlock; + + constructor( + private route: ActivatedRoute, + private stateService: StateService, + private websocketService: WebsocketService, + ) { } + + ngOnInit(): void { + this.websocketService.want(['blocks', 'stats', 'mempool-blocks']); + + this.route.paramMap.pipe( + switchMap((params: ParamMap) => { + this.mempoolBlockIndex = parseInt(params.get('id'), 10) || 0; + return this.stateService.mempoolBlocks$ + .pipe( + map((mempoolBlocks) => mempoolBlocks[this.mempoolBlockIndex]) + ); + }) + ) + .subscribe((mempoolBlock) => { + this.mempoolBlock = mempoolBlock; + }); + } + +} diff --git a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.html b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.html index da3661576..06d908ddd 100644 --- a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.html +++ b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.html @@ -2,6 +2,7 @@
+  
~{{ projectedBlock.medianFee | ceil }} sats/vB 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 159d7af08..87c5ca59e 100644 --- a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.scss +++ b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.scss @@ -104,3 +104,11 @@ border-right: 35px solid transparent; border-bottom: 35px solid #FFF; } + +.blockLink { + width: 100%; + height: 100%; + position: absolute; + left: 0; + z-index: 10; +} 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 f22e919a1..d39d91f26 100644 --- a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts +++ b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts @@ -20,6 +20,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { rightPosition = 0; @Input() txFeePerVSize: number; + @Input() markIndex: number; constructor( private stateService: StateService, @@ -42,7 +43,6 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { } } - ngOnChanges() { this.calculateTransactionPosition(); } @@ -91,13 +91,18 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { } calculateTransactionPosition() { - if (!this.txFeePerVSize || !this.mempoolBlocks) { + if ((!this.txFeePerVSize && this.markIndex === -1) || !this.mempoolBlocks) { this.arrowVisible = false; return; } this.arrowVisible = true; + if (this.markIndex > -1) { + this.rightPosition = this.markIndex * (this.blockWidth + this.blockPadding) + 0.5 * this.blockWidth; + return; + } + for (const block of this.mempoolBlocks) { for (let i = 0; i < block.feeRange.length - 1; i++) { if (this.txFeePerVSize < block.feeRange[i + 1] && this.txFeePerVSize >= block.feeRange[i]) { diff --git a/frontend/src/app/interfaces/websocket.interface.ts b/frontend/src/app/interfaces/websocket.interface.ts index 852ba6bf9..fd63d016d 100644 --- a/frontend/src/app/interfaces/websocket.interface.ts +++ b/frontend/src/app/interfaces/websocket.interface.ts @@ -19,6 +19,7 @@ export interface MempoolBlock { blockVSize: number; nTx: number; medianFee: number; + totalFees: number; feeRange: number[]; }