diff --git a/backend/src/api/explorer/nodes.api.ts b/backend/src/api/explorer/nodes.api.ts
index d86ecf665..d429299e1 100644
--- a/backend/src/api/explorer/nodes.api.ts
+++ b/backend/src/api/explorer/nodes.api.ts
@@ -373,7 +373,7 @@ class NodesApi {
public async $searchNodeByPublicKeyOrAlias(search: string) {
try {
- const publicKeySearch = search.replace('%', '') + '%';
+ const publicKeySearch = search.replace(/[^a-zA-Z0-9]/g, '') + '%';
const aliasSearch = search
.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.
diff --git a/backend/src/api/mempool.ts b/backend/src/api/mempool.ts
index e3543f4fc..70635237f 100644
--- a/backend/src/api/mempool.ts
+++ b/backend/src/api/mempool.ts
@@ -186,6 +186,12 @@ class Mempool {
loadingIndicators.setProgress('mempool', progress);
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
diff --git a/frontend/cypress/e2e/mainnet/mainnet.spec.ts b/frontend/cypress/e2e/mainnet/mainnet.spec.ts
index da9e00b9f..b12b93cf1 100644
--- a/frontend/cypress/e2e/mainnet/mainnet.spec.ts
+++ b/frontend/cypress/e2e/mainnet/mainnet.spec.ts
@@ -537,7 +537,7 @@ describe('Mainnet', () => {
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) => {
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('.btn-danger').then(getRectangle).then((rectA) => {
+ cy.get('.btn-warning').then(getRectangle).then((rectA) => {
cy.get('.alert').then(getRectangle).then((rectB) => {
expect(areOverlapping(rectA, rectB), 'Confirmations box and RBF alert are overlapping').to.be.false;
});
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 2ee611bc6..39fbb95e0 100644
--- a/frontend/src/app/components/blocks-list/blocks-list.component.html
+++ b/frontend/src/app/components/blocks-list/blocks-list.component.html
@@ -44,21 +44,16 @@
= 99"
- [class.badge-warning]="auditScores[block.id] >= 75 && auditScores[block.id] < 99"
- [class.badge-danger]="auditScores[block.id] < 75"
- [routerLink]="auditScores[block.id] != null ? ['/block/' | relativeUrl, block.id] : null"
+ [class.badge-success]="block.extras.matchRate >= 99"
+ [class.badge-warning]="block.extras.matchRate >= 75 && block.extras.matchRate < 99"
+ [class.badge-danger]="block.extras.matchRate < 75"
+ [routerLink]="block.extras.matchRate != null ? ['/block/' | relativeUrl, block.id] : null"
[state]="{ data: { block: block } }"
- *ngIf="auditScores[block.id] != null; else nullHealth"
- >{{ auditScores[block.id] }}%
+ >{{ block.extras.matchRate }}%
-
- Unknown
-
-
-
-
+ Unknown
|
diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.ts b/frontend/src/app/components/blocks-list/blocks-list.component.ts
index 8b4aa38e7..324807628 100644
--- a/frontend/src/app/components/blocks-list/blocks-list.component.ts
+++ b/frontend/src/app/components/blocks-list/blocks-list.component.ts
@@ -1,6 +1,6 @@
-import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, ChangeDetectorRef } from '@angular/core';
-import { BehaviorSubject, combineLatest, concat, Observable, timer, EMPTY, Subscription, of } from 'rxjs';
-import { catchError, delayWhen, map, retryWhen, scan, skip, switchMap, tap } from 'rxjs/operators';
+import { Component, OnInit, ChangeDetectionStrategy, Input, ChangeDetectorRef } from '@angular/core';
+import { BehaviorSubject, combineLatest, Observable, timer, of } from 'rxjs';
+import { delayWhen, map, retryWhen, scan, switchMap, tap } from 'rxjs/operators';
import { BlockExtended } from '../../interfaces/node-api.interface';
import { ApiService } from '../../services/api.service';
import { StateService } from '../../services/state.service';
@@ -12,19 +12,14 @@ import { WebsocketService } from '../../services/websocket.service';
styleUrls: ['./blocks-list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class BlocksList implements OnInit, OnDestroy {
+export class BlocksList implements OnInit {
@Input() widget: boolean = false;
blocks$: Observable = undefined;
- auditScores: { [hash: string]: number | void } = {};
-
- auditScoreSubscription: Subscription;
- latestScoreSubscription: Subscription;
indexingAvailable = false;
auditAvailable = false;
isLoading = true;
- loadingScores = true;
fromBlockHeight = undefined;
paginationMaxSize: number;
page = 1;
@@ -66,7 +61,7 @@ export class BlocksList implements OnInit, OnDestroy {
this.blocksCount = blocks[0].height + 1;
}
this.isLoading = false;
- this.lastBlockHeight = Math.max(...blocks.map(o => o.height))
+ this.lastBlockHeight = Math.max(...blocks.map(o => o.height));
}),
map(blocks => {
if (this.indexingAvailable) {
@@ -82,7 +77,7 @@ export class BlocksList implements OnInit, OnDestroy {
return blocks;
}),
retryWhen(errors => errors.pipe(delayWhen(() => timer(10000))))
- )
+ );
})
),
this.stateService.blocks$
@@ -121,68 +116,17 @@ export class BlocksList implements OnInit, OnDestroy {
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 {
- this.auditScoreSubscription?.unsubscribe();
- this.latestScoreSubscription?.unsubscribe();
- }
-
- pageChange(page: number) {
+ pageChange(page: number): void {
this.fromHeightSubject.next((this.blocksCount - 1) - (page - 1) * 15);
}
- trackByBlock(index: number, block: BlockExtended) {
+ trackByBlock(index: number, block: BlockExtended): number {
return block.height;
}
- isEllipsisActive(e) {
+ isEllipsisActive(e): boolean {
return (e.offsetWidth < e.scrollWidth);
}
}
\ No newline at end of file
diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html
index 64eeeaecd..a5a44ee90 100644
--- a/frontend/src/app/components/transaction/transaction.component.html
+++ b/frontend/src/app/components/transaction/transaction.component.html
@@ -18,7 +18,12 @@
diff --git a/frontend/src/app/shared/components/confirmations/confirmations.component.html b/frontend/src/app/shared/components/confirmations/confirmations.component.html
index 1d7138c7c..e785ce4dd 100644
--- a/frontend/src/app/shared/components/confirmations/confirmations.component.html
+++ b/frontend/src/app/shared/components/confirmations/confirmations.component.html
@@ -6,8 +6,11 @@
-
+
-
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/app/shared/components/confirmations/confirmations.component.ts b/frontend/src/app/shared/components/confirmations/confirmations.component.ts
index 8d14128e5..624c58278 100644
--- a/frontend/src/app/shared/components/confirmations/confirmations.component.ts
+++ b/frontend/src/app/shared/components/confirmations/confirmations.component.ts
@@ -11,6 +11,7 @@ export class ConfirmationsComponent implements OnChanges {
@Input() chainTip: number;
@Input() height: number;
@Input() replaced: boolean = false;
+ @Input() removed: boolean = false;
@Input() hideUnconfirmed: boolean = false;
@Input() buttonClass: string = '';
diff --git a/production/nginx/location-api.conf b/production/nginx/location-api.conf
index 638e1911c..2b2b85411 100644
--- a/production/nginx/location-api.conf
+++ b/production/nginx/location-api.conf
@@ -97,10 +97,10 @@ location @mempool-api-v1-cache-normal {
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache api;
- proxy_cache_valid 200 10s;
+ proxy_cache_valid 200 2s;
proxy_redirect off;
- expires 10s;
+ expires 2s;
}
location @mempool-api-v1-cache-disabled {
|