multiblock mempool page
This commit is contained in:
parent
62c8766e24
commit
7b04ff72cd
@ -3,7 +3,8 @@ import * as WebSocket from 'ws';
|
|||||||
import {
|
import {
|
||||||
BlockExtended, TransactionExtended, MempoolTransactionExtended, WebsocketResponse,
|
BlockExtended, TransactionExtended, MempoolTransactionExtended, WebsocketResponse,
|
||||||
OptimizedStatistic, ILoadingIndicators, GbtCandidates, TxTrackingInfo,
|
OptimizedStatistic, ILoadingIndicators, GbtCandidates, TxTrackingInfo,
|
||||||
MempoolDelta, MempoolDeltaTxids
|
MempoolDelta, MempoolDeltaTxids,
|
||||||
|
TransactionCompressed
|
||||||
} from '../mempool.interfaces';
|
} from '../mempool.interfaces';
|
||||||
import blocks from './blocks';
|
import blocks from './blocks';
|
||||||
import memPool from './mempool';
|
import memPool from './mempool';
|
||||||
@ -315,6 +316,7 @@ class WebsocketHandler {
|
|||||||
|
|
||||||
if (parsedMessage && parsedMessage['track-mempool-block'] !== undefined) {
|
if (parsedMessage && parsedMessage['track-mempool-block'] !== undefined) {
|
||||||
if (Number.isInteger(parsedMessage['track-mempool-block']) && parsedMessage['track-mempool-block'] >= 0) {
|
if (Number.isInteger(parsedMessage['track-mempool-block']) && parsedMessage['track-mempool-block'] >= 0) {
|
||||||
|
client['track-mempool-blocks'] = undefined;
|
||||||
const index = parsedMessage['track-mempool-block'];
|
const index = parsedMessage['track-mempool-block'];
|
||||||
client['track-mempool-block'] = index;
|
client['track-mempool-block'] = index;
|
||||||
const mBlocksWithTransactions = mempoolBlocks.getMempoolBlocksWithTransactions();
|
const mBlocksWithTransactions = mempoolBlocks.getMempoolBlocksWithTransactions();
|
||||||
@ -324,7 +326,31 @@ class WebsocketHandler {
|
|||||||
blockTransactions: (mBlocksWithTransactions[index]?.transactions || []).map(mempoolBlocks.compressTx),
|
blockTransactions: (mBlocksWithTransactions[index]?.transactions || []).map(mempoolBlocks.compressTx),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
client['track-mempool-block'] = null;
|
client['track-mempool-block'] = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsedMessage && parsedMessage['track-mempool-blocks'] !== undefined) {
|
||||||
|
if (parsedMessage['track-mempool-blocks'].length > 0) {
|
||||||
|
client['track-mempool-block'] = undefined;
|
||||||
|
const indices: number[] = [];
|
||||||
|
const mBlocksWithTransactions = mempoolBlocks.getMempoolBlocksWithTransactions();
|
||||||
|
const updates: { index: number, sequence: number, blockTransactions: TransactionCompressed[] }[] = [];
|
||||||
|
for (const i of parsedMessage['track-mempool-blocks']) {
|
||||||
|
const index = parseInt(i);
|
||||||
|
if (Number.isInteger(index) && index >= 0) {
|
||||||
|
indices.push(index);
|
||||||
|
updates.push({
|
||||||
|
index: index,
|
||||||
|
sequence: this.mempoolSequence,
|
||||||
|
blockTransactions: (mBlocksWithTransactions[index]?.transactions || []).map(mempoolBlocks.compressTx),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client['track-mempool-blocks'] = indices;
|
||||||
|
response['projected-block-transactions'] = JSON.stringify(updates);
|
||||||
|
} else {
|
||||||
|
client['track-mempool-blocks'] = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -908,6 +934,19 @@ class WebsocketHandler {
|
|||||||
delta: mBlockDeltas[index],
|
delta: mBlockDeltas[index],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} else if (client['track-mempool-blocks']?.length && memPool.isInSync()) {
|
||||||
|
const indices = client['track-mempool-blocks'];
|
||||||
|
const updates: string[] = [];
|
||||||
|
for (const index of indices) {
|
||||||
|
if (mBlockDeltas[index]) {
|
||||||
|
updates.push(getCachedResponse(`projected-block-transactions-${index}`, {
|
||||||
|
index: index,
|
||||||
|
sequence: this.mempoolSequence,
|
||||||
|
delta: mBlockDeltas[index],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response['projected-block-transactions'] = '[' + updates.join(',') + ']';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client['track-rbf'] === 'all' && rbfReplacements) {
|
if (client['track-rbf'] === 'all' && rbfReplacements) {
|
||||||
@ -1296,6 +1335,27 @@ class WebsocketHandler {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (client['track-mempool-blocks']?.length && memPool.isInSync()) {
|
||||||
|
const indices = client['track-mempool-blocks'];
|
||||||
|
const updates: string[] = [];
|
||||||
|
for (const index of indices) {
|
||||||
|
if (mBlockDeltas && mBlockDeltas[index] && mBlocksWithTransactions[index]?.transactions?.length) {
|
||||||
|
if (mBlockDeltas[index].added.length > (mBlocksWithTransactions[index]?.transactions.length / 2)) {
|
||||||
|
updates.push(getCachedResponse(`projected-block-transactions-full-${index}`, {
|
||||||
|
index: index,
|
||||||
|
sequence: this.mempoolSequence,
|
||||||
|
blockTransactions: mBlocksWithTransactions[index].transactions.map(mempoolBlocks.compressTx),
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
updates.push(getCachedResponse(`projected-block-transactions-delta-${index}`, {
|
||||||
|
index: index,
|
||||||
|
sequence: this.mempoolSequence,
|
||||||
|
delta: mBlockDeltas[index],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response['projected-block-transactions'] = '[' + updates.join(',') + ']';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client['track-mempool-txids']) {
|
if (client['track-mempool-txids']) {
|
||||||
|
@ -3,6 +3,7 @@ import { Routes, RouterModule } from '@angular/router';
|
|||||||
import { AppPreloadingStrategy } from './app.preloading-strategy'
|
import { AppPreloadingStrategy } from './app.preloading-strategy'
|
||||||
import { BlockViewComponent } from './components/block-view/block-view.component';
|
import { BlockViewComponent } from './components/block-view/block-view.component';
|
||||||
import { EightBlocksComponent } from './components/eight-blocks/eight-blocks.component';
|
import { EightBlocksComponent } from './components/eight-blocks/eight-blocks.component';
|
||||||
|
import { EightMempoolComponent } from './components/eight-mempool/eight-mempool.component';
|
||||||
import { MempoolBlockViewComponent } from './components/mempool-block-view/mempool-block-view.component';
|
import { MempoolBlockViewComponent } from './components/mempool-block-view/mempool-block-view.component';
|
||||||
import { ClockComponent } from './components/clock/clock.component';
|
import { ClockComponent } from './components/clock/clock.component';
|
||||||
import { StatusViewComponent } from './components/status-view/status-view.component';
|
import { StatusViewComponent } from './components/status-view/status-view.component';
|
||||||
@ -205,6 +206,10 @@ let routes: Routes = [
|
|||||||
path: 'view/blocks',
|
path: 'view/blocks',
|
||||||
component: EightBlocksComponent,
|
component: EightBlocksComponent,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'view/mempool-blocks',
|
||||||
|
component: EightMempoolComponent,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'status',
|
path: 'status',
|
||||||
data: { networks: ['bitcoin', 'liquid'] },
|
data: { networks: ['bitcoin', 'liquid'] },
|
||||||
|
@ -278,7 +278,7 @@ export default class BlockScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private applyTxUpdate(tx: TxView, update: ViewUpdateParams): void {
|
private applyTxUpdate(tx: TxView, update: ViewUpdateParams): void {
|
||||||
this.animateUntil = Math.max(this.animateUntil, tx.update(update, { minX: this.x, maxY: this.y + this.height }));
|
this.animateUntil = Math.max(this.animateUntil, tx.update(update, { minX: this.x - this.width, maxX: this.x + this.width + this.width, maxY: this.y + this.height }));
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateTxColor(tx: TxView, startTime: number, delay: number, animate: boolean = true, duration?: number): void {
|
private updateTxColor(tx: TxView, startTime: number, delay: number, animate: boolean = true, duration?: number): void {
|
||||||
|
@ -17,10 +17,11 @@ export default class TxSprite {
|
|||||||
tempAttributes: OptionalAttributes;
|
tempAttributes: OptionalAttributes;
|
||||||
|
|
||||||
minX: number;
|
minX: number;
|
||||||
|
maxX: number;
|
||||||
maxY: number;
|
maxY: number;
|
||||||
|
|
||||||
|
|
||||||
constructor(params: SpriteUpdateParams, vertexArray: FastVertexArray, minX, maxY: number) {
|
constructor(params: SpriteUpdateParams, vertexArray: FastVertexArray, minX: number, maxX: number, maxY: number) {
|
||||||
const offsetTime = params.start;
|
const offsetTime = params.start;
|
||||||
this.vertexArray = vertexArray;
|
this.vertexArray = vertexArray;
|
||||||
this.vertexData = Array(VI.length).fill(0);
|
this.vertexData = Array(VI.length).fill(0);
|
||||||
@ -30,6 +31,7 @@ export default class TxSprite {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.minX = minX;
|
this.minX = minX;
|
||||||
|
this.maxX = maxX;
|
||||||
this.maxY = maxY;
|
this.maxY = maxY;
|
||||||
|
|
||||||
this.attributes = {
|
this.attributes = {
|
||||||
@ -84,7 +86,7 @@ export default class TxSprite {
|
|||||||
minDuration: minimum remaining transition duration when adjust = true
|
minDuration: minimum remaining transition duration when adjust = true
|
||||||
temp: if true, this update is only temporary (can be reversed with 'resume')
|
temp: if true, this update is only temporary (can be reversed with 'resume')
|
||||||
*/
|
*/
|
||||||
update(params: SpriteUpdateParams, minX?: number, maxY?: number): void {
|
update(params: SpriteUpdateParams, minX?: number, maxX?: number, maxY?: number): void {
|
||||||
const offsetTime = params.start || performance.now();
|
const offsetTime = params.start || performance.now();
|
||||||
const v = params.duration > 0 ? (1 / params.duration) : 0;
|
const v = params.duration > 0 ? (1 / params.duration) : 0;
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ export default class TxView implements TransactionStripped {
|
|||||||
|
|
||||||
returns minimum transition end time
|
returns minimum transition end time
|
||||||
*/
|
*/
|
||||||
update(params: ViewUpdateParams, { minX, maxY }: { minX: number, maxY: number }): number {
|
update(params: ViewUpdateParams, { minX, maxX, maxY }: { minX: number, maxX: number, maxY: number }): number {
|
||||||
if (params.jitter) {
|
if (params.jitter) {
|
||||||
params.delay += (Math.random() * params.jitter);
|
params.delay += (Math.random() * params.jitter);
|
||||||
}
|
}
|
||||||
@ -117,6 +117,7 @@ export default class TxView implements TransactionStripped {
|
|||||||
toSpriteUpdate(params),
|
toSpriteUpdate(params),
|
||||||
this.vertexArray,
|
this.vertexArray,
|
||||||
minX,
|
minX,
|
||||||
|
maxX,
|
||||||
maxY
|
maxY
|
||||||
);
|
);
|
||||||
// apply any pending hover event
|
// apply any pending hover event
|
||||||
@ -130,6 +131,7 @@ export default class TxView implements TransactionStripped {
|
|||||||
temp: true
|
temp: true
|
||||||
},
|
},
|
||||||
minX,
|
minX,
|
||||||
|
maxX,
|
||||||
maxY
|
maxY
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -137,6 +139,7 @@ export default class TxView implements TransactionStripped {
|
|||||||
this.sprite.update(
|
this.sprite.update(
|
||||||
toSpriteUpdate(params),
|
toSpriteUpdate(params),
|
||||||
minX,
|
minX,
|
||||||
|
maxX,
|
||||||
maxY
|
maxY
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ export class BlockOverviewMultiComponent implements AfterViewInit, OnDestroy, On
|
|||||||
@Input() isLoading: boolean;
|
@Input() isLoading: boolean;
|
||||||
@Input() resolution: number;
|
@Input() resolution: number;
|
||||||
@Input() numBlocks: number;
|
@Input() numBlocks: number;
|
||||||
|
@Input() padding: number = 0;
|
||||||
@Input() blockWidth: number = 360;
|
@Input() blockWidth: number = 360;
|
||||||
@Input() autofit: boolean = false;
|
@Input() autofit: boolean = false;
|
||||||
@Input() blockLimit: number;
|
@Input() blockLimit: number;
|
||||||
@ -285,10 +286,10 @@ export class BlockOverviewMultiComponent implements AfterViewInit, OnDestroy, On
|
|||||||
for (const [index, pendingUpdate] of this.pendingUpdates.entries()) {
|
for (const [index, pendingUpdate] of this.pendingUpdates.entries()) {
|
||||||
if (pendingUpdate.count && performance.now() > (this.lastUpdate + this.animationDuration)) {
|
if (pendingUpdate.count && performance.now() > (this.lastUpdate + this.animationDuration)) {
|
||||||
this.applyUpdate(index, Object.values(pendingUpdate.add), Object.values(pendingUpdate.remove), Object.values(pendingUpdate.change), pendingUpdate.direction);
|
this.applyUpdate(index, Object.values(pendingUpdate.add), Object.values(pendingUpdate.remove), Object.values(pendingUpdate.change), pendingUpdate.direction);
|
||||||
}
|
|
||||||
this.clearUpdateQueue(index);
|
this.clearUpdateQueue(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
clearUpdateQueue(block: number): void {
|
clearUpdateQueue(block: number): void {
|
||||||
this.pendingUpdates[block] = {
|
this.pendingUpdates[block] = {
|
||||||
@ -391,8 +392,8 @@ export class BlockOverviewMultiComponent implements AfterViewInit, OnDestroy, On
|
|||||||
this.gl.viewport(0, 0, this.displayWidth, this.displayHeight);
|
this.gl.viewport(0, 0, this.displayWidth, this.displayHeight);
|
||||||
}
|
}
|
||||||
for (let i = 0; i < this.scenes.length; i++) {
|
for (let i = 0; i < this.scenes.length; i++) {
|
||||||
const blocksPerRow = Math.floor(this.displayWidth / this.blockWidth);
|
const blocksPerRow = Math.floor((this.displayWidth + this.padding) / (this.blockWidth + this.padding));
|
||||||
const x = (i % blocksPerRow) * this.blockWidth;
|
const x = (i % blocksPerRow) * (this.blockWidth + this.padding);
|
||||||
const row = Math.floor(i / blocksPerRow);
|
const row = Math.floor(i / blocksPerRow);
|
||||||
const y = this.displayHeight - ((row + 1) * this.blockWidth);
|
const y = this.displayHeight - ((row + 1) * this.blockWidth);
|
||||||
if (this.scenes[i]) {
|
if (this.scenes[i]) {
|
||||||
@ -401,7 +402,7 @@ export class BlockOverviewMultiComponent implements AfterViewInit, OnDestroy, On
|
|||||||
} else {
|
} else {
|
||||||
this.scenes[i] = new BlockScene({ x, y, width: this.blockWidth, height: this.blockWidth, resolution: this.resolution,
|
this.scenes[i] = new BlockScene({ x, y, width: this.blockWidth, height: this.blockWidth, resolution: this.resolution,
|
||||||
blockLimit: this.blockLimit, orientation: this.orientation, flip: this.flip, vertexArray: this.vertexArray, theme: this.themeService,
|
blockLimit: this.blockLimit, orientation: this.orientation, flip: this.flip, vertexArray: this.vertexArray, theme: this.themeService,
|
||||||
highlighting: this.auditHighlighting, animationDuration: this.animationDuration, animationOffset: 0,
|
highlighting: this.auditHighlighting, animationDuration: this.animationDuration, animationOffset: this.animationOffset,
|
||||||
colorFunction: this.getColorFunction() });
|
colorFunction: this.getColorFunction() });
|
||||||
this.start();
|
this.start();
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ export class EightBlocksComponent implements OnInit, OnDestroy {
|
|||||||
this.wrapBlocks = params.wrap !== 'false';
|
this.wrapBlocks = params.wrap !== 'false';
|
||||||
this.stagger = Number.isInteger(Number(params.stagger)) ? Number(params.stagger) : 0;
|
this.stagger = Number.isInteger(Number(params.stagger)) ? Number(params.stagger) : 0;
|
||||||
this.animationDuration = Number.isInteger(Number(params.animationDuration)) ? Number(params.animationDuration) : 2000;
|
this.animationDuration = Number.isInteger(Number(params.animationDuration)) ? Number(params.animationDuration) : 2000;
|
||||||
this.animationOffset = this.padding * 2;
|
this.animationOffset = 0;
|
||||||
|
|
||||||
if (this.autofit) {
|
if (this.autofit) {
|
||||||
this.resolution = bestFitResolution(76, 96, this.blockWidth - this.padding * 2);
|
this.resolution = bestFitResolution(76, 96, this.blockWidth - this.padding * 2);
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
<!-- <div class="blocks" [class.wrap]="wrapBlocks">
|
||||||
|
<ng-container *ngFor="let i of blockIndices">
|
||||||
|
<div class="block-wrapper" [style]="wrapperStyle">
|
||||||
|
<div class="block-container" [style]="containerStyle"> -->
|
||||||
|
<app-block-overview-multi
|
||||||
|
#blockGraph
|
||||||
|
[isLoading]="false"
|
||||||
|
[numBlocks]="numBlocks"
|
||||||
|
[padding]="padding"
|
||||||
|
[blockWidth]="blockWidth"
|
||||||
|
[resolution]="resolution"
|
||||||
|
[blockLimit]="stateService.blockVSize"
|
||||||
|
[orientation]="'left'"
|
||||||
|
[flip]="true"
|
||||||
|
[animationDuration]="animationDuration"
|
||||||
|
[animationOffset]="animationOffset"
|
||||||
|
[disableSpinner]="true"
|
||||||
|
></app-block-overview-multi>
|
||||||
|
<!-- <div *ngIf="showInfo && blockInfo[i]" class="info" @infoChange>
|
||||||
|
<h1 class="height">{{ blockInfo[i].height }}</h1>
|
||||||
|
<h2 class="mined-by">by {{ blockInfo[i].extras.pool.name || 'Unknown' }}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</div> -->
|
@ -0,0 +1,69 @@
|
|||||||
|
.blocks {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
align-content: flex-start;
|
||||||
|
|
||||||
|
&.wrap {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-wrapper {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
--block-width: 1080px;
|
||||||
|
|
||||||
|
.info {
|
||||||
|
position: absolute;
|
||||||
|
left: 8%;
|
||||||
|
top: 8%;
|
||||||
|
right: 8%;
|
||||||
|
bottom: 8%;
|
||||||
|
height: 84%;
|
||||||
|
width: 84%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: calc(var(--block-width) * 0.03);
|
||||||
|
text-shadow: 0 0 calc(var(--block-width) * 0.05) black;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 6em;
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: calc(var(--block-width) * 0.03);
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 1.8em;
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: calc(var(--block-width) * 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hash {
|
||||||
|
font-family: monospace;
|
||||||
|
word-wrap: break-word;
|
||||||
|
font-size: 1.4em;
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: calc(var(--block-width) * 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mined-by {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-container {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,201 @@
|
|||||||
|
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { catchError } from 'rxjs/operators';
|
||||||
|
import { Subject, Subscription, of } from 'rxjs';
|
||||||
|
import { StateService } from '../../services/state.service';
|
||||||
|
import { WebsocketService } from '../../services/websocket.service';
|
||||||
|
import { BlockExtended, TransactionStripped } from '../../interfaces/node-api.interface';
|
||||||
|
import { ApiService } from '../../services/api.service';
|
||||||
|
import { detectWebGL } from '../../shared/graphs.utils';
|
||||||
|
import { animate, style, transition, trigger } from '@angular/animations';
|
||||||
|
import { BytesPipe } from '../../shared/pipes/bytes-pipe/bytes.pipe';
|
||||||
|
import { BlockOverviewMultiComponent } from '../block-overview-multi/block-overview-multi.component';
|
||||||
|
import { CacheService } from '../../services/cache.service';
|
||||||
|
import { isMempoolDelta, MempoolBlockDelta } from '../../interfaces/websocket.interface';
|
||||||
|
|
||||||
|
function bestFitResolution(min, max, n): number {
|
||||||
|
const target = (min + max) / 2;
|
||||||
|
let bestScore = Infinity;
|
||||||
|
let best = null;
|
||||||
|
for (let i = min; i <= max; i++) {
|
||||||
|
const remainder = (n % i);
|
||||||
|
if (remainder < bestScore || (remainder === bestScore && (Math.abs(i - target) < Math.abs(best - target)))) {
|
||||||
|
bestScore = remainder;
|
||||||
|
best = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BlockInfo extends BlockExtended {
|
||||||
|
timeString: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-eight-mempool',
|
||||||
|
templateUrl: './eight-mempool.component.html',
|
||||||
|
styleUrls: ['./eight-mempool.component.scss'],
|
||||||
|
animations: [
|
||||||
|
trigger('infoChange', [
|
||||||
|
transition(':enter', [
|
||||||
|
style({ opacity: 0 }),
|
||||||
|
animate('1000ms', style({ opacity: 1 })),
|
||||||
|
]),
|
||||||
|
transition(':leave', [
|
||||||
|
animate('1000ms 500ms', style({ opacity: 0 }))
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class EightMempoolComponent implements OnInit, OnDestroy {
|
||||||
|
network = '';
|
||||||
|
strippedTransactions: { [height: number]: TransactionStripped[] } = {};
|
||||||
|
webGlEnabled = true;
|
||||||
|
hoverTx: string | null = null;
|
||||||
|
|
||||||
|
tipSubscription: Subscription;
|
||||||
|
networkChangedSubscription: Subscription;
|
||||||
|
queryParamsSubscription: Subscription;
|
||||||
|
graphChangeSubscription: Subscription;
|
||||||
|
blockSub: Subscription;
|
||||||
|
|
||||||
|
chainDirection: string = 'right';
|
||||||
|
poolDirection: string = 'left';
|
||||||
|
|
||||||
|
lastBlockHeight: number = 0;
|
||||||
|
lastBlockHeightUpdate: number[] = [];
|
||||||
|
numBlocks: number = 8;
|
||||||
|
blockIndices: number[] = [];
|
||||||
|
autofit: boolean = false;
|
||||||
|
padding: number = 0;
|
||||||
|
wrapBlocks: boolean = false;
|
||||||
|
blockWidth: number = 360;
|
||||||
|
animationDuration: number = 2000;
|
||||||
|
animationOffset: number = 0;
|
||||||
|
stagger: number = 0;
|
||||||
|
testing: boolean = true;
|
||||||
|
testHeight: number = 800000;
|
||||||
|
testShiftTimeout: number;
|
||||||
|
|
||||||
|
showInfo: boolean = true;
|
||||||
|
blockInfo: BlockInfo[] = [];
|
||||||
|
|
||||||
|
wrapperStyle = {
|
||||||
|
'--block-width': '1080px',
|
||||||
|
width: '1080px',
|
||||||
|
maxWidth: '1080px',
|
||||||
|
padding: '',
|
||||||
|
};
|
||||||
|
containerStyle = {};
|
||||||
|
resolution: number = 86;
|
||||||
|
|
||||||
|
@ViewChild('blockGraph') blockGraph: BlockOverviewMultiComponent;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
public stateService: StateService,
|
||||||
|
private websocketService: WebsocketService,
|
||||||
|
private apiService: ApiService,
|
||||||
|
private cacheService: CacheService,
|
||||||
|
private bytesPipe: BytesPipe,
|
||||||
|
) {
|
||||||
|
this.webGlEnabled = this.stateService.isBrowser && detectWebGL();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.websocketService.want(['blocks']);
|
||||||
|
this.network = this.stateService.network;
|
||||||
|
|
||||||
|
this.blockSub = this.stateService.mempoolBlockUpdate$.subscribe((update) => {
|
||||||
|
// process update
|
||||||
|
if (isMempoolDelta(update)) {
|
||||||
|
// delta
|
||||||
|
this.updateBlock(update);
|
||||||
|
} else {
|
||||||
|
const transactionsStripped = update.transactions;
|
||||||
|
const inOldBlock = {};
|
||||||
|
const inNewBlock = {};
|
||||||
|
const added: TransactionStripped[] = [];
|
||||||
|
const changed: { txid: string, rate: number | undefined, flags: number, acc: boolean | undefined }[] = [];
|
||||||
|
const removed: string[] = [];
|
||||||
|
for (const tx of transactionsStripped) {
|
||||||
|
inNewBlock[tx.txid] = true;
|
||||||
|
}
|
||||||
|
for (const txid of Object.keys(this.blockGraph?.scenes[update.block]?.txs || {})) {
|
||||||
|
inOldBlock[txid] = true;
|
||||||
|
if (!inNewBlock[txid]) {
|
||||||
|
removed.push(txid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const tx of transactionsStripped) {
|
||||||
|
if (!inOldBlock[tx.txid]) {
|
||||||
|
added.push(tx);
|
||||||
|
} else {
|
||||||
|
changed.push({
|
||||||
|
txid: tx.txid,
|
||||||
|
rate: tx.rate,
|
||||||
|
flags: tx.flags,
|
||||||
|
acc: tx.acc
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.updateBlock({
|
||||||
|
block: update.block,
|
||||||
|
removed,
|
||||||
|
changed,
|
||||||
|
added
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.queryParamsSubscription = this.route.queryParams.subscribe((params) => {
|
||||||
|
this.numBlocks = Number.isInteger(Number(params.numBlocks)) ? Number(params.numBlocks) : 8;
|
||||||
|
this.blockIndices = [...Array(this.numBlocks).keys()];
|
||||||
|
this.lastBlockHeightUpdate = this.blockIndices.map(() => 0);
|
||||||
|
this.autofit = params.autofit !== 'false';
|
||||||
|
this.blockWidth = Number.isInteger(Number(params.blockWidth)) ? Number(params.blockWidth) : 540;
|
||||||
|
this.padding = Number.isInteger(Number(params.padding)) ? Number(params.padding) : this.blockWidth;
|
||||||
|
this.wrapBlocks = params.wrap !== 'false';
|
||||||
|
this.stagger = Number.isInteger(Number(params.stagger)) ? Number(params.stagger) : 0;
|
||||||
|
this.animationDuration = Number.isInteger(Number(params.animationDuration)) ? Number(params.animationDuration) : 2000;
|
||||||
|
this.animationOffset = 0;
|
||||||
|
|
||||||
|
if (this.autofit) {
|
||||||
|
this.resolution = bestFitResolution(76, 96, this.blockWidth - this.padding * 2);
|
||||||
|
} else {
|
||||||
|
this.resolution = 86;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.wrapperStyle = {
|
||||||
|
'--block-width': this.blockWidth + 'px',
|
||||||
|
width: this.blockWidth + 'px',
|
||||||
|
maxWidth: this.blockWidth + 'px',
|
||||||
|
padding: (this.padding || 0) +'px 0px',
|
||||||
|
};
|
||||||
|
|
||||||
|
this.websocketService.startTrackMempoolBlocks(this.blockIndices);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.networkChangedSubscription = this.stateService.networkChanged$
|
||||||
|
.subscribe((network) => this.network = network);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.stateService.markBlock$.next({});
|
||||||
|
this.tipSubscription.unsubscribe();
|
||||||
|
this.networkChangedSubscription?.unsubscribe();
|
||||||
|
this.queryParamsSubscription?.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBlock(delta: MempoolBlockDelta): void {
|
||||||
|
const blockMined = (this.stateService.latestBlockHeight > this.lastBlockHeightUpdate[delta.block]);
|
||||||
|
if (blockMined) {
|
||||||
|
this.blockGraph.update(this.numBlocks - delta.block - 1, delta.added, delta.removed, delta.changed || [], blockMined ? this.chainDirection : this.poolDirection, blockMined);
|
||||||
|
} else {
|
||||||
|
this.blockGraph.update(this.numBlocks - delta.block - 1, delta.added, delta.removed, delta.changed || [], this.poolDirection);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastBlockHeightUpdate[delta.block] = this.stateService.latestBlockHeight;
|
||||||
|
}
|
||||||
|
}
|
@ -33,6 +33,7 @@ export interface WebsocketResponse {
|
|||||||
'track-scriptpubkeys'?: string[];
|
'track-scriptpubkeys'?: string[];
|
||||||
'track-asset'?: string;
|
'track-asset'?: string;
|
||||||
'track-mempool-block'?: number;
|
'track-mempool-block'?: number;
|
||||||
|
'track-mempool-blocks'?: number[];
|
||||||
'track-rbf'?: string;
|
'track-rbf'?: string;
|
||||||
'track-rbf-summary'?: boolean;
|
'track-rbf-summary'?: boolean;
|
||||||
'track-accelerations'?: boolean;
|
'track-accelerations'?: boolean;
|
||||||
|
@ -29,12 +29,14 @@ export class WebsocketService {
|
|||||||
private isTrackingTx = false;
|
private isTrackingTx = false;
|
||||||
private trackingTxId: string;
|
private trackingTxId: string;
|
||||||
private isTrackingMempoolBlock = false;
|
private isTrackingMempoolBlock = false;
|
||||||
|
private isTrackingMempoolBlocks = false;
|
||||||
private isTrackingRbf: 'all' | 'fullRbf' | false = false;
|
private isTrackingRbf: 'all' | 'fullRbf' | false = false;
|
||||||
private isTrackingRbfSummary = false;
|
private isTrackingRbfSummary = false;
|
||||||
private isTrackingAddress: string | false = false;
|
private isTrackingAddress: string | false = false;
|
||||||
private isTrackingAddresses: string[] | false = false;
|
private isTrackingAddresses: string[] | false = false;
|
||||||
private isTrackingAccelerations: boolean = false;
|
private isTrackingAccelerations: boolean = false;
|
||||||
private trackingMempoolBlock: number;
|
private trackingMempoolBlock: number;
|
||||||
|
private trackingMempoolBlocks: number[];
|
||||||
private stoppingTrackMempoolBlock: any | null = null;
|
private stoppingTrackMempoolBlock: any | null = null;
|
||||||
private latestGitCommit = '';
|
private latestGitCommit = '';
|
||||||
private onlineCheckTimeout: number;
|
private onlineCheckTimeout: number;
|
||||||
@ -122,6 +124,9 @@ export class WebsocketService {
|
|||||||
if (this.isTrackingMempoolBlock) {
|
if (this.isTrackingMempoolBlock) {
|
||||||
this.startTrackMempoolBlock(this.trackingMempoolBlock, true);
|
this.startTrackMempoolBlock(this.trackingMempoolBlock, true);
|
||||||
}
|
}
|
||||||
|
if (this.isTrackingMempoolBlocks) {
|
||||||
|
this.startTrackMempoolBlocks(this.trackingMempoolBlocks);
|
||||||
|
}
|
||||||
if (this.isTrackingRbf) {
|
if (this.isTrackingRbf) {
|
||||||
this.startTrackRbf(this.isTrackingRbf);
|
this.startTrackRbf(this.isTrackingRbf);
|
||||||
}
|
}
|
||||||
@ -218,6 +223,13 @@ export class WebsocketService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startTrackMempoolBlocks(blocks: number[], force: boolean = false): boolean {
|
||||||
|
this.websocketSubject.next({ 'track-mempool-blocks': blocks });
|
||||||
|
this.isTrackingMempoolBlocks = true;
|
||||||
|
this.trackingMempoolBlocks = blocks;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
stopTrackMempoolBlock(): void {
|
stopTrackMempoolBlock(): void {
|
||||||
if (this.stoppingTrackMempoolBlock) {
|
if (this.stoppingTrackMempoolBlock) {
|
||||||
clearTimeout(this.stoppingTrackMempoolBlock);
|
clearTimeout(this.stoppingTrackMempoolBlock);
|
||||||
@ -231,6 +243,11 @@ export class WebsocketService {
|
|||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stopTrackMempoolBlocks(): void {
|
||||||
|
this.websocketSubject.next({ 'track-mempool-blocks': [] });
|
||||||
|
this.isTrackingMempoolBlocks = false;
|
||||||
|
}
|
||||||
|
|
||||||
startTrackRbf(mode: 'all' | 'fullRbf') {
|
startTrackRbf(mode: 'all' | 'fullRbf') {
|
||||||
this.websocketSubject.next({ 'track-rbf': mode });
|
this.websocketSubject.next({ 'track-rbf': mode });
|
||||||
this.isTrackingRbf = mode;
|
this.isTrackingRbf = mode;
|
||||||
@ -433,20 +450,25 @@ export class WebsocketService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (response['projected-block-transactions']) {
|
if (response['projected-block-transactions']) {
|
||||||
if (response['projected-block-transactions'].index == this.trackingMempoolBlock) {
|
if (response['projected-block-transactions'].index != null) {
|
||||||
if (response['projected-block-transactions'].blockTransactions) {
|
const update = response['projected-block-transactions'];
|
||||||
this.stateService.mempoolSequence = response['projected-block-transactions'].sequence;
|
if (update.blockTransactions) {
|
||||||
this.stateService.mempoolBlockUpdate$.next({
|
this.stateService.mempoolBlockUpdate$.next({
|
||||||
block: this.trackingMempoolBlock,
|
block: update.index,
|
||||||
transactions: response['projected-block-transactions'].blockTransactions.map(uncompressTx),
|
transactions: update.blockTransactions.map(uncompressTx),
|
||||||
});
|
});
|
||||||
} else if (response['projected-block-transactions'].delta) {
|
} else if (update.delta) {
|
||||||
if (this.stateService.mempoolSequence && response['projected-block-transactions'].sequence !== this.stateService.mempoolSequence + 1) {
|
this.stateService.mempoolBlockUpdate$.next(uncompressDeltaChange(update.index, update.delta));
|
||||||
this.stateService.mempoolSequence = 0;
|
}
|
||||||
this.startTrackMempoolBlock(this.trackingMempoolBlock, true);
|
} else if (response['projected-block-transactions'].length) {
|
||||||
} else {
|
for (const update of response['projected-block-transactions']) {
|
||||||
this.stateService.mempoolSequence = response['projected-block-transactions'].sequence;
|
if (update.blockTransactions) {
|
||||||
this.stateService.mempoolBlockUpdate$.next(uncompressDeltaChange(this.trackingMempoolBlock, response['projected-block-transactions'].delta));
|
this.stateService.mempoolBlockUpdate$.next({
|
||||||
|
block: update.index,
|
||||||
|
transactions: update.blockTransactions.map(uncompressTx),
|
||||||
|
});
|
||||||
|
} else if (update.delta) {
|
||||||
|
this.stateService.mempoolBlockUpdate$.next(uncompressDeltaChange(update.index, update.delta));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,6 +106,7 @@ import { AccelerationSparklesComponent } from '../components/acceleration/sparkl
|
|||||||
|
|
||||||
import { BlockViewComponent } from '../components/block-view/block-view.component';
|
import { BlockViewComponent } from '../components/block-view/block-view.component';
|
||||||
import { EightBlocksComponent } from '../components/eight-blocks/eight-blocks.component';
|
import { EightBlocksComponent } from '../components/eight-blocks/eight-blocks.component';
|
||||||
|
import { EightMempoolComponent } from '../components/eight-mempool/eight-mempool.component';
|
||||||
import { MempoolBlockViewComponent } from '../components/mempool-block-view/mempool-block-view.component';
|
import { MempoolBlockViewComponent } from '../components/mempool-block-view/mempool-block-view.component';
|
||||||
import { MempoolBlockOverviewComponent } from '../components/mempool-block-overview/mempool-block-overview.component';
|
import { MempoolBlockOverviewComponent } from '../components/mempool-block-overview/mempool-block-overview.component';
|
||||||
import { ClockchainComponent } from '../components/clockchain/clockchain.component';
|
import { ClockchainComponent } from '../components/clockchain/clockchain.component';
|
||||||
@ -156,6 +157,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
|
|||||||
BlockchainComponent,
|
BlockchainComponent,
|
||||||
BlockViewComponent,
|
BlockViewComponent,
|
||||||
EightBlocksComponent,
|
EightBlocksComponent,
|
||||||
|
EightMempoolComponent,
|
||||||
MempoolBlockViewComponent,
|
MempoolBlockViewComponent,
|
||||||
MempoolBlocksComponent,
|
MempoolBlocksComponent,
|
||||||
BlockchainBlocksComponent,
|
BlockchainBlocksComponent,
|
||||||
@ -219,6 +221,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
|
|||||||
BitcoinsatoshisPipe,
|
BitcoinsatoshisPipe,
|
||||||
BlockViewComponent,
|
BlockViewComponent,
|
||||||
EightBlocksComponent,
|
EightBlocksComponent,
|
||||||
|
EightMempoolComponent,
|
||||||
MempoolBlockViewComponent,
|
MempoolBlockViewComponent,
|
||||||
MempoolBlockOverviewComponent,
|
MempoolBlockOverviewComponent,
|
||||||
ClockchainComponent,
|
ClockchainComponent,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user