Merge remote-tracking branch 'origin/master' into mononaut/rust-gbt

This commit is contained in:
junderw 2023-07-05 08:39:48 -07:00
commit 0c7c1dd0a8
No known key found for this signature in database
GPG Key ID: B256185D3A971908
15 changed files with 184 additions and 74 deletions

View File

@ -106,6 +106,7 @@ class Mining {
emptyBlocks: emptyBlocksCount.length > 0 ? emptyBlocksCount[0]['count'] : 0,
slug: poolInfo.slug,
avgMatchRate: poolInfo.avgMatchRate !== null ? Math.round(100 * poolInfo.avgMatchRate) / 100 : null,
avgFeeDelta: poolInfo.avgFeeDelta,
};
poolsStats.push(poolStat);
});

View File

@ -19,6 +19,7 @@ export interface PoolInfo {
blockCount: number;
slug: string;
avgMatchRate: number | null;
avgFeeDelta: number | null;
}
export interface PoolStats extends PoolInfo {

View File

@ -39,7 +39,8 @@ class PoolsRepository {
pools.name AS name,
pools.link AS link,
slug,
AVG(blocks_audits.match_rate) AS avgMatchRate
AVG(blocks_audits.match_rate) AS avgMatchRate,
AVG((CAST(blocks.fees as SIGNED) - CAST(blocks_audits.expected_fees as SIGNED)) / NULLIF(CAST(blocks_audits.expected_fees as SIGNED), 0)) AS avgFeeDelta
FROM blocks
JOIN pools on pools.id = pool_id
LEFT JOIN blocks_audits ON blocks_audits.height = blocks.height

View File

@ -1,9 +1,9 @@
<div class="text-center" class="blockchain-wrapper" [class.time-ltr]="timeLtr" [class.ltr-transition]="ltrTransitionEnabled" #container>
<div class="position-container" [ngClass]="network ? network : ''">
<div class="position-container" [ngClass]="network ? network : ''" [style.--divider-offset]="dividerOffset + 'px'" [style.--mempool-offset]="mempoolOffset + 'px'">
<span>
<div class="blocks-wrapper">
<div class="scroll-spacer" *ngIf="minScrollWidth" [style.left]="minScrollWidth + 'px'"></div>
<app-mempool-blocks [hidden]="pageIndex > 0"></app-mempool-blocks>
<app-mempool-blocks [hidden]="pageIndex > 0" [allBlocks]="scrollableMempool" (widthChange)="onMempoolWidthChange($event)"></app-mempool-blocks>
<app-blockchain-blocks [hidden]="pageIndex > 0"></app-blockchain-blocks>
<ng-container *ngFor="let page of pages; trackBy: trackByPageFn">
<app-blockchain-blocks [static]="true" [offset]="page.offset" [height]="page.height" [count]="blocksPerPage" [loadingTip]="loadingTip" [connected]="connected"></app-blockchain-blocks>

View File

@ -26,43 +26,14 @@
position: absolute;
left: 0;
top: 75px;
transform: translateX(50vw);
--divider-offset: 50vw;
--mempool-offset: 0px;
transform: translateX(calc(var(--divider-offset) + var(--mempool-offset)));
}
.position-container.liquid, .position-container.liquidtestnet {
transform: translateX(420px);
}
@media (min-width: 768px) {
.blockchain-wrapper.time-ltr {
.position-container.liquid, .position-container.liquidtestnet {
transform: translateX(calc(100vw - 420px));
}
}
}
@media (max-width: 767.98px) {
.blockchain-wrapper {
.position-container {
transform: translateX(95vw);
}
.position-container.liquid, .position-container.liquidtestnet {
transform: translateX(50vw);
}
.position-container.loading {
transform: translateX(50vw);
}
}
.blockchain-wrapper.time-ltr {
.position-container {
transform: translateX(5vw);
}
.position-container.liquid, .position-container.liquidtestnet {
transform: translateX(50vw);
}
.position-container.loading {
transform: translateX(50vw);
}
.blockchain-wrapper.time-ltr {
.position-container {
transform: translateX(calc(100vw - var(--divider-offset) - var(--mempool-offset)));
}
}

View File

@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, OnChanges, SimpleChanges } from '@angular/core';
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, Output, EventEmitter, HostListener, ChangeDetectorRef } from '@angular/core';
import { firstValueFrom, Subscription } from 'rxjs';
import { StateService } from '../../services/state.service';
@ -13,43 +13,95 @@ export class BlockchainComponent implements OnInit, OnDestroy {
@Input() pageIndex: number;
@Input() blocksPerPage: number = 8;
@Input() minScrollWidth: number = 0;
@Input() scrollableMempool: boolean = false;
@Output() mempoolOffsetChange: EventEmitter<number> = new EventEmitter();
network: string;
timeLtrSubscription: Subscription;
timeLtr: boolean = this.stateService.timeLtr.value;
ltrTransitionEnabled = false;
flipping = false;
connectionStateSubscription: Subscription;
loadingTip: boolean = true;
connected: boolean = true;
dividerOffset: number = 0;
mempoolOffset: number = 0;
constructor(
public stateService: StateService,
private cd: ChangeDetectorRef,
) {}
ngOnInit() {
ngOnInit(): void {
this.onResize();
this.network = this.stateService.network;
this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => {
this.timeLtr = !!ltr;
});
this.connectionStateSubscription = this.stateService.connectionState$.subscribe(state => {
this.connected = (state === 2);
})
firstValueFrom(this.stateService.chainTip$).then(tip => {
});
firstValueFrom(this.stateService.chainTip$).then(() => {
this.loadingTip = false;
});
}
ngOnDestroy() {
ngOnDestroy(): void {
this.timeLtrSubscription.unsubscribe();
this.connectionStateSubscription.unsubscribe();
}
trackByPageFn(index: number, item: { index: number }) {
trackByPageFn(index: number, item: { index: number }): number {
return item.index;
}
toggleTimeDirection() {
this.ltrTransitionEnabled = true;
this.stateService.timeLtr.next(!this.timeLtr);
toggleTimeDirection(): void {
this.ltrTransitionEnabled = false;
const prevOffset = this.mempoolOffset;
this.mempoolOffset = 0;
this.mempoolOffsetChange.emit(0);
setTimeout(() => {
this.ltrTransitionEnabled = true;
this.flipping = true;
this.stateService.timeLtr.next(!this.timeLtr);
setTimeout(() => {
this.ltrTransitionEnabled = false;
this.flipping = false;
this.mempoolOffset = prevOffset;
this.mempoolOffsetChange.emit(this.mempoolOffset);
}, 1000);
}, 0);
this.cd.markForCheck();
}
onMempoolWidthChange(width): void {
if (this.flipping) {
return;
}
this.mempoolOffset = Math.max(0, width - this.dividerOffset);
this.cd.markForCheck();
setTimeout(() => {
this.mempoolOffsetChange.emit(this.mempoolOffset);
}, 0);
}
@HostListener('window:resize', ['$event'])
onResize(): void {
if (window.innerWidth >= 768) {
if (this.stateService.isLiquid()) {
this.dividerOffset = 420;
} else {
this.dividerOffset = window.innerWidth * 0.5;
}
} else {
if (this.stateService.isLiquid()) {
this.dividerOffset = window.innerWidth * 0.5;
} else {
this.dividerOffset = window.innerWidth * 0.95;
}
}
this.cd.markForCheck();
}
}

View File

@ -1,9 +1,9 @@
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, HostListener, Input, OnChanges, SimpleChanges } from '@angular/core';
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, HostListener, Input, OnChanges, SimpleChanges, Output, EventEmitter } from '@angular/core';
import { Subscription, Observable, fromEvent, merge, of, combineLatest } from 'rxjs';
import { MempoolBlock } from '../../interfaces/websocket.interface';
import { StateService } from '../../services/state.service';
import { Router } from '@angular/router';
import { take, map, switchMap } from 'rxjs/operators';
import { take, map, switchMap, tap } from 'rxjs/operators';
import { feeLevels, mempoolFeeColors } from '../../app.constants';
import { specialBlocks } from '../../app.constants';
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
@ -29,6 +29,9 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
@Input() count: number = null;
@Input() spotlight: number = 0;
@Input() getHref?: (index) => string = (index) => `/mempool-block/${index}`;
@Input() allBlocks: boolean = false;
@Output() widthChange: EventEmitter<number> = new EventEmitter();
specialBlocks = specialBlocks;
mempoolBlocks: MempoolBlock[] = [];
@ -147,7 +150,12 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
this.updateMempoolBlockStyles();
this.calculateTransactionPosition();
return this.mempoolBlocks;
}),
tap(() => {
this.cd.markForCheck();
this.widthChange.emit(this.containerOffset + this.mempoolBlocks.length * this.blockOffset);
})
);
@ -257,7 +265,10 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
reduceEmptyBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] {
const innerWidth = this.stateService.env.BASE_MODULE !== 'liquid' && window.innerWidth <= 767.98 ? window.innerWidth : window.innerWidth / 2;
const blocksAmount = Math.min(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT, Math.floor(innerWidth / (this.blockWidth + this.blockPadding)));
let blocksAmount = this.stateService.env.MEMPOOL_BLOCKS_AMOUNT;
if (!this.allBlocks) {
blocksAmount = Math.min(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT, Math.floor(innerWidth / (this.blockWidth + this.blockPadding)));
}
while (blocks.length < blocksAmount) {
blocks.push({
blockSize: 0,
@ -277,10 +288,10 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
reduceMempoolBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] {
const innerWidth = this.stateService.env.BASE_MODULE !== 'liquid' && window.innerWidth <= 767.98 ? window.innerWidth : window.innerWidth / 2;
let blocksAmount;
let blocksAmount = this.stateService.env.MEMPOOL_BLOCKS_AMOUNT;
if (this.count) {
blocksAmount = 8;
} else {
} else if (!this.allBlocks) {
blocksAmount = Math.min(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT, Math.floor(innerWidth / (this.blockWidth + this.blockPadding)));
}
while (blocks.length > blocksAmount) {

View File

@ -94,7 +94,8 @@
<th class="" i18n="master-page.blocks">Blocks</th>
<th *ngIf="auditAvailable" class="health text-right widget" i18n="latest-blocks.avg_health"
i18n-ngbTooltip="latest-blocks.avg_health" ngbTooltip="Avg Health" placement="bottom" #health [disableTooltip]="!isEllipsisActive(health)">Avg Health</th>
<th class="d-none d-md-table-cell" i18n="mining.empty-blocks">Empty blocks</th>
<th *ngIf="auditAvailable" class="d-none d-sm-table-cell" i18n="mining.fees-per-block">Avg Block Fees</th>
<th class="d-none d-lg-table-cell" i18n="mining.empty-blocks">Empty blocks</th>
</tr>
</thead>
<tbody [attr.data-cy]="'pools-table'" *ngIf="(miningStatsObservable$ | async) as miningStats">
@ -121,7 +122,15 @@
<span class="health-badge badge badge-secondary" i18n="unknown">Unknown</span>
</ng-template>
</td>
<td class="d-none d-md-table-cell">{{ pool.emptyBlocks }} ({{ pool.emptyBlockRatio }}%)</td>
<td *ngIf="auditAvailable" class="d-none d-sm-table-cell">
<span *ngIf="pool.avgFeeDelta != null; else nullFeeDelta" class="difference" [class.positive]="pool.avgFeeDelta >= 0" [class.negative]="pool.avgFeeDelta < 0">
{{ pool.avgFeeDelta > 0 ? '+' : '' }}{{ (pool.avgFeeDelta * 100) | amountShortener: 2 }}%
</span>
<ng-template #nullFeeDelta>
-
</ng-template>
</td>
<td class="d-none d-lg-table-cell">{{ pool.emptyBlocks }} ({{ pool.emptyBlockRatio }}%)</td>
</tr>
<tr style="border-top: 1px solid #555">
<td class="d-none d-md-table-cell"></td>

View File

@ -110,4 +110,15 @@
.disabled {
pointer-events: none;
opacity: 0.5;
}
}
td {
.difference {
&.positive {
color: rgb(66, 183, 71);
}
&.negative {
color: rgb(183, 66, 66);
}
}
}

View File

@ -18,7 +18,7 @@
(dragstart)="onDragStart($event)"
(scroll)="onScroll($event)"
>
<app-blockchain [pageIndex]="pageIndex" [pages]="pages" [blocksPerPage]="blocksPerPage" [minScrollWidth]="minScrollWidth"></app-blockchain>
<app-blockchain [pageIndex]="pageIndex" [pages]="pages" [blocksPerPage]="blocksPerPage" [minScrollWidth]="minScrollWidth" [scrollableMempool]="true" (mempoolOffsetChange)="onMempoolOffsetChange($event)"></app-blockchain>
</div>
<div class="reset-scroll" [class.hidden]="pageIndex === 0" (click)="resetScroll()">
<fa-icon [icon]="['fas', 'circle-left']" [fixedWidth]="true"></fa-icon>

View File

@ -1,6 +1,6 @@
import { Component, ElementRef, HostListener, OnInit, OnDestroy, ViewChild, Input } from '@angular/core';
import { Subscription } from 'rxjs';
import { StateService } from '../../services/state.service';
import { MarkBlockState, StateService } from '../../services/state.service';
import { specialBlocks } from '../../app.constants';
@Component({
@ -24,6 +24,7 @@ export class StartComponent implements OnInit, OnDestroy {
chainTipSubscription: Subscription;
chainTip: number = -1;
tipIsSet: boolean = false;
lastMark: MarkBlockState;
markBlockSubscription: Subscription;
blockCounterSubscription: Subscription;
@ViewChild('blockchainContainer') blockchainContainer: ElementRef;
@ -40,10 +41,11 @@ export class StartComponent implements OnInit, OnDestroy {
minScrollWidth: number;
pageIndex: number = 0;
pages: any[] = [];
pendingMark: number | void = null;
pendingMark: number | null = null;
lastUpdate: number = 0;
lastMouseX: number;
velocity: number = 0;
mempoolOffset: number = 0;
constructor(
private stateService: StateService,
@ -70,19 +72,40 @@ export class StartComponent implements OnInit, OnDestroy {
this.chainTip = height;
this.tipIsSet = true;
this.updatePages();
if (this.pendingMark != null) {
this.scrollToBlock(this.pendingMark);
this.pendingMark = null;
}
this.applyPendingMarkArrow();
});
this.markBlockSubscription = this.stateService.markBlock$.subscribe((mark) => {
let blockHeight;
let newMark = true;
if (mark?.blockHeight != null) {
if (this.lastMark?.blockHeight === mark.blockHeight) {
newMark = false;
}
blockHeight = mark.blockHeight;
} else if (mark?.mempoolBlockIndex != null) {
if (this.lastMark?.mempoolBlockIndex === mark.mempoolBlockIndex || (mark.txid && this.lastMark?.txid === mark.txid)) {
newMark = false;
}
blockHeight = -1 - mark.mempoolBlockIndex;
} else if (mark?.mempoolPosition?.block != null) {
if (this.lastMark?.txid === mark.txid) {
newMark = false;
}
blockHeight = -1 - mark.mempoolPosition.block;
}
this.lastMark = mark;
if (blockHeight != null) {
if (this.tipIsSet) {
if (!this.blockInViewport(mark.blockHeight)) {
this.scrollToBlock(mark.blockHeight);
let scrollToHeight = blockHeight;
if (blockHeight < 0) {
scrollToHeight = this.chainTip - blockHeight;
}
} else {
this.pendingMark = mark.blockHeight;
if (newMark && !this.blockInViewport(scrollToHeight)) {
this.scrollToBlock(scrollToHeight);
}
}
if (!this.tipIsSet || (blockHeight < 0 && !this.mempoolOffset)) {
this.pendingMark = blockHeight;
}
}
});
@ -117,6 +140,24 @@ export class StartComponent implements OnInit, OnDestroy {
});
}
onMempoolOffsetChange(offset): void {
const delta = offset - this.mempoolOffset;
this.addConvertedScrollOffset(delta);
this.mempoolOffset = offset;
this.applyPendingMarkArrow();
}
applyPendingMarkArrow(): void {
if (this.pendingMark != null) {
if (this.pendingMark < 0) {
this.scrollToBlock(this.chainTip - this.pendingMark);
} else {
this.scrollToBlock(this.pendingMark);
}
this.pendingMark = null;
}
}
@HostListener('window:resize', ['$event'])
onResize(): void {
this.isMobile = window.innerWidth <= 767.98;
@ -350,7 +391,7 @@ export class StartComponent implements OnInit, OnDestroy {
resetScroll(): void {
this.scrollToBlock(this.chainTip);
this.blockchainContainer.nativeElement.scrollLeft = 0;
this.setScrollLeft(0);
}
getPageIndexOf(height: number): number {
@ -368,9 +409,17 @@ export class StartComponent implements OnInit, OnDestroy {
getConvertedScrollOffset(): number {
if (this.timeLtr) {
return -this.blockchainContainer?.nativeElement?.scrollLeft || 0;
return -(this.blockchainContainer?.nativeElement?.scrollLeft || 0) - this.mempoolOffset;
} else {
return this.blockchainContainer?.nativeElement?.scrollLeft || 0;
return (this.blockchainContainer?.nativeElement?.scrollLeft || 0) - this.mempoolOffset;
}
}
setScrollLeft(offset: number): void {
if (this.timeLtr) {
this.blockchainContainer.nativeElement.scrollLeft = offset - this.mempoolOffset;
} else {
this.blockchainContainer.nativeElement.scrollLeft = offset + this.mempoolOffset;
}
}
@ -388,7 +437,7 @@ export class StartComponent implements OnInit, OnDestroy {
ngOnDestroy() {
if (this.blockchainContainer?.nativeElement) {
// clean up scroll position to prevent caching wrong scroll in Firefox
this.blockchainContainer.nativeElement.scrollLeft = 0;
this.setScrollLeft(0);
}
this.timeLtrSubscription.unsubscribe();
this.chainTipSubscription.unsubscribe();

View File

@ -241,6 +241,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.mempoolPosition = txPosition.position;
if (this.tx && !this.tx.status.confirmed) {
this.stateService.markBlock$.next({
txid: txPosition.txid,
mempoolPosition: this.mempoolPosition
});
this.txInBlockIndex = this.mempoolPosition.block;
@ -360,6 +361,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
} else {
if (tx.cpfpChecked) {
this.stateService.markBlock$.next({
txid: tx.txid,
txFeePerVSize: tx.effectiveFeePerVsize,
mempoolPosition: this.mempoolPosition,
});

View File

@ -91,6 +91,7 @@ export interface SinglePoolStats {
logo: string;
slug: string;
avgMatchRate: number;
avgFeeDelta: number;
}
export interface PoolsStats {
blockCount: number;

View File

@ -8,8 +8,9 @@ import { isPlatformBrowser } from '@angular/common';
import { map, scan, shareReplay, tap } from 'rxjs/operators';
import { StorageService } from './storage.service';
interface MarkBlockState {
export interface MarkBlockState {
blockHeight?: number;
txid?: string;
mempoolBlockIndex?: number;
txFeePerVSize?: number;
mempoolPosition?: MempoolPosition;

View File

@ -361,10 +361,10 @@ BITCOIN_ELECTRS_REPO_NAME=electrs
BITCOIN_ELECTRS_REPO_BRANCH=mempool
BITCOIN_ELECTRS_LATEST_RELEASE=mempool
ELEMENTS_ELECTRS_REPO_URL=https://github.com/blockstream/electrs
ELEMENTS_ELECTRS_REPO_URL=https://github.com/mempool/electrs
ELEMENTS_ELECTRS_REPO_NAME=electrs
ELEMENTS_ELECTRS_REPO_BRANCH=new-index
ELEMENTS_ELECTRS_LATEST_RELEASE=new-index
ELEMENTS_ELECTRS_REPO_BRANCH=mempool
ELEMENTS_ELECTRS_LATEST_RELEASE=mempool
LIQUID_ASSET_REGISTRY_DB_URL=https://github.com/blockstream/asset_registry_db
LIQUID_ASSET_REGISTRY_DB_NAME=asset_registry_db