Merge pull request #3962 from mempool/mononaut/latest-replacements

Switch "Latest blocks" to "Latest replacements"
This commit is contained in:
softsimon
2023-07-14 18:30:25 +09:00
committed by GitHub
8 changed files with 159 additions and 75 deletions

View File

@@ -75,36 +75,31 @@
<div class="col" style="max-height: 410px">
<div class="card">
<div class="card-body">
<a class="title-link" href="" [routerLink]="['/blocks' | relativeUrl]">
<h5 class="card-title d-inline" i18n="dashboard.latest-blocks">Latest blocks</h5>
<a class="title-link" href="" [routerLink]="['/rbf' | relativeUrl]">
<h5 class="card-title d-inline" i18n="dashboard.latest-rbf-replacements">Latest replacements</h5>
<span>&nbsp;</span>
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: '#4a68b9'"></fa-icon>
</a>
<table class="table lastest-blocks-table">
<table class="table lastest-replacements-table">
<thead>
<th class="table-cell-height" i18n="dashboard.latest-blocks.height">Height</th>
<th *ngIf="!stateService.env.MINING_DASHBOARD" class="table-cell-mined" i18n="dashboard.latest-blocks.mined">Mined</th>
<th *ngIf="stateService.env.MINING_DASHBOARD" class="table-cell-mined pl-lg-4" i18n="mining.pool-name">Pool</th>
<th class="table-cell-transaction-count" i18n="dashboard.latest-blocks.transaction-count">TXs</th>
<th class="table-cell-size" i18n="dashboard.latest-blocks.size">Size</th>
<th class="table-cell-txid" i18n="dashboard.latest-transactions.txid">TXID</th>
<th class="table-cell-old-fee" i18n="dashboard.previous-transaction-fee">Previous fee</th>
<th class="table-cell-new-fee" i18n="dashboard.new-transaction-fee">New fee</th>
<th class="table-cell-badges" i18n="transaction.status|Transaction Status">Status</th>
</thead>
<tbody>
<tr *ngFor="let block of blocks$ | async; let i = index; trackBy: trackByBlock">
<td class="table-cell-height" ><a [routerLink]="['/block' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a></td>
<td *ngIf="!stateService.env.MINING_DASHBOARD" class="table-cell-mined" ><app-time kind="since" [time]="block.timestamp" [fastRender]="true"></app-time></td>
<td *ngIf="stateService.env.MINING_DASHBOARD" class="table-cell-mined pl-lg-4">
<a class="clear-link" [routerLink]="[('/mining/pool/' + block.extras.pool.slug) | relativeUrl]">
<img width="22" height="22" src="{{ block.extras.pool['logo'] }}"
onError="this.src = '/resources/mining-pools/default.svg'">
<span class="pool-name">{{ block.extras.pool.name }}</span>
<tr *ngFor="let replacement of replacements$ | async;">
<td class="table-cell-txid">
<a [routerLink]="['/tx' | relativeUrl, replacement.txid]">
<app-truncate [text]="replacement.txid" [lastChars]="5"></app-truncate>
</a>
</td>
<td class="table-cell-transaction-count">{{ block.tx_count | number }}</td>
<td class="table-cell-size">
<div class="progress">
<div class="progress-bar progress-mempool {{ network$ | async }}" role="progressbar" [ngStyle]="{'width': (block.weight / stateService.env.BLOCK_WEIGHT_UNITS)*100 + '%' }">&nbsp;</div>
<div class="progress-text" [innerHTML]="block.size | bytes: 2"></div>
</div>
<td class="table-cell-old-fee"><app-fee-rate [fee]="replacement.oldFee" [weight]="replacement.oldVsize * 4"></app-fee-rate></td>
<td class="table-cell-new-fee"><app-fee-rate [fee]="replacement.newFee" [weight]="replacement.newVsize * 4"></app-fee-rate></td>
<td class="table-cell-badges">
<span *ngIf="replacement.mined" class="badge badge-success" i18n="transaction.rbf.mined">Mined</span>
<span *ngIf="replacement.fullRbf" class="badge badge-info" i18n="transaction.full-rbf">Full RBF</span>
<span *ngIf="!replacement.fullRbf" class="badge badge-success" i18n="transaction.rbf">RBF</span>
</td>
</tr>
</tbody>

View File

@@ -175,39 +175,43 @@
height: 18px;
}
.lastest-blocks-table {
.lastest-replacements-table {
width: 100%;
text-align: left;
table-layout:fixed;
tr, td, th {
border: 0px;
padding-top: 0.65rem !important;
padding-bottom: 0.7rem !important;
padding-top: 0.71rem !important;
padding-bottom: 0.75rem !important;
}
.table-cell-height {
width: 15%;
td {
overflow:hidden;
width: 25%;
}
.table-cell-mined {
width: 35%;
text-align: left;
.table-cell-txid {
width: 25%;
text-align: start;
}
.table-cell-transaction-count {
display: none;
text-align: right;
width: 20%;
display: table-cell;
}
.table-cell-size {
display: none;
text-align: center;
width: 30%;
@media (min-width: 485px) {
display: table-cell;
}
@media (min-width: 768px) {
.table-cell-old-fee {
width: 25%;
text-align: end;
@media(max-width: 1080px) {
display: none;
}
@media (min-width: 992px) {
display: table-cell;
}
.table-cell-new-fee {
width: 20%;
text-align: end;
}
.table-cell-badges {
width: 23%;
padding-right: 0;
padding-left: 5px;
text-align: end;
.badge {
margin-left: 5px;
}
}
}

View File

@@ -1,8 +1,8 @@
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
import { combineLatest, merge, Observable, of, Subscription } from 'rxjs';
import { filter, map, scan, share, switchMap, tap } from 'rxjs/operators';
import { BlockExtended, OptimizedMempoolStats } from '../interfaces/node-api.interface';
import { MempoolInfo, TransactionStripped } from '../interfaces/websocket.interface';
import { filter, map, scan, share, switchMap } from 'rxjs/operators';
import { BlockExtended, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface';
import { MempoolInfo, TransactionStripped, ReplacementInfo } from '../interfaces/websocket.interface';
import { ApiService } from '../services/api.service';
import { StateService } from '../services/state.service';
import { WebsocketService } from '../services/websocket.service';
@@ -38,8 +38,8 @@ export class DashboardComponent implements OnInit, OnDestroy {
mempoolInfoData$: Observable<MempoolInfoData>;
mempoolLoadingStatus$: Observable<number>;
vBytesPerSecondLimit = 1667;
blocks$: Observable<BlockExtended[]>;
transactions$: Observable<TransactionStripped[]>;
replacements$: Observable<ReplacementInfo[]>;
latestBlockHeight: number;
mempoolTransactionsWeightPerSecondData: any;
mempoolStats$: Observable<MempoolStatsData>;
@@ -58,12 +58,14 @@ export class DashboardComponent implements OnInit, OnDestroy {
ngOnDestroy(): void {
this.currencySubscription.unsubscribe();
this.websocketService.stopTrackRbfSummary();
}
ngOnInit(): void {
this.isLoadingWebSocket$ = this.stateService.isLoadingWebSocket$;
this.seoService.resetTitle();
this.websocketService.want(['blocks', 'stats', 'mempool-blocks', 'live-2h-chart']);
this.websocketService.startTrackRbfSummary();
this.network$ = merge(of(''), this.stateService.networkChanged$);
this.mempoolLoadingStatus$ = this.stateService.loadingIndicators$
.pipe(
@@ -130,23 +132,6 @@ export class DashboardComponent implements OnInit, OnDestroy {
}),
);
this.blocks$ = this.stateService.blocks$
.pipe(
tap((blocks) => {
this.latestBlockHeight = blocks[0].height;
}),
switchMap((blocks) => {
if (this.stateService.env.MINING_DASHBOARD === true) {
for (const block of blocks) {
// @ts-ignore: Need to add an extra field for the template
block.extras.pool.logo = `/resources/mining-pools/` +
block.extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg';
}
}
return of(blocks.slice(0, 6));
})
);
this.transactions$ = this.stateService.transactions$
.pipe(
scan((acc, tx) => {
@@ -159,6 +144,8 @@ export class DashboardComponent implements OnInit, OnDestroy {
}, []),
);
this.replacements$ = this.stateService.rbfLatestSummary$;
this.mempoolStats$ = this.stateService.connectionState$
.pipe(
filter((state) => state === 2),
@@ -219,4 +206,16 @@ export class DashboardComponent implements OnInit, OnDestroy {
trackByBlock(index: number, block: BlockExtended) {
return block.height;
}
checkFullRbf(tree: RbfTree): void {
let fullRbf = false;
for (const replaced of tree.replaces) {
if (!replaced.tx.rbf) {
fullRbf = true;
}
replaced.replacedBy = tree.tx;
this.checkFullRbf(replaced);
}
tree.tx.fullRbf = fullRbf;
}
}

View File

@@ -18,6 +18,7 @@ export interface WebsocketResponse {
txReplaced?: ReplacedTransaction;
rbfInfo?: RbfTree;
rbfLatest?: RbfTree[];
rbfLatestSummary?: ReplacementInfo[];
utxoSpent?: object;
transactions?: TransactionStripped[];
loadingIndicators?: ILoadingIndicators;
@@ -29,6 +30,7 @@ export interface WebsocketResponse {
'track-asset'?: string;
'track-mempool-block'?: number;
'track-rbf'?: string;
'track-rbf-summary'?: boolean;
'watch-mempool'?: boolean;
'track-bisq-market'?: string;
'refresh-blocks'?: boolean;
@@ -37,6 +39,16 @@ export interface WebsocketResponse {
export interface ReplacedTransaction extends Transaction {
txid: string;
}
export interface ReplacementInfo {
mined: boolean;
fullRbf: boolean;
txid: string;
oldFee: number;
oldVsize: number;
newFee: number;
newVsize: number;
}
export interface MempoolBlock {
blink?: boolean;
height?: number;

View File

@@ -1,7 +1,7 @@
import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core';
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable, merge } from 'rxjs';
import { Transaction } from '../interfaces/electrs.interface';
import { IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, TransactionStripped } from '../interfaces/websocket.interface';
import { IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, TransactionStripped } from '../interfaces/websocket.interface';
import { BlockExtended, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface';
import { Router, NavigationStart } from '@angular/router';
import { isPlatformBrowser } from '@angular/common';
@@ -108,6 +108,7 @@ export class StateService {
txReplaced$ = new Subject<ReplacedTransaction>();
txRbfInfo$ = new Subject<RbfTree>();
rbfLatest$ = new Subject<RbfTree[]>();
rbfLatestSummary$ = new Subject<ReplacementInfo[]>();
utxoSpent$ = new Subject<object>();
difficultyAdjustment$ = new ReplaySubject<DifficultyAdjustment>(1);
mempoolTransactions$ = new Subject<Transaction>();

View File

@@ -29,6 +29,7 @@ export class WebsocketService {
private trackingTxId: string;
private isTrackingMempoolBlock = false;
private isTrackingRbf = false;
private isTrackingRbfSummary = false;
private trackingMempoolBlock: number;
private latestGitCommit = '';
private onlineCheckTimeout: number;
@@ -185,6 +186,16 @@ export class WebsocketService {
this.isTrackingRbf = false;
}
startTrackRbfSummary() {
this.websocketSubject.next({ 'track-rbf-summary': true });
this.isTrackingRbfSummary = true;
}
stopTrackRbfSummary() {
this.websocketSubject.next({ 'track-rbf-summary': false });
this.isTrackingRbfSummary = false;
}
startTrackBisqMarket(market: string) {
this.websocketSubject.next({ 'track-bisq-market': market });
}
@@ -283,6 +294,10 @@ export class WebsocketService {
this.stateService.rbfLatest$.next(response.rbfLatest);
}
if (response.rbfLatestSummary) {
this.stateService.rbfLatestSummary$.next(response.rbfLatestSummary);
}
if (response.txReplaced) {
this.stateService.txReplaced$.next(response.txReplaced);
}