diff --git a/backend/src/repositories/BlocksAuditsRepository.ts b/backend/src/repositories/BlocksAuditsRepository.ts
index 43e54b171..6f914ca0b 100644
--- a/backend/src/repositories/BlocksAuditsRepository.ts
+++ b/backend/src/repositories/BlocksAuditsRepository.ts
@@ -1,3 +1,4 @@
+import blocks from '../api/blocks';
import DB from '../database';
import logger from '../logger';
import { BlockAudit, AuditScore } from '../mempool.interfaces';
@@ -64,6 +65,12 @@ class BlocksAuditRepositories {
rows[0].freshTxs = JSON.parse(rows[0].freshTxs);
rows[0].transactions = JSON.parse(rows[0].transactions);
rows[0].template = JSON.parse(rows[0].template);
+ } else {
+ // fallback to non-audited transaction summary
+ const strippedTransactions = await blocks.$getStrippedBlockTransactions(hash);
+ return {
+ transactions: strippedTransactions
+ }
}
return rows[0];
diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts
index 69c78fc83..d9c6a93bb 100644
--- a/frontend/src/app/app-routing.module.ts
+++ b/frontend/src/app/app-routing.module.ts
@@ -4,7 +4,6 @@ import { AppPreloadingStrategy } from './app.preloading-strategy'
import { StartComponent } from './components/start/start.component';
import { TransactionComponent } from './components/transaction/transaction.component';
import { BlockComponent } from './components/block/block.component';
-import { BlockAuditComponent } from './components/block-audit/block-audit.component';
import { AddressComponent } from './components/address/address.component';
import { MasterPageComponent } from './components/master-page/master-page.component';
import { AboutComponent } from './components/about/about.component';
@@ -103,16 +102,6 @@ let routes: Routes = [
},
],
},
- {
- path: 'block-audit',
- data: { networkSpecific: true },
- children: [
- {
- path: ':id',
- component: BlockAuditComponent,
- },
- ],
- },
{
path: 'docs',
loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule),
@@ -219,16 +208,6 @@ let routes: Routes = [
},
],
},
- {
- path: 'block-audit',
- data: { networkSpecific: true },
- children: [
- {
- path: ':id',
- component: BlockAuditComponent,
- },
- ],
- },
{
path: 'docs',
loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule)
@@ -331,16 +310,6 @@ let routes: Routes = [
},
],
},
- {
- path: 'block-audit',
- data: { networkSpecific: true },
- children: [
- {
- path: ':id',
- component: BlockAuditComponent
- },
- ],
- },
{
path: 'docs',
loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule)
diff --git a/frontend/src/app/components/block-audit/block-audit.component.html b/frontend/src/app/components/block-audit/block-audit.component.html
deleted file mode 100644
index a3f2e2ada..000000000
--- a/frontend/src/app/components/block-audit/block-audit.component.html
+++ /dev/null
@@ -1,172 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Transactions |
- {{ blockAudit.tx_count }} |
-
-
- Block health |
- {{ blockAudit.matchRate }}% |
-
-
- Removed txs |
- {{ blockAudit.missingTxs.length }} |
-
-
- Added txs |
- {{ blockAudit.addedTxs.length }} |
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- audit unavailable
-
- {{ error.error }}
-
-
-
-
-
-
- Error loading data.
-
- {{ error }}
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/frontend/src/app/components/block-audit/block-audit.component.scss b/frontend/src/app/components/block-audit/block-audit.component.scss
deleted file mode 100644
index 1e35b7c63..000000000
--- a/frontend/src/app/components/block-audit/block-audit.component.scss
+++ /dev/null
@@ -1,44 +0,0 @@
-.title-block {
- border-top: none;
-}
-
-.table {
- tr td {
- &:last-child {
- text-align: right;
- @media (min-width: 768px) {
- text-align: left;
- }
- }
- }
-}
-
-.block-tx-title {
- display: flex;
- justify-content: space-between;
- flex-direction: column;
- position: relative;
- @media (min-width: 550px) {
- flex-direction: row;
- }
- h2 {
- line-height: 1;
- margin: 0;
- position: relative;
- padding-bottom: 10px;
- @media (min-width: 550px) {
- padding-bottom: 0px;
- align-self: end;
- }
- }
-}
-
-.menu-button {
- @media (min-width: 768px) {
- max-width: 150px;
- }
-}
-
-.block-subtitle {
- text-align: center;
-}
\ No newline at end of file
diff --git a/frontend/src/app/components/block-audit/block-audit.component.ts b/frontend/src/app/components/block-audit/block-audit.component.ts
deleted file mode 100644
index 3787796fd..000000000
--- a/frontend/src/app/components/block-audit/block-audit.component.ts
+++ /dev/null
@@ -1,229 +0,0 @@
-import { Component, OnDestroy, OnInit, AfterViewInit, ViewChildren, QueryList } from '@angular/core';
-import { ActivatedRoute, ParamMap, Router } from '@angular/router';
-import { Subscription, combineLatest, of } from 'rxjs';
-import { map, switchMap, startWith, catchError, filter } from 'rxjs/operators';
-import { BlockAudit, TransactionStripped } from '../../interfaces/node-api.interface';
-import { ApiService } from '../../services/api.service';
-import { ElectrsApiService } from '../../services/electrs-api.service';
-import { StateService } from '../../services/state.service';
-import { detectWebGL } from '../../shared/graphs.utils';
-import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
-import { BlockOverviewGraphComponent } from '../block-overview-graph/block-overview-graph.component';
-
-@Component({
- selector: 'app-block-audit',
- templateUrl: './block-audit.component.html',
- styleUrls: ['./block-audit.component.scss'],
- styles: [`
- .loadingGraphs {
- position: absolute;
- top: 50%;
- left: calc(50% - 15px);
- z-index: 100;
- }
- `],
-})
-export class BlockAuditComponent implements OnInit, AfterViewInit, OnDestroy {
- blockAudit: BlockAudit = undefined;
- transactions: string[];
- auditSubscription: Subscription;
- urlFragmentSubscription: Subscription;
-
- paginationMaxSize: number;
- page = 1;
- itemsPerPage: number;
-
- mode: 'projected' | 'actual' = 'projected';
- error: any;
- isLoading = true;
- webGlEnabled = true;
- isMobile = window.innerWidth <= 767.98;
- hoverTx: string;
-
- childChangeSubscription: Subscription;
-
- blockHash: string;
- numMissing: number = 0;
- numUnexpected: number = 0;
-
- @ViewChildren('blockGraphProjected') blockGraphProjected: QueryList;
- @ViewChildren('blockGraphActual') blockGraphActual: QueryList;
-
- constructor(
- private route: ActivatedRoute,
- public stateService: StateService,
- private router: Router,
- private apiService: ApiService,
- private electrsApiService: ElectrsApiService,
- ) {
- this.webGlEnabled = detectWebGL();
- }
-
- ngOnDestroy() {
- this.childChangeSubscription.unsubscribe();
- this.urlFragmentSubscription.unsubscribe();
- }
-
- ngOnInit(): void {
- this.paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5;
- this.itemsPerPage = this.stateService.env.ITEMS_PER_PAGE;
-
- this.urlFragmentSubscription = this.route.fragment.subscribe((fragment) => {
- if (fragment === 'actual') {
- this.mode = 'actual';
- } else {
- this.mode = 'projected'
- }
- this.setupBlockGraphs();
- });
-
- this.auditSubscription = this.route.paramMap.pipe(
- switchMap((params: ParamMap) => {
- const blockHash = params.get('id') || null;
- if (!blockHash) {
- return null;
- }
-
- let isBlockHeight = false;
- if (/^[0-9]+$/.test(blockHash)) {
- isBlockHeight = true;
- } else {
- this.blockHash = blockHash;
- }
-
- if (isBlockHeight) {
- return this.electrsApiService.getBlockHashFromHeight$(parseInt(blockHash, 10))
- .pipe(
- switchMap((hash: string) => {
- if (hash) {
- this.blockHash = hash;
- return this.apiService.getBlockAudit$(this.blockHash)
- } else {
- return null;
- }
- }),
- catchError((err) => {
- this.error = err;
- return of(null);
- }),
- );
- }
- return this.apiService.getBlockAudit$(this.blockHash)
- }),
- filter((response) => response != null),
- map((response) => {
- const blockAudit = response.body;
- const inTemplate = {};
- const inBlock = {};
- const isAdded = {};
- const isCensored = {};
- const isMissing = {};
- const isSelected = {};
- this.numMissing = 0;
- this.numUnexpected = 0;
- for (const tx of blockAudit.template) {
- inTemplate[tx.txid] = true;
- }
- for (const tx of blockAudit.transactions) {
- inBlock[tx.txid] = true;
- }
- for (const txid of blockAudit.addedTxs) {
- isAdded[txid] = true;
- }
- for (const txid of blockAudit.missingTxs) {
- isCensored[txid] = true;
- }
- // set transaction statuses
- for (const tx of blockAudit.template) {
- if (isCensored[tx.txid]) {
- tx.status = 'censored';
- } else if (inBlock[tx.txid]) {
- tx.status = 'found';
- } else {
- tx.status = 'missing';
- isMissing[tx.txid] = true;
- this.numMissing++;
- }
- }
- for (const [index, tx] of blockAudit.transactions.entries()) {
- if (index === 0) {
- tx.status = null;
- } else if (isAdded[tx.txid]) {
- tx.status = 'added';
- } else if (inTemplate[tx.txid]) {
- tx.status = 'found';
- } else {
- tx.status = 'selected';
- isSelected[tx.txid] = true;
- this.numUnexpected++;
- }
- }
- for (const tx of blockAudit.transactions) {
- inBlock[tx.txid] = true;
- }
- return blockAudit;
- }),
- catchError((err) => {
- console.log(err);
- this.error = err;
- this.isLoading = false;
- return of(null);
- }),
- ).subscribe((blockAudit) => {
- this.blockAudit = blockAudit;
- this.setupBlockGraphs();
- this.isLoading = false;
- });
- }
-
- ngAfterViewInit() {
- this.childChangeSubscription = combineLatest([this.blockGraphProjected.changes.pipe(startWith(null)), this.blockGraphActual.changes.pipe(startWith(null))]).subscribe(() => {
- this.setupBlockGraphs();
- })
- }
-
- setupBlockGraphs() {
- if (this.blockAudit) {
- this.blockGraphProjected.forEach(graph => {
- graph.destroy();
- if (this.isMobile && this.mode === 'actual') {
- graph.setup(this.blockAudit.transactions);
- } else {
- graph.setup(this.blockAudit.template);
- }
- })
- this.blockGraphActual.forEach(graph => {
- graph.destroy();
- graph.setup(this.blockAudit.transactions);
- })
- }
- }
-
- onResize(event: any) {
- const isMobile = event.target.innerWidth <= 767.98;
- const changed = isMobile !== this.isMobile;
- this.isMobile = isMobile;
- this.paginationMaxSize = event.target.innerWidth < 670 ? 3 : 5;
-
- if (changed) {
- this.changeMode(this.mode);
- }
- }
-
- changeMode(mode: 'projected' | 'actual') {
- this.router.navigate([], { fragment: mode });
- }
-
- onTxClick(event: TransactionStripped): void {
- const url = new RelativeUrlPipe(this.stateService).transform(`/tx/${event.txid}`);
- this.router.navigate([url]);
- }
-
- onTxHover(txid: string): void {
- if (txid && txid.length) {
- this.hoverTx = txid;
- } else {
- this.hoverTx = null;
- }
- }
-}
diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.html b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.html
index 782cbe25e..77ee62cae 100644
--- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.html
+++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.html
@@ -1,7 +1,8 @@
-
-
+
();
@Output() txHoverEvent = new EventEmitter();
@Output() readyEvent = new EventEmitter();
diff --git a/frontend/src/app/components/block-overview-graph/tx-view.ts b/frontend/src/app/components/block-overview-graph/tx-view.ts
index f07d96eb0..f73b83fd4 100644
--- a/frontend/src/app/components/block-overview-graph/tx-view.ts
+++ b/frontend/src/app/components/block-overview-graph/tx-view.ts
@@ -3,12 +3,13 @@ import { FastVertexArray } from './fast-vertex-array';
import { TransactionStripped } from '../../interfaces/websocket.interface';
import { SpriteUpdateParams, Square, Color, ViewUpdateParams } from './sprite-types';
import { feeLevels, mempoolFeeColors } from '../../app.constants';
+import BlockScene from './block-scene';
const hoverTransitionTime = 300;
const defaultHoverColor = hexToColor('1bd8f4');
const feeColors = mempoolFeeColors.map(hexToColor);
-const auditFeeColors = feeColors.map((color) => desaturate(color, 0.3));
+const auditFeeColors = feeColors.map((color) => darken(desaturate(color, 0.3), 0.9));
const auditColors = {
censored: hexToColor('f344df'),
missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7),
@@ -34,7 +35,8 @@ export default class TxView implements TransactionStripped {
vsize: number;
value: number;
feerate: number;
- status?: 'found' | 'missing' | 'added' | 'censored' | 'selected';
+ status?: 'found' | 'missing' | 'fresh' | 'added' | 'censored' | 'selected';
+ context?: 'projected' | 'actual';
initialised: boolean;
vertexArray: FastVertexArray;
@@ -48,6 +50,7 @@ export default class TxView implements TransactionStripped {
dirty: boolean;
constructor(tx: TransactionStripped, vertexArray: FastVertexArray) {
+ this.context = tx.context;
this.txid = tx.txid;
this.fee = tx.fee;
this.vsize = tx.vsize;
@@ -159,12 +162,18 @@ export default class TxView implements TransactionStripped {
return auditColors.censored;
case 'missing':
return auditColors.missing;
+ case 'fresh':
+ return auditColors.missing;
case 'added':
return auditColors.added;
case 'selected':
return auditColors.selected;
case 'found':
- return auditFeeColors[feeLevelIndex] || auditFeeColors[mempoolFeeColors.length - 1];
+ if (this.context === 'projected') {
+ return auditFeeColors[feeLevelIndex] || auditFeeColors[mempoolFeeColors.length - 1];
+ } else {
+ return feeLevelColor;
+ }
default:
return feeLevelColor;
}
diff --git a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html
index 8c1002025..71801bfb4 100644
--- a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html
+++ b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html
@@ -37,9 +37,10 @@
match |
removed |
- omitted |
+ marginal fee rate |
+ recently broadcast |
added |
- extra |
+ marginal fee rate |
diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html
index ba8f3aef3..51f1162e3 100644
--- a/frontend/src/app/components/block/block.component.html
+++ b/frontend/src/app/components/block/block.component.html
@@ -54,7 +54,19 @@
Weight |
|
-
+
+ Block health |
+
+ {{ blockAudit.matchRate }}%
+ Unknown
+ |
+
+
+
+
+ Fee span |
+ {{ block.extras.feeRange[0] | number:'1.0-0' }} - {{ block.extras.feeRange[block.extras.feeRange.length - 1] | number:'1.0-0' }} sat/vB |
+
Median fee |
~{{ block?.extras?.medianFee | number:'1.0-0' }} sat/vB |
@@ -98,26 +110,19 @@
Miner |
-
{{ block.extras.pool.name }}
|
-
{{ block.extras.pool.name }}
|
-
- Block health |
-
- {{ block.extras.matchRate }}%
- Unknown
- |
-
-
+
@@ -138,7 +143,11 @@
|
-
+
+ |
+
+
+
|
@@ -148,17 +157,25 @@
|
-
+
|
-
+
+ |
+
+
-
-
+
+
+
+
+ Fee span |
+ {{ block.extras.feeRange[0] | number:'1.0-0' }} - {{ block.extras.feeRange[block.extras.feeRange.length - 1] | number:'1.0-0' }} sat/vB |
+
Median fee |
~{{ block?.extras?.medianFee | number:'1.0-0' }} sat/vB |
@@ -216,8 +233,9 @@
-
+
+
|
@@ -230,22 +248,54 @@
|
+
+ |
+
-
-
+
+
+
+
+
+
+
+
@@ -273,6 +323,7 @@
+
Difficulty |
{{ block.difficulty }} |
diff --git a/frontend/src/app/components/block/block.component.scss b/frontend/src/app/components/block/block.component.scss
index d6c4d65b4..69002de79 100644
--- a/frontend/src/app/components/block/block.component.scss
+++ b/frontend/src/app/components/block/block.component.scss
@@ -171,3 +171,35 @@ h1 {
margin: auto;
}
}
+
+.menu-button {
+ @media (min-width: 768px) {
+ max-width: 150px;
+ }
+}
+
+.block-subtitle {
+ text-align: center;
+}
+
+.nav-tabs {
+ border-color: white;
+ border-width: 1px;
+}
+
+.nav-tabs .nav-link {
+ background: inherit;
+ border-width: 1px;
+ border-bottom: none;
+ border-color: transparent;
+ margin-bottom: -1px;
+ cursor: pointer;
+
+ &.active {
+ background: #24273e;
+ }
+
+ &.active, &:hover {
+ border-color: white;
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts
index aff07a95e..66dad6d4b 100644
--- a/frontend/src/app/components/block/block.component.ts
+++ b/frontend/src/app/components/block/block.component.ts
@@ -1,15 +1,15 @@
-import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
+import { Component, OnInit, OnDestroy, ViewChildren, QueryList } from '@angular/core';
import { Location } from '@angular/common';
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { ElectrsApiService } from '../../services/electrs-api.service';
-import { switchMap, tap, throttleTime, catchError, map, shareReplay, startWith, pairwise } from 'rxjs/operators';
+import { switchMap, tap, throttleTime, catchError, map, shareReplay, startWith, pairwise, filter } from 'rxjs/operators';
import { Transaction, Vout } from '../../interfaces/electrs.interface';
-import { Observable, of, Subscription, asyncScheduler, EMPTY, Subject } from 'rxjs';
+import { Observable, of, Subscription, asyncScheduler, EMPTY, combineLatest } from 'rxjs';
import { StateService } from '../../services/state.service';
import { SeoService } from '../../services/seo.service';
import { WebsocketService } from '../../services/websocket.service';
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
-import { BlockExtended, TransactionStripped } from '../../interfaces/node-api.interface';
+import { BlockAudit, BlockExtended, TransactionStripped } from '../../interfaces/node-api.interface';
import { ApiService } from '../../services/api.service';
import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component';
import { detectWebGL } from '../../shared/graphs.utils';
@@ -17,11 +17,20 @@ import { detectWebGL } from '../../shared/graphs.utils';
@Component({
selector: 'app-block',
templateUrl: './block.component.html',
- styleUrls: ['./block.component.scss']
+ styleUrls: ['./block.component.scss'],
+ styles: [`
+ .loadingGraphs {
+ position: absolute;
+ top: 50%;
+ left: calc(50% - 15px);
+ z-index: 100;
+ }
+ `],
})
export class BlockComponent implements OnInit, OnDestroy {
network = '';
block: BlockExtended;
+ blockAudit: BlockAudit = undefined;
blockHeight: number;
lastBlockHeight: number;
nextBlockHeight: number;
@@ -48,9 +57,16 @@ export class BlockComponent implements OnInit, OnDestroy {
overviewError: any = null;
webGlEnabled = true;
indexingAvailable = false;
+ auditEnabled = true;
+ isMobile = window.innerWidth <= 767.98;
+ hoverTx: string;
+ numMissing: number = 0;
+ numUnexpected: number = 0;
+ mode: 'projected' | 'actual' = 'projected';
transactionSubscription: Subscription;
overviewSubscription: Subscription;
+ auditSubscription: Subscription;
keyNavigationSubscription: Subscription;
blocksSubscription: Subscription;
networkChangedSubscription: Subscription;
@@ -60,10 +76,10 @@ export class BlockComponent implements OnInit, OnDestroy {
nextBlockTxListSubscription: Subscription = undefined;
timeLtrSubscription: Subscription;
timeLtr: boolean;
- fetchAuditScore$ = new Subject();
- fetchAuditScoreSubscription: Subscription;
+ childChangeSubscription: Subscription;
- @ViewChild('blockGraph') blockGraph: BlockOverviewGraphComponent;
+ @ViewChildren('blockGraphProjected') blockGraphProjected: QueryList;
+ @ViewChildren('blockGraphActual') blockGraphActual: QueryList;
constructor(
private route: ActivatedRoute,
@@ -89,8 +105,8 @@ export class BlockComponent implements OnInit, OnDestroy {
this.timeLtr = !!ltr;
});
- this.indexingAvailable = (this.stateService.env.BASE_MODULE === 'mempool' &&
- this.stateService.env.MINING_DASHBOARD === true);
+ this.indexingAvailable = (this.stateService.env.BASE_MODULE === 'mempool' && this.stateService.env.MINING_DASHBOARD === true);
+ this.auditEnabled = this.indexingAvailable;
this.txsLoadingStatus$ = this.route.paramMap
.pipe(
@@ -107,30 +123,12 @@ export class BlockComponent implements OnInit, OnDestroy {
if (block.id === this.blockHash) {
this.block = block;
- if (this.block.id && this.block?.extras?.matchRate == null) {
- this.fetchAuditScore$.next(this.block.id);
- }
if (block?.extras?.reward != undefined) {
this.fees = block.extras.reward / 100000000 - this.blockSubsidy;
}
}
});
- if (this.indexingAvailable) {
- this.fetchAuditScoreSubscription = this.fetchAuditScore$
- .pipe(
- switchMap((hash) => this.apiService.getBlockAuditScore$(hash)),
- catchError(() => EMPTY),
- )
- .subscribe((score) => {
- if (score && score.hash === this.block.id) {
- this.block.extras.matchRate = score.matchRate || null;
- } else {
- this.block.extras.matchRate = null;
- }
- });
- }
-
const block$ = this.route.paramMap.pipe(
switchMap((params: ParamMap) => {
const blockHash: string = params.get('id') || '';
@@ -212,7 +210,11 @@ export class BlockComponent implements OnInit, OnDestroy {
setTimeout(() => {
this.nextBlockSubscription = this.apiService.getBlock$(block.previousblockhash).subscribe();
this.nextBlockTxListSubscription = this.electrsApiService.getBlockTransactions$(block.previousblockhash).subscribe();
- this.nextBlockSummarySubscription = this.apiService.getStrippedBlockTransactions$(block.previousblockhash).subscribe();
+ if (this.indexingAvailable) {
+ this.apiService.getBlockAudit$(block.previousblockhash);
+ } else {
+ this.nextBlockSummarySubscription = this.apiService.getStrippedBlockTransactions$(block.previousblockhash).subscribe();
+ }
}, 100);
}
@@ -229,9 +231,6 @@ export class BlockComponent implements OnInit, OnDestroy {
this.fees = block.extras.reward / 100000000 - this.blockSubsidy;
}
this.stateService.markBlock$.next({ blockHeight: this.blockHeight });
- if (this.block.id && this.block?.extras?.matchRate == null) {
- this.fetchAuditScore$.next(this.block.id);
- }
this.isLoadingTransactions = true;
this.transactions = null;
this.transactionsError = null;
@@ -263,40 +262,126 @@ export class BlockComponent implements OnInit, OnDestroy {
this.isLoadingOverview = false;
});
- this.overviewSubscription = block$.pipe(
- startWith(null),
- pairwise(),
- switchMap(([prevBlock, block]) => this.apiService.getStrippedBlockTransactions$(block.id)
- .pipe(
- catchError((err) => {
- this.overviewError = err;
- return of([]);
- }),
- switchMap((transactions) => {
- if (prevBlock) {
- return of({ transactions, direction: (prevBlock.height < block.height) ? 'right' : 'left' });
- } else {
- return of({ transactions, direction: 'down' });
+ if (!this.indexingAvailable) {
+ this.overviewSubscription = block$.pipe(
+ startWith(null),
+ pairwise(),
+ switchMap(([prevBlock, block]) => this.apiService.getStrippedBlockTransactions$(block.id)
+ .pipe(
+ catchError((err) => {
+ this.overviewError = err;
+ return of([]);
+ }),
+ switchMap((transactions) => {
+ if (prevBlock) {
+ return of({ transactions, direction: (prevBlock.height < block.height) ? 'right' : 'left' });
+ } else {
+ return of({ transactions, direction: 'down' });
+ }
+ })
+ )
+ ),
+ )
+ .subscribe(({transactions, direction}: {transactions: TransactionStripped[], direction: string}) => {
+ this.strippedTransactions = transactions;
+ this.isLoadingOverview = false;
+ this.setupBlockGraphs();
+ },
+ (error) => {
+ this.error = error;
+ this.isLoadingOverview = false;
+ });
+ }
+
+ if (this.indexingAvailable) {
+ this.auditSubscription = block$.pipe(
+ startWith(null),
+ pairwise(),
+ switchMap(([prevBlock, block]) => this.apiService.getBlockAudit$(block.id)
+ .pipe(
+ catchError((err) => {
+ this.overviewError = err;
+ return of([]);
+ })
+ )
+ ),
+ filter((response) => response != null),
+ map((response) => {
+ const blockAudit = response.body;
+ const inTemplate = {};
+ const inBlock = {};
+ const isAdded = {};
+ const isCensored = {};
+ const isMissing = {};
+ const isSelected = {};
+ const isFresh = {};
+ this.numMissing = 0;
+ this.numUnexpected = 0;
+
+ if (blockAudit.template) {
+ for (const tx of blockAudit.template) {
+ inTemplate[tx.txid] = true;
}
- })
- )
- ),
- )
- .subscribe(({transactions, direction}: {transactions: TransactionStripped[], direction: string}) => {
- this.strippedTransactions = transactions;
- this.isLoadingOverview = false;
- if (this.blockGraph) {
- this.blockGraph.destroy();
- this.blockGraph.setup(this.strippedTransactions);
- }
- },
- (error) => {
- this.error = error;
- this.isLoadingOverview = false;
- if (this.blockGraph) {
- this.blockGraph.destroy();
- }
- });
+ for (const tx of blockAudit.transactions) {
+ inBlock[tx.txid] = true;
+ }
+ for (const txid of blockAudit.addedTxs) {
+ isAdded[txid] = true;
+ }
+ for (const txid of blockAudit.missingTxs) {
+ isCensored[txid] = true;
+ }
+ for (const txid of blockAudit.freshTxs || []) {
+ isFresh[txid] = true;
+ }
+ // set transaction statuses
+ for (const tx of blockAudit.template) {
+ tx.context = 'projected';
+ if (isCensored[tx.txid]) {
+ tx.status = 'censored';
+ } else if (inBlock[tx.txid]) {
+ tx.status = 'found';
+ } else {
+ tx.status = isFresh[tx.txid] ? 'fresh' : 'missing';
+ isMissing[tx.txid] = true;
+ this.numMissing++;
+ }
+ }
+ for (const [index, tx] of blockAudit.transactions.entries()) {
+ tx.context = 'actual';
+ if (index === 0) {
+ tx.status = null;
+ } else if (isAdded[tx.txid]) {
+ tx.status = 'added';
+ } else if (inTemplate[tx.txid]) {
+ tx.status = 'found';
+ } else {
+ tx.status = 'selected';
+ isSelected[tx.txid] = true;
+ this.numUnexpected++;
+ }
+ }
+ for (const tx of blockAudit.transactions) {
+ inBlock[tx.txid] = true;
+ }
+ this.auditEnabled = true;
+ } else {
+ this.auditEnabled = false;
+ }
+ return blockAudit;
+ }),
+ catchError((err) => {
+ console.log(err);
+ this.error = err;
+ this.isLoadingOverview = false;
+ return of(null);
+ }),
+ ).subscribe((blockAudit) => {
+ this.blockAudit = blockAudit;
+ this.setupBlockGraphs();
+ this.isLoadingOverview = false;
+ });
+ }
this.networkChangedSubscription = this.stateService.networkChanged$
.subscribe((network) => this.network = network);
@@ -307,6 +392,12 @@ export class BlockComponent implements OnInit, OnDestroy {
} else {
this.showDetails = false;
}
+ if (params.view === 'projected') {
+ this.mode = 'projected';
+ } else {
+ this.mode = 'actual';
+ }
+ this.setupBlockGraphs();
});
this.keyNavigationSubscription = this.stateService.keyNavigation$.subscribe((event) => {
@@ -325,17 +416,24 @@ export class BlockComponent implements OnInit, OnDestroy {
});
}
+ ngAfterViewInit(): void {
+ this.childChangeSubscription = combineLatest([this.blockGraphProjected.changes.pipe(startWith(null)), this.blockGraphActual.changes.pipe(startWith(null))]).subscribe(() => {
+ this.setupBlockGraphs();
+ });
+ }
+
ngOnDestroy() {
this.stateService.markBlock$.next({});
this.transactionSubscription.unsubscribe();
- this.overviewSubscription.unsubscribe();
+ this.overviewSubscription?.unsubscribe();
+ this.auditSubscription?.unsubscribe();
this.keyNavigationSubscription.unsubscribe();
this.blocksSubscription.unsubscribe();
this.networkChangedSubscription.unsubscribe();
this.queryParamsSubscription.unsubscribe();
this.timeLtrSubscription.unsubscribe();
- this.fetchAuditScoreSubscription?.unsubscribe();
this.unsubscribeNextBlockSubscriptions();
+ this.childChangeSubscription.unsubscribe();
}
unsubscribeNextBlockSubscriptions() {
@@ -382,7 +480,7 @@ export class BlockComponent implements OnInit, OnDestroy {
this.showDetails = false;
this.router.navigate([], {
relativeTo: this.route,
- queryParams: { showDetails: false },
+ queryParams: { showDetails: false, view: this.mode },
queryParamsHandling: 'merge',
fragment: 'block'
});
@@ -390,7 +488,7 @@ export class BlockComponent implements OnInit, OnDestroy {
this.showDetails = true;
this.router.navigate([], {
relativeTo: this.route,
- queryParams: { showDetails: true },
+ queryParams: { showDetails: true, view: this.mode },
queryParamsHandling: 'merge',
fragment: 'details'
});
@@ -409,10 +507,6 @@ export class BlockComponent implements OnInit, OnDestroy {
return this.block && this.block.height > 681393 && (new Date().getTime() / 1000) < 1628640000;
}
- onResize(event: any) {
- this.paginationMaxSize = event.target.innerWidth < 670 ? 3 : 5;
- }
-
navigateToPreviousBlock() {
if (!this.block) {
return;
@@ -443,8 +537,53 @@ export class BlockComponent implements OnInit, OnDestroy {
}
}
+ setupBlockGraphs(): void {
+ if (this.blockAudit || this.strippedTransactions) {
+ this.blockGraphProjected.forEach(graph => {
+ graph.destroy();
+ if (this.isMobile && this.mode === 'actual') {
+ graph.setup(this.blockAudit?.transactions || this.strippedTransactions || []);
+ } else {
+ graph.setup(this.blockAudit?.template || []);
+ }
+ });
+ this.blockGraphActual.forEach(graph => {
+ graph.destroy();
+ graph.setup(this.blockAudit?.transactions || this.strippedTransactions || []);
+ });
+ }
+ }
+
+ onResize(event: any): void {
+ const isMobile = event.target.innerWidth <= 767.98;
+ const changed = isMobile !== this.isMobile;
+ this.isMobile = isMobile;
+ this.paginationMaxSize = event.target.innerWidth < 670 ? 3 : 5;
+
+ if (changed) {
+ this.changeMode(this.mode);
+ }
+ }
+
+ changeMode(mode: 'projected' | 'actual'): void {
+ this.router.navigate([], {
+ relativeTo: this.route,
+ queryParams: { showDetails: this.showDetails, view: mode },
+ queryParamsHandling: 'merge',
+ fragment: 'overview'
+ });
+ }
+
onTxClick(event: TransactionStripped): void {
const url = new RelativeUrlPipe(this.stateService).transform(`/tx/${event.txid}`);
this.router.navigate([url]);
}
+
+ onTxHover(txid: string): void {
+ if (txid && txid.length) {
+ this.hoverTx = txid;
+ } else {
+ this.hoverTx = null;
+ }
+ }
}
\ No newline at end of file
diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.html b/frontend/src/app/components/blocks-list/blocks-list.component.html
index 69bcf3141..7cc55a815 100644
--- a/frontend/src/app/components/blocks-list/blocks-list.component.html
+++ b/frontend/src/app/components/blocks-list/blocks-list.component.html
@@ -46,7 +46,7 @@
-
+
diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts
index 39d0c3d5d..5df095432 100644
--- a/frontend/src/app/interfaces/node-api.interface.ts
+++ b/frontend/src/app/interfaces/node-api.interface.ts
@@ -141,7 +141,7 @@ export interface TransactionStripped {
fee: number;
vsize: number;
value: number;
- status?: 'found' | 'missing' | 'added' | 'censored' | 'selected';
+ status?: 'found' | 'missing' | 'fresh' | 'added' | 'censored' | 'selected';
}
export interface RewardStats {
diff --git a/frontend/src/app/interfaces/websocket.interface.ts b/frontend/src/app/interfaces/websocket.interface.ts
index 67cc0ffc7..96f7530c9 100644
--- a/frontend/src/app/interfaces/websocket.interface.ts
+++ b/frontend/src/app/interfaces/websocket.interface.ts
@@ -70,7 +70,8 @@ export interface TransactionStripped {
fee: number;
vsize: number;
value: number;
- status?: 'found' | 'missing' | 'added' | 'censored' | 'selected';
+ status?: 'found' | 'missing' | 'fresh' | 'added' | 'censored' | 'selected';
+ context?: 'projected' | 'actual';
}
export interface IBackendInfo {
diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts
index 9e9e2e2a5..ca4501d58 100644
--- a/frontend/src/app/shared/shared.module.ts
+++ b/frontend/src/app/shared/shared.module.ts
@@ -45,7 +45,6 @@ import { StartComponent } from '../components/start/start.component';
import { TransactionComponent } from '../components/transaction/transaction.component';
import { TransactionsListComponent } from '../components/transactions-list/transactions-list.component';
import { BlockComponent } from '../components/block/block.component';
-import { BlockAuditComponent } from '../components/block-audit/block-audit.component';
import { BlockOverviewGraphComponent } from '../components/block-overview-graph/block-overview-graph.component';
import { BlockOverviewTooltipComponent } from '../components/block-overview-tooltip/block-overview-tooltip.component';
import { AddressComponent } from '../components/address/address.component';
@@ -120,7 +119,6 @@ import { GeolocationComponent } from '../shared/components/geolocation/geolocati
StartComponent,
TransactionComponent,
BlockComponent,
- BlockAuditComponent,
BlockOverviewGraphComponent,
BlockOverviewTooltipComponent,
TransactionsListComponent,
@@ -223,7 +221,6 @@ import { GeolocationComponent } from '../shared/components/geolocation/geolocati
StartComponent,
TransactionComponent,
BlockComponent,
- BlockAuditComponent,
BlockOverviewGraphComponent,
BlockOverviewTooltipComponent,
TransactionsListComponent,
|