Merge branch 'mempool:master' into master
This commit is contained in:
commit
32490bfdb7
@ -373,7 +373,7 @@ class NodesApi {
|
|||||||
|
|
||||||
public async $searchNodeByPublicKeyOrAlias(search: string) {
|
public async $searchNodeByPublicKeyOrAlias(search: string) {
|
||||||
try {
|
try {
|
||||||
const publicKeySearch = search.replace('%', '') + '%';
|
const publicKeySearch = search.replace(/[^a-zA-Z0-9]/g, '') + '%';
|
||||||
const aliasSearch = search
|
const aliasSearch = search
|
||||||
.replace(/[-_.]/g, ' ') // Replace all -_. characters with empty space. Eg: "ln.nicehash" becomes "ln nicehash".
|
.replace(/[-_.]/g, ' ') // Replace all -_. characters with empty space. Eg: "ln.nicehash" becomes "ln nicehash".
|
||||||
.replace(/[^a-zA-Z0-9 ]/g, '') // Remove all special characters and keep just A to Z, 0 to 9.
|
.replace(/[^a-zA-Z0-9 ]/g, '') // Remove all special characters and keep just A to Z, 0 to 9.
|
||||||
|
@ -186,6 +186,12 @@ class Mempool {
|
|||||||
loadingIndicators.setProgress('mempool', progress);
|
loadingIndicators.setProgress('mempool', progress);
|
||||||
loggerTimer = new Date().getTime() / 1000;
|
loggerTimer = new Date().getTime() / 1000;
|
||||||
}
|
}
|
||||||
|
// Break and restart mempool loop if we spend too much time processing
|
||||||
|
// new transactions that may lead to falling behind on block height
|
||||||
|
if (this.inSync && (new Date().getTime()) - start > 10_000) {
|
||||||
|
logger.debug('Breaking mempool loop because the 10s time limit exceeded.');
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset esplora 404 counter and log a warning if needed
|
// Reset esplora 404 counter and log a warning if needed
|
||||||
|
@ -537,7 +537,7 @@ describe('Mainnet', () => {
|
|||||||
cy.get('.container-xl > :nth-child(3)').invoke('css', 'width').should('equal', alertWidth);
|
cy.get('.container-xl > :nth-child(3)').invoke('css', 'width').should('equal', alertWidth);
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.get('.btn-danger').then(getRectangle).then((rectA) => {
|
cy.get('.btn-warning').then(getRectangle).then((rectA) => {
|
||||||
cy.get('.alert').then(getRectangle).then((rectB) => {
|
cy.get('.alert').then(getRectangle).then((rectB) => {
|
||||||
expect(areOverlapping(rectA, rectB), 'Confirmations box and RBF alert are overlapping').to.be.false;
|
expect(areOverlapping(rectA, rectB), 'Confirmations box and RBF alert are overlapping').to.be.false;
|
||||||
});
|
});
|
||||||
@ -582,7 +582,7 @@ describe('Mainnet', () => {
|
|||||||
cy.get(alertLocator).invoke('css', 'width').should('equal', firstWidth);
|
cy.get(alertLocator).invoke('css', 'width').should('equal', firstWidth);
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.get('.btn-danger').then(getRectangle).then((rectA) => {
|
cy.get('.btn-warning').then(getRectangle).then((rectA) => {
|
||||||
cy.get('.alert').then(getRectangle).then((rectB) => {
|
cy.get('.alert').then(getRectangle).then((rectB) => {
|
||||||
expect(areOverlapping(rectA, rectB), 'Confirmations box and RBF alert are overlapping').to.be.false;
|
expect(areOverlapping(rectA, rectB), 'Confirmations box and RBF alert are overlapping').to.be.false;
|
||||||
});
|
});
|
||||||
|
@ -44,21 +44,16 @@
|
|||||||
</td>
|
</td>
|
||||||
<td *ngIf="auditAvailable" class="health text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
<td *ngIf="auditAvailable" class="health text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
||||||
<a
|
<a
|
||||||
|
*ngIf="block?.extras?.matchRate != null; else nullHealth"
|
||||||
class="health-badge badge"
|
class="health-badge badge"
|
||||||
[class.badge-success]="auditScores[block.id] >= 99"
|
[class.badge-success]="block.extras.matchRate >= 99"
|
||||||
[class.badge-warning]="auditScores[block.id] >= 75 && auditScores[block.id] < 99"
|
[class.badge-warning]="block.extras.matchRate >= 75 && block.extras.matchRate < 99"
|
||||||
[class.badge-danger]="auditScores[block.id] < 75"
|
[class.badge-danger]="block.extras.matchRate < 75"
|
||||||
[routerLink]="auditScores[block.id] != null ? ['/block/' | relativeUrl, block.id] : null"
|
[routerLink]="block.extras.matchRate != null ? ['/block/' | relativeUrl, block.id] : null"
|
||||||
[state]="{ data: { block: block } }"
|
[state]="{ data: { block: block } }"
|
||||||
*ngIf="auditScores[block.id] != null; else nullHealth"
|
>{{ block.extras.matchRate }}%</a>
|
||||||
>{{ auditScores[block.id] }}%</a>
|
|
||||||
<ng-template #nullHealth>
|
<ng-template #nullHealth>
|
||||||
<ng-container *ngIf="!loadingScores; else loadingHealth">
|
<span class="health-badge badge badge-secondary" i18n="unknown">Unknown</span>
|
||||||
<span class="health-badge badge badge-secondary" i18n="unknown">Unknown</span>
|
|
||||||
</ng-container>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template #loadingHealth>
|
|
||||||
<span class="skeleton-loader" style="max-width: 60px"></span>
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</td>
|
</td>
|
||||||
<td *ngIf="indexingAvailable" class="reward text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
<td *ngIf="indexingAvailable" class="reward text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, ChangeDetectorRef } from '@angular/core';
|
import { Component, OnInit, ChangeDetectionStrategy, Input, ChangeDetectorRef } from '@angular/core';
|
||||||
import { BehaviorSubject, combineLatest, concat, Observable, timer, EMPTY, Subscription, of } from 'rxjs';
|
import { BehaviorSubject, combineLatest, Observable, timer, of } from 'rxjs';
|
||||||
import { catchError, delayWhen, map, retryWhen, scan, skip, switchMap, tap } from 'rxjs/operators';
|
import { delayWhen, map, retryWhen, scan, switchMap, tap } from 'rxjs/operators';
|
||||||
import { BlockExtended } from '../../interfaces/node-api.interface';
|
import { BlockExtended } from '../../interfaces/node-api.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';
|
||||||
@ -12,19 +12,14 @@ import { WebsocketService } from '../../services/websocket.service';
|
|||||||
styleUrls: ['./blocks-list.component.scss'],
|
styleUrls: ['./blocks-list.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class BlocksList implements OnInit, OnDestroy {
|
export class BlocksList implements OnInit {
|
||||||
@Input() widget: boolean = false;
|
@Input() widget: boolean = false;
|
||||||
|
|
||||||
blocks$: Observable<BlockExtended[]> = undefined;
|
blocks$: Observable<BlockExtended[]> = undefined;
|
||||||
auditScores: { [hash: string]: number | void } = {};
|
|
||||||
|
|
||||||
auditScoreSubscription: Subscription;
|
|
||||||
latestScoreSubscription: Subscription;
|
|
||||||
|
|
||||||
indexingAvailable = false;
|
indexingAvailable = false;
|
||||||
auditAvailable = false;
|
auditAvailable = false;
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
loadingScores = true;
|
|
||||||
fromBlockHeight = undefined;
|
fromBlockHeight = undefined;
|
||||||
paginationMaxSize: number;
|
paginationMaxSize: number;
|
||||||
page = 1;
|
page = 1;
|
||||||
@ -66,7 +61,7 @@ export class BlocksList implements OnInit, OnDestroy {
|
|||||||
this.blocksCount = blocks[0].height + 1;
|
this.blocksCount = blocks[0].height + 1;
|
||||||
}
|
}
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
this.lastBlockHeight = Math.max(...blocks.map(o => o.height))
|
this.lastBlockHeight = Math.max(...blocks.map(o => o.height));
|
||||||
}),
|
}),
|
||||||
map(blocks => {
|
map(blocks => {
|
||||||
if (this.indexingAvailable) {
|
if (this.indexingAvailable) {
|
||||||
@ -82,7 +77,7 @@ export class BlocksList implements OnInit, OnDestroy {
|
|||||||
return blocks;
|
return blocks;
|
||||||
}),
|
}),
|
||||||
retryWhen(errors => errors.pipe(delayWhen(() => timer(10000))))
|
retryWhen(errors => errors.pipe(delayWhen(() => timer(10000))))
|
||||||
)
|
);
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
this.stateService.blocks$
|
this.stateService.blocks$
|
||||||
@ -121,68 +116,17 @@ export class BlocksList implements OnInit, OnDestroy {
|
|||||||
return of(blocks);
|
return of(blocks);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.indexingAvailable && this.auditAvailable) {
|
|
||||||
this.auditScoreSubscription = this.fromHeightSubject.pipe(
|
|
||||||
switchMap((fromBlockHeight) => {
|
|
||||||
this.loadingScores = true;
|
|
||||||
return this.apiService.getBlockAuditScores$(this.page === 1 ? undefined : fromBlockHeight)
|
|
||||||
.pipe(
|
|
||||||
catchError(() => {
|
|
||||||
return EMPTY;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})
|
|
||||||
).subscribe((scores) => {
|
|
||||||
Object.values(scores).forEach(score => {
|
|
||||||
this.auditScores[score.hash] = score?.matchRate != null ? score.matchRate : null;
|
|
||||||
});
|
|
||||||
this.loadingScores = false;
|
|
||||||
this.cd.markForCheck();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.latestScoreSubscription = this.stateService.blocks$.pipe(
|
|
||||||
switchMap((block) => {
|
|
||||||
if (block[0]?.extras?.matchRate != null) {
|
|
||||||
return of({
|
|
||||||
hash: block[0].id,
|
|
||||||
matchRate: block[0]?.extras?.matchRate,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (block[0]?.id && this.auditScores[block[0].id] === undefined) {
|
|
||||||
return this.apiService.getBlockAuditScore$(block[0].id)
|
|
||||||
.pipe(
|
|
||||||
catchError(() => {
|
|
||||||
return EMPTY;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return EMPTY;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
).subscribe((score) => {
|
|
||||||
if (score && score.hash) {
|
|
||||||
this.auditScores[score.hash] = score?.matchRate != null ? score.matchRate : null;
|
|
||||||
this.cd.markForCheck();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
pageChange(page: number): void {
|
||||||
this.auditScoreSubscription?.unsubscribe();
|
|
||||||
this.latestScoreSubscription?.unsubscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
pageChange(page: number) {
|
|
||||||
this.fromHeightSubject.next((this.blocksCount - 1) - (page - 1) * 15);
|
this.fromHeightSubject.next((this.blocksCount - 1) - (page - 1) * 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
trackByBlock(index: number, block: BlockExtended) {
|
trackByBlock(index: number, block: BlockExtended): number {
|
||||||
return block.height;
|
return block.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
isEllipsisActive(e) {
|
isEllipsisActive(e): boolean {
|
||||||
return (e.offsetWidth < e.scrollWidth);
|
return (e.offsetWidth < e.scrollWidth);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -18,7 +18,12 @@
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="container-buttons">
|
<div class="container-buttons">
|
||||||
<app-confirmations [chainTip]="latestBlock?.height" [height]="tx?.status?.block_height" [replaced]="replaced"></app-confirmations>
|
<app-confirmations
|
||||||
|
[chainTip]="latestBlock?.height"
|
||||||
|
[height]="tx?.status?.block_height"
|
||||||
|
[replaced]="replaced"
|
||||||
|
[removed]="this.rbfInfo?.mined && !this.tx?.status?.confirmed"
|
||||||
|
></app-confirmations>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,8 +6,11 @@
|
|||||||
</button>
|
</button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template [ngIf]="!hideUnconfirmed && !confirmations && replaced">
|
<ng-template [ngIf]="!hideUnconfirmed && !confirmations && replaced">
|
||||||
<button type="button" class="btn btn-sm btn-danger {{buttonClass}}" i18n="transaction.unconfirmed|Transaction unconfirmed state">Replaced</button>
|
<button type="button" class="btn btn-sm btn-warning {{buttonClass}}" i18n="transaction.replaced|Transaction replaced state">Replaced</button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template [ngIf]="!hideUnconfirmed && !confirmations && !replaced">
|
<ng-template [ngIf]="!hideUnconfirmed && !confirmations && !replaced && removed">
|
||||||
|
<button type="button" class="btn btn-sm btn-warning {{buttonClass}}" i18n="transaction.audit.removed|Transaction removed state">Removed</button>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template [ngIf]="!hideUnconfirmed && !confirmations && !replaced && !removed">
|
||||||
<button type="button" class="btn btn-sm btn-danger {{buttonClass}}" i18n="transaction.unconfirmed|Transaction unconfirmed state">Unconfirmed</button>
|
<button type="button" class="btn btn-sm btn-danger {{buttonClass}}" i18n="transaction.unconfirmed|Transaction unconfirmed state">Unconfirmed</button>
|
||||||
</ng-template>
|
</ng-template>
|
@ -11,6 +11,7 @@ export class ConfirmationsComponent implements OnChanges {
|
|||||||
@Input() chainTip: number;
|
@Input() chainTip: number;
|
||||||
@Input() height: number;
|
@Input() height: number;
|
||||||
@Input() replaced: boolean = false;
|
@Input() replaced: boolean = false;
|
||||||
|
@Input() removed: boolean = false;
|
||||||
@Input() hideUnconfirmed: boolean = false;
|
@Input() hideUnconfirmed: boolean = false;
|
||||||
@Input() buttonClass: string = '';
|
@Input() buttonClass: string = '';
|
||||||
|
|
||||||
|
@ -97,10 +97,10 @@ location @mempool-api-v1-cache-normal {
|
|||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
proxy_cache api;
|
proxy_cache api;
|
||||||
proxy_cache_valid 200 10s;
|
proxy_cache_valid 200 2s;
|
||||||
proxy_redirect off;
|
proxy_redirect off;
|
||||||
|
|
||||||
expires 10s;
|
expires 2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
location @mempool-api-v1-cache-disabled {
|
location @mempool-api-v1-cache-disabled {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user