Merge pull request #3962 from mempool/mononaut/latest-replacements
Switch "Latest blocks" to "Latest replacements"
This commit is contained in:
commit
f8fc0439f8
@ -6,6 +6,7 @@ import { Common } from "./common";
|
|||||||
interface RbfTransaction extends TransactionStripped {
|
interface RbfTransaction extends TransactionStripped {
|
||||||
rbf?: boolean;
|
rbf?: boolean;
|
||||||
mined?: boolean;
|
mined?: boolean;
|
||||||
|
fullRbf?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RbfTree {
|
interface RbfTree {
|
||||||
@ -17,6 +18,16 @@ interface RbfTree {
|
|||||||
replaces: RbfTree[];
|
replaces: RbfTree[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ReplacementInfo {
|
||||||
|
mined: boolean;
|
||||||
|
fullRbf: boolean;
|
||||||
|
txid: string;
|
||||||
|
oldFee: number;
|
||||||
|
oldVsize: number;
|
||||||
|
newFee: number;
|
||||||
|
newVsize: number;
|
||||||
|
}
|
||||||
|
|
||||||
class RbfCache {
|
class RbfCache {
|
||||||
private replacedBy: Map<string, string> = new Map();
|
private replacedBy: Map<string, string> = new Map();
|
||||||
private replaces: Map<string, string[]> = new Map();
|
private replaces: Map<string, string[]> = new Map();
|
||||||
@ -41,11 +52,15 @@ class RbfCache {
|
|||||||
this.txs.set(newTx.txid, newTxExtended);
|
this.txs.set(newTx.txid, newTxExtended);
|
||||||
|
|
||||||
// maintain rbf trees
|
// maintain rbf trees
|
||||||
let fullRbf = false;
|
let txFullRbf = false;
|
||||||
|
let treeFullRbf = false;
|
||||||
const replacedTrees: RbfTree[] = [];
|
const replacedTrees: RbfTree[] = [];
|
||||||
for (const replacedTxExtended of replaced) {
|
for (const replacedTxExtended of replaced) {
|
||||||
const replacedTx = Common.stripTransaction(replacedTxExtended) as RbfTransaction;
|
const replacedTx = Common.stripTransaction(replacedTxExtended) as RbfTransaction;
|
||||||
replacedTx.rbf = replacedTxExtended.vin.some((v) => v.sequence < 0xfffffffe);
|
replacedTx.rbf = replacedTxExtended.vin.some((v) => v.sequence < 0xfffffffe);
|
||||||
|
if (!replacedTx.rbf) {
|
||||||
|
txFullRbf = true;
|
||||||
|
}
|
||||||
this.replacedBy.set(replacedTx.txid, newTx.txid);
|
this.replacedBy.set(replacedTx.txid, newTx.txid);
|
||||||
if (this.treeMap.has(replacedTx.txid)) {
|
if (this.treeMap.has(replacedTx.txid)) {
|
||||||
const treeId = this.treeMap.get(replacedTx.txid);
|
const treeId = this.treeMap.get(replacedTx.txid);
|
||||||
@ -55,7 +70,7 @@ class RbfCache {
|
|||||||
if (tree) {
|
if (tree) {
|
||||||
tree.interval = newTime - tree?.time;
|
tree.interval = newTime - tree?.time;
|
||||||
replacedTrees.push(tree);
|
replacedTrees.push(tree);
|
||||||
fullRbf = fullRbf || tree.fullRbf || !tree.tx.rbf;
|
treeFullRbf = treeFullRbf || tree.fullRbf || !tree.tx.rbf;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -67,15 +82,16 @@ class RbfCache {
|
|||||||
fullRbf: !replacedTx.rbf,
|
fullRbf: !replacedTx.rbf,
|
||||||
replaces: [],
|
replaces: [],
|
||||||
});
|
});
|
||||||
fullRbf = fullRbf || !replacedTx.rbf;
|
treeFullRbf = treeFullRbf || !replacedTx.rbf;
|
||||||
this.txs.set(replacedTx.txid, replacedTxExtended);
|
this.txs.set(replacedTx.txid, replacedTxExtended);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
newTx.fullRbf = txFullRbf;
|
||||||
const treeId = replacedTrees[0].tx.txid;
|
const treeId = replacedTrees[0].tx.txid;
|
||||||
const newTree = {
|
const newTree = {
|
||||||
tx: newTx,
|
tx: newTx,
|
||||||
time: newTime,
|
time: newTime,
|
||||||
fullRbf,
|
fullRbf: treeFullRbf,
|
||||||
replaces: replacedTrees
|
replaces: replacedTrees
|
||||||
};
|
};
|
||||||
this.rbfTrees.set(treeId, newTree);
|
this.rbfTrees.set(treeId, newTree);
|
||||||
@ -349,6 +365,27 @@ class RbfCache {
|
|||||||
}
|
}
|
||||||
return tree;
|
return tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getLatestRbfSummary(): ReplacementInfo[] {
|
||||||
|
const rbfList = this.getRbfTrees(false);
|
||||||
|
return rbfList.slice(0, 6).map(rbfTree => {
|
||||||
|
let oldFee = 0;
|
||||||
|
let oldVsize = 0;
|
||||||
|
for (const replaced of rbfTree.replaces) {
|
||||||
|
oldFee += replaced.tx.fee;
|
||||||
|
oldVsize += replaced.tx.vsize;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
txid: rbfTree.tx.txid,
|
||||||
|
mined: !!rbfTree.tx.mined,
|
||||||
|
fullRbf: !!rbfTree.tx.fullRbf,
|
||||||
|
oldFee,
|
||||||
|
oldVsize,
|
||||||
|
newFee: rbfTree.tx.fee,
|
||||||
|
newVsize: rbfTree.tx.vsize,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new RbfCache();
|
export default new RbfCache();
|
||||||
|
@ -12,7 +12,7 @@ import { Common } from './common';
|
|||||||
import loadingIndicators from './loading-indicators';
|
import loadingIndicators from './loading-indicators';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import transactionUtils from './transaction-utils';
|
import transactionUtils from './transaction-utils';
|
||||||
import rbfCache from './rbf-cache';
|
import rbfCache, { ReplacementInfo } from './rbf-cache';
|
||||||
import difficultyAdjustment from './difficulty-adjustment';
|
import difficultyAdjustment from './difficulty-adjustment';
|
||||||
import feeApi from './fee-api';
|
import feeApi from './fee-api';
|
||||||
import BlocksAuditsRepository from '../repositories/BlocksAuditsRepository';
|
import BlocksAuditsRepository from '../repositories/BlocksAuditsRepository';
|
||||||
@ -40,6 +40,7 @@ class WebsocketHandler {
|
|||||||
|
|
||||||
private socketData: { [key: string]: string } = {};
|
private socketData: { [key: string]: string } = {};
|
||||||
private serializedInitData: string = '{}';
|
private serializedInitData: string = '{}';
|
||||||
|
private lastRbfSummary: ReplacementInfo | null = null;
|
||||||
|
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
@ -225,6 +226,15 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parsedMessage && parsedMessage['track-rbf-summary'] != null) {
|
||||||
|
if (parsedMessage['track-rbf-summary']) {
|
||||||
|
client['track-rbf-summary'] = true;
|
||||||
|
response['rbfLatestSummary'] = this.socketData['rbfSummary'];
|
||||||
|
} else {
|
||||||
|
client['track-rbf-summary'] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (parsedMessage.action === 'init') {
|
if (parsedMessage.action === 'init') {
|
||||||
if (!this.socketData['blocks']?.length || !this.socketData['da']) {
|
if (!this.socketData['blocks']?.length || !this.socketData['da']) {
|
||||||
this.updateSocketData();
|
this.updateSocketData();
|
||||||
@ -395,10 +405,13 @@ class WebsocketHandler {
|
|||||||
const rbfChanges = rbfCache.getRbfChanges();
|
const rbfChanges = rbfCache.getRbfChanges();
|
||||||
let rbfReplacements;
|
let rbfReplacements;
|
||||||
let fullRbfReplacements;
|
let fullRbfReplacements;
|
||||||
|
let rbfSummary;
|
||||||
if (Object.keys(rbfChanges.trees).length) {
|
if (Object.keys(rbfChanges.trees).length) {
|
||||||
rbfReplacements = rbfCache.getRbfTrees(false);
|
rbfReplacements = rbfCache.getRbfTrees(false);
|
||||||
fullRbfReplacements = rbfCache.getRbfTrees(true);
|
fullRbfReplacements = rbfCache.getRbfTrees(true);
|
||||||
|
rbfSummary = rbfCache.getLatestRbfSummary();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const deletedTx of deletedTransactions) {
|
for (const deletedTx of deletedTransactions) {
|
||||||
rbfCache.evict(deletedTx.txid);
|
rbfCache.evict(deletedTx.txid);
|
||||||
}
|
}
|
||||||
@ -409,7 +422,7 @@ class WebsocketHandler {
|
|||||||
const latestTransactions = newTransactions.slice(0, 6).map((tx) => Common.stripTransaction(tx));
|
const latestTransactions = newTransactions.slice(0, 6).map((tx) => Common.stripTransaction(tx));
|
||||||
|
|
||||||
// update init data
|
// update init data
|
||||||
this.updateSocketDataFields({
|
const socketDataFields = {
|
||||||
'mempoolInfo': mempoolInfo,
|
'mempoolInfo': mempoolInfo,
|
||||||
'vBytesPerSecond': vBytesPerSecond,
|
'vBytesPerSecond': vBytesPerSecond,
|
||||||
'mempool-blocks': mBlocks,
|
'mempool-blocks': mBlocks,
|
||||||
@ -417,7 +430,11 @@ class WebsocketHandler {
|
|||||||
'loadingIndicators': loadingIndicators.getLoadingIndicators(),
|
'loadingIndicators': loadingIndicators.getLoadingIndicators(),
|
||||||
'da': da?.previousTime ? da : undefined,
|
'da': da?.previousTime ? da : undefined,
|
||||||
'fees': recommendedFees,
|
'fees': recommendedFees,
|
||||||
});
|
};
|
||||||
|
if (rbfSummary) {
|
||||||
|
socketDataFields['rbfSummary'] = rbfSummary;
|
||||||
|
}
|
||||||
|
this.updateSocketDataFields(socketDataFields);
|
||||||
|
|
||||||
// cache serialized objects to avoid stringify-ing the same thing for every client
|
// cache serialized objects to avoid stringify-ing the same thing for every client
|
||||||
const responseCache = { ...this.socketData };
|
const responseCache = { ...this.socketData };
|
||||||
@ -601,6 +618,10 @@ class WebsocketHandler {
|
|||||||
response['rbfLatest'] = getCachedResponse('fullrbfLatest', fullRbfReplacements);
|
response['rbfLatest'] = getCachedResponse('fullrbfLatest', fullRbfReplacements);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (client['track-rbf-summary'] && rbfSummary) {
|
||||||
|
response['rbfLatestSummary'] = getCachedResponse('rbfLatestSummary', rbfSummary);
|
||||||
|
}
|
||||||
|
|
||||||
if (Object.keys(response).length) {
|
if (Object.keys(response).length) {
|
||||||
const serializedResponse = this.serializeResponse(response);
|
const serializedResponse = this.serializeResponse(response);
|
||||||
client.send(serializedResponse);
|
client.send(serializedResponse);
|
||||||
|
@ -75,36 +75,31 @@
|
|||||||
<div class="col" style="max-height: 410px">
|
<div class="col" style="max-height: 410px">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<a class="title-link" href="" [routerLink]="['/blocks' | relativeUrl]">
|
<a class="title-link" href="" [routerLink]="['/rbf' | relativeUrl]">
|
||||||
<h5 class="card-title d-inline" i18n="dashboard.latest-blocks">Latest blocks</h5>
|
<h5 class="card-title d-inline" i18n="dashboard.latest-rbf-replacements">Latest replacements</h5>
|
||||||
<span> </span>
|
<span> </span>
|
||||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: '#4a68b9'"></fa-icon>
|
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: 'text-top'; font-size: 13px; color: '#4a68b9'"></fa-icon>
|
||||||
</a>
|
</a>
|
||||||
<table class="table lastest-blocks-table">
|
<table class="table lastest-replacements-table">
|
||||||
<thead>
|
<thead>
|
||||||
<th class="table-cell-height" i18n="dashboard.latest-blocks.height">Height</th>
|
<th class="table-cell-txid" i18n="dashboard.latest-transactions.txid">TXID</th>
|
||||||
<th *ngIf="!stateService.env.MINING_DASHBOARD" class="table-cell-mined" i18n="dashboard.latest-blocks.mined">Mined</th>
|
<th class="table-cell-old-fee" i18n="dashboard.previous-transaction-fee">Previous fee</th>
|
||||||
<th *ngIf="stateService.env.MINING_DASHBOARD" class="table-cell-mined pl-lg-4" i18n="mining.pool-name">Pool</th>
|
<th class="table-cell-new-fee" i18n="dashboard.new-transaction-fee">New fee</th>
|
||||||
<th class="table-cell-transaction-count" i18n="dashboard.latest-blocks.transaction-count">TXs</th>
|
<th class="table-cell-badges" i18n="transaction.status|Transaction Status">Status</th>
|
||||||
<th class="table-cell-size" i18n="dashboard.latest-blocks.size">Size</th>
|
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let block of blocks$ | async; let i = index; trackBy: trackByBlock">
|
<tr *ngFor="let replacement of replacements$ | async;">
|
||||||
<td class="table-cell-height" ><a [routerLink]="['/block' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a></td>
|
<td class="table-cell-txid">
|
||||||
<td *ngIf="!stateService.env.MINING_DASHBOARD" class="table-cell-mined" ><app-time kind="since" [time]="block.timestamp" [fastRender]="true"></app-time></td>
|
<a [routerLink]="['/tx' | relativeUrl, replacement.txid]">
|
||||||
<td *ngIf="stateService.env.MINING_DASHBOARD" class="table-cell-mined pl-lg-4">
|
<app-truncate [text]="replacement.txid" [lastChars]="5"></app-truncate>
|
||||||
<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>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="table-cell-transaction-count">{{ block.tx_count | number }}</td>
|
<td class="table-cell-old-fee"><app-fee-rate [fee]="replacement.oldFee" [weight]="replacement.oldVsize * 4"></app-fee-rate></td>
|
||||||
<td class="table-cell-size">
|
<td class="table-cell-new-fee"><app-fee-rate [fee]="replacement.newFee" [weight]="replacement.newVsize * 4"></app-fee-rate></td>
|
||||||
<div class="progress">
|
<td class="table-cell-badges">
|
||||||
<div class="progress-bar progress-mempool {{ network$ | async }}" role="progressbar" [ngStyle]="{'width': (block.weight / stateService.env.BLOCK_WEIGHT_UNITS)*100 + '%' }"> </div>
|
<span *ngIf="replacement.mined" class="badge badge-success" i18n="transaction.rbf.mined">Mined</span>
|
||||||
<div class="progress-text" [innerHTML]="block.size | bytes: 2"></div>
|
<span *ngIf="replacement.fullRbf" class="badge badge-info" i18n="transaction.full-rbf">Full RBF</span>
|
||||||
</div>
|
<span *ngIf="!replacement.fullRbf" class="badge badge-success" i18n="transaction.rbf">RBF</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -175,39 +175,43 @@
|
|||||||
height: 18px;
|
height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lastest-blocks-table {
|
.lastest-replacements-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
table-layout:fixed;
|
||||||
tr, td, th {
|
tr, td, th {
|
||||||
border: 0px;
|
border: 0px;
|
||||||
padding-top: 0.65rem !important;
|
padding-top: 0.71rem !important;
|
||||||
padding-bottom: 0.7rem !important;
|
padding-bottom: 0.75rem !important;
|
||||||
}
|
}
|
||||||
.table-cell-height {
|
td {
|
||||||
width: 15%;
|
overflow:hidden;
|
||||||
|
width: 25%;
|
||||||
}
|
}
|
||||||
.table-cell-mined {
|
.table-cell-txid {
|
||||||
width: 35%;
|
width: 25%;
|
||||||
text-align: left;
|
text-align: start;
|
||||||
}
|
}
|
||||||
.table-cell-transaction-count {
|
.table-cell-old-fee {
|
||||||
display: none;
|
width: 25%;
|
||||||
text-align: right;
|
text-align: end;
|
||||||
width: 20%;
|
|
||||||
display: table-cell;
|
@media(max-width: 1080px) {
|
||||||
}
|
|
||||||
.table-cell-size {
|
|
||||||
display: none;
|
|
||||||
text-align: center;
|
|
||||||
width: 30%;
|
|
||||||
@media (min-width: 485px) {
|
|
||||||
display: table-cell;
|
|
||||||
}
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
display: none;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { combineLatest, merge, Observable, of, Subscription } from 'rxjs';
|
import { combineLatest, merge, Observable, of, Subscription } from 'rxjs';
|
||||||
import { filter, map, scan, share, switchMap, tap } from 'rxjs/operators';
|
import { filter, map, scan, share, switchMap } from 'rxjs/operators';
|
||||||
import { BlockExtended, OptimizedMempoolStats } from '../interfaces/node-api.interface';
|
import { BlockExtended, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface';
|
||||||
import { MempoolInfo, TransactionStripped } from '../interfaces/websocket.interface';
|
import { MempoolInfo, TransactionStripped, ReplacementInfo } from '../interfaces/websocket.interface';
|
||||||
import { ApiService } from '../services/api.service';
|
import { ApiService } from '../services/api.service';
|
||||||
import { StateService } from '../services/state.service';
|
import { StateService } from '../services/state.service';
|
||||||
import { WebsocketService } from '../services/websocket.service';
|
import { WebsocketService } from '../services/websocket.service';
|
||||||
@ -38,8 +38,8 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
mempoolInfoData$: Observable<MempoolInfoData>;
|
mempoolInfoData$: Observable<MempoolInfoData>;
|
||||||
mempoolLoadingStatus$: Observable<number>;
|
mempoolLoadingStatus$: Observable<number>;
|
||||||
vBytesPerSecondLimit = 1667;
|
vBytesPerSecondLimit = 1667;
|
||||||
blocks$: Observable<BlockExtended[]>;
|
|
||||||
transactions$: Observable<TransactionStripped[]>;
|
transactions$: Observable<TransactionStripped[]>;
|
||||||
|
replacements$: Observable<ReplacementInfo[]>;
|
||||||
latestBlockHeight: number;
|
latestBlockHeight: number;
|
||||||
mempoolTransactionsWeightPerSecondData: any;
|
mempoolTransactionsWeightPerSecondData: any;
|
||||||
mempoolStats$: Observable<MempoolStatsData>;
|
mempoolStats$: Observable<MempoolStatsData>;
|
||||||
@ -58,12 +58,14 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.currencySubscription.unsubscribe();
|
this.currencySubscription.unsubscribe();
|
||||||
|
this.websocketService.stopTrackRbfSummary();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.isLoadingWebSocket$ = this.stateService.isLoadingWebSocket$;
|
this.isLoadingWebSocket$ = this.stateService.isLoadingWebSocket$;
|
||||||
this.seoService.resetTitle();
|
this.seoService.resetTitle();
|
||||||
this.websocketService.want(['blocks', 'stats', 'mempool-blocks', 'live-2h-chart']);
|
this.websocketService.want(['blocks', 'stats', 'mempool-blocks', 'live-2h-chart']);
|
||||||
|
this.websocketService.startTrackRbfSummary();
|
||||||
this.network$ = merge(of(''), this.stateService.networkChanged$);
|
this.network$ = merge(of(''), this.stateService.networkChanged$);
|
||||||
this.mempoolLoadingStatus$ = this.stateService.loadingIndicators$
|
this.mempoolLoadingStatus$ = this.stateService.loadingIndicators$
|
||||||
.pipe(
|
.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$
|
this.transactions$ = this.stateService.transactions$
|
||||||
.pipe(
|
.pipe(
|
||||||
scan((acc, tx) => {
|
scan((acc, tx) => {
|
||||||
@ -159,6 +144,8 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
}, []),
|
}, []),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.replacements$ = this.stateService.rbfLatestSummary$;
|
||||||
|
|
||||||
this.mempoolStats$ = this.stateService.connectionState$
|
this.mempoolStats$ = this.stateService.connectionState$
|
||||||
.pipe(
|
.pipe(
|
||||||
filter((state) => state === 2),
|
filter((state) => state === 2),
|
||||||
@ -219,4 +206,16 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
trackByBlock(index: number, block: BlockExtended) {
|
trackByBlock(index: number, block: BlockExtended) {
|
||||||
return block.height;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ export interface WebsocketResponse {
|
|||||||
txReplaced?: ReplacedTransaction;
|
txReplaced?: ReplacedTransaction;
|
||||||
rbfInfo?: RbfTree;
|
rbfInfo?: RbfTree;
|
||||||
rbfLatest?: RbfTree[];
|
rbfLatest?: RbfTree[];
|
||||||
|
rbfLatestSummary?: ReplacementInfo[];
|
||||||
utxoSpent?: object;
|
utxoSpent?: object;
|
||||||
transactions?: TransactionStripped[];
|
transactions?: TransactionStripped[];
|
||||||
loadingIndicators?: ILoadingIndicators;
|
loadingIndicators?: ILoadingIndicators;
|
||||||
@ -29,6 +30,7 @@ export interface WebsocketResponse {
|
|||||||
'track-asset'?: string;
|
'track-asset'?: string;
|
||||||
'track-mempool-block'?: number;
|
'track-mempool-block'?: number;
|
||||||
'track-rbf'?: string;
|
'track-rbf'?: string;
|
||||||
|
'track-rbf-summary'?: boolean;
|
||||||
'watch-mempool'?: boolean;
|
'watch-mempool'?: boolean;
|
||||||
'track-bisq-market'?: string;
|
'track-bisq-market'?: string;
|
||||||
'refresh-blocks'?: boolean;
|
'refresh-blocks'?: boolean;
|
||||||
@ -37,6 +39,16 @@ export interface WebsocketResponse {
|
|||||||
export interface ReplacedTransaction extends Transaction {
|
export interface ReplacedTransaction extends Transaction {
|
||||||
txid: string;
|
txid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ReplacementInfo {
|
||||||
|
mined: boolean;
|
||||||
|
fullRbf: boolean;
|
||||||
|
txid: string;
|
||||||
|
oldFee: number;
|
||||||
|
oldVsize: number;
|
||||||
|
newFee: number;
|
||||||
|
newVsize: number;
|
||||||
|
}
|
||||||
export interface MempoolBlock {
|
export interface MempoolBlock {
|
||||||
blink?: boolean;
|
blink?: boolean;
|
||||||
height?: number;
|
height?: number;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core';
|
import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core';
|
||||||
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable, merge } from 'rxjs';
|
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable, merge } from 'rxjs';
|
||||||
import { Transaction } from '../interfaces/electrs.interface';
|
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 { 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';
|
||||||
@ -108,6 +108,7 @@ export class StateService {
|
|||||||
txReplaced$ = new Subject<ReplacedTransaction>();
|
txReplaced$ = new Subject<ReplacedTransaction>();
|
||||||
txRbfInfo$ = new Subject<RbfTree>();
|
txRbfInfo$ = new Subject<RbfTree>();
|
||||||
rbfLatest$ = new Subject<RbfTree[]>();
|
rbfLatest$ = new Subject<RbfTree[]>();
|
||||||
|
rbfLatestSummary$ = new Subject<ReplacementInfo[]>();
|
||||||
utxoSpent$ = new Subject<object>();
|
utxoSpent$ = new Subject<object>();
|
||||||
difficultyAdjustment$ = new ReplaySubject<DifficultyAdjustment>(1);
|
difficultyAdjustment$ = new ReplaySubject<DifficultyAdjustment>(1);
|
||||||
mempoolTransactions$ = new Subject<Transaction>();
|
mempoolTransactions$ = new Subject<Transaction>();
|
||||||
|
@ -29,6 +29,7 @@ export class WebsocketService {
|
|||||||
private trackingTxId: string;
|
private trackingTxId: string;
|
||||||
private isTrackingMempoolBlock = false;
|
private isTrackingMempoolBlock = false;
|
||||||
private isTrackingRbf = false;
|
private isTrackingRbf = false;
|
||||||
|
private isTrackingRbfSummary = false;
|
||||||
private trackingMempoolBlock: number;
|
private trackingMempoolBlock: number;
|
||||||
private latestGitCommit = '';
|
private latestGitCommit = '';
|
||||||
private onlineCheckTimeout: number;
|
private onlineCheckTimeout: number;
|
||||||
@ -185,6 +186,16 @@ export class WebsocketService {
|
|||||||
this.isTrackingRbf = false;
|
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) {
|
startTrackBisqMarket(market: string) {
|
||||||
this.websocketSubject.next({ 'track-bisq-market': market });
|
this.websocketSubject.next({ 'track-bisq-market': market });
|
||||||
}
|
}
|
||||||
@ -283,6 +294,10 @@ export class WebsocketService {
|
|||||||
this.stateService.rbfLatest$.next(response.rbfLatest);
|
this.stateService.rbfLatest$.next(response.rbfLatest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (response.rbfLatestSummary) {
|
||||||
|
this.stateService.rbfLatestSummary$.next(response.rbfLatestSummary);
|
||||||
|
}
|
||||||
|
|
||||||
if (response.txReplaced) {
|
if (response.txReplaced) {
|
||||||
this.stateService.txReplaced$.next(response.txReplaced);
|
this.stateService.txReplaced$.next(response.txReplaced);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user