Merge branch 'mempool:master' into cost-to-spend-utxos

This commit is contained in:
Jonathan 2026-06-29 00:52:26 -06:00 committed by GitHub
commit 877f3cd5b2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 227 additions and 48 deletions

View file

@ -37,7 +37,7 @@ import { calculateGoodBlockCpfp } from './cpfp';
import blockProcessor, { BlockProcessingResult, detectTemplateAlgorithm, saveCpfpDataToCpfpSummary } from './block-processor';
import mempool from './mempool';
import CpfpRepository from '../repositories/CpfpRepository';
import { parseDATUMTemplateCreator } from '../utils/bitcoin-script';
import { parseDATUMTemplateCreator, parseDMNDTemplateCreator } from '../utils/bitcoin-script';
import database from '../database';
import { getBlockFirstSeenFromLogs, getOldestLogTimestampFromLogs, scanLogsForBlocksFirstSeen } from '../utils/file-read';
@ -359,6 +359,8 @@ class Blocks {
if (extras.pool.name === 'OCEAN') {
extras.pool.minerNames = parseDATUMTemplateCreator(extras.coinbaseRaw);
} else if (extras.pool.name === 'DMND') {
extras.pool.minerNames = parseDMNDTemplateCreator(extras.coinbaseRaw);
}
}

View file

@ -14,7 +14,7 @@ import chainTips from '../api/chain-tips';
import blocks from '../api/blocks';
import BlocksAuditsRepository from './BlocksAuditsRepository';
import transactionUtils from '../api/transaction-utils';
import { parseDATUMTemplateCreator } from '../utils/bitcoin-script';
import { parseDATUMTemplateCreator, parseDMNDTemplateCreator } from '../utils/bitcoin-script';
import poolsUpdater from '../tasks/pools-updater';
interface DatabaseBlock {
@ -1374,6 +1374,8 @@ class BlocksRepository {
if (extras.pool.name === 'OCEAN') {
extras.pool.minerNames = parseDATUMTemplateCreator(extras.coinbaseRaw);
} else if (extras.pool.name === 'DMND') {
extras.pool.minerNames = parseDMNDTemplateCreator(extras.coinbaseRaw);
}
blk.extras = <BlockExtension>extras;

View file

@ -224,4 +224,27 @@ export function parseDATUMTemplateCreator(coinbaseRaw: string): string[] | null
tagString = tagString.replace('\x00', '');
return tagString.split('\x0f').map((name) => name.replace(/[^a-zA-Z0-9 ]/g, ''));
}
}
/** Extracts miner names from a DMND coinbase transaction */
export function parseDMNDTemplateCreator(coinbaseRaw: string): string[] {
try {
if (!coinbaseRaw || coinbaseRaw.length % 2 !== 0 || !/^[0-9a-fA-F]+$/.test(coinbaseRaw)) {
return [];
}
const bytes = Buffer.from(coinbaseRaw, 'hex');
const blockHeightLength = bytes[0];
const tagDelimiterIndex = 1 + blockHeightLength;
if (bytes.length <= tagDelimiterIndex || bytes[tagDelimiterIndex] !== 0x00) {
return [];
}
const tagStart = tagDelimiterIndex + 1;
const tagEnd = bytes.indexOf(0x00, tagStart);
const tags = bytes.subarray(tagStart, tagEnd === -1 ? undefined : tagEnd);
return tags.toString('utf8').split('/').slice(1, -1);
} catch {
return [];
}
}

View file

@ -53,15 +53,23 @@
<td i18n="block.miner">Miner</td>
<td *ngIf="stateService.env.MINING_DASHBOARD">
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge" style="color: #FFF;padding:0;">
<span class="miner-name" *ngIf="block.extras.pool.minerNames?.length > 1 && block.extras.pool.minerNames[1] != ''">
@if (block.extras.pool.minerNames[1].length > 16) {
{{ block.extras.pool.minerNames[1].slice(0, 15) }}…
} @else {
{{ block.extras.pool.minerNames[1] }}
}
</span>
<img class="pool-logo" [src]="'/resources/mining-pools/' + block.extras.pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'">
{{ block.extras.pool.name }}
<ng-container *ngIf="block.extras.pool.minerSlug && block.extras.pool.minerName; else minerName">
<img class="pool-logo" [src]="'/resources/mining-pools/' + block.extras.pool.minerSlug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.minerName + ' miner'">
<span class="miner-name">{{ block.extras.pool.minerName }}</span>
<img class="pool-logo" [src]="'/resources/mining-pools/' + block.extras.pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'">
{{ block.extras.pool.name }}
</ng-container>
<ng-template #minerName>
<span class="miner-name" *ngIf="block.extras.pool.minerNames?.length > 1 && block.extras.pool.minerNames[1] != ''">
@if (block.extras.pool.minerNames[1].length > 16) {
{{ block.extras.pool.minerNames[1].slice(0, 15) }}…
} @else {
{{ block.extras.pool.minerNames[1] }}
}
</span>
<img class="pool-logo" [src]="'/resources/mining-pools/' + block.extras.pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'">
{{ block.extras.pool.name }}
</ng-template>
</a>
</td>
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">

View file

@ -188,14 +188,21 @@
<td i18n="block.miner">Miner</td>
<td *ngIf="stateService.env.MINING_DASHBOARD">
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge" style="color: #FFF;padding:0;">
<span class="miner-name" *ngIf="block.extras.pool.minerNames?.length > 1 && block.extras.pool.minerNames[1] != ''">
@if (block.extras.pool.minerNames[1].length > 16) {
{{ block.extras.pool.minerNames[1].slice(0, 15) }}…
} @else {
{{ block.extras.pool.minerNames[1] }}
}
</span>
<app-mining-pool [slug]="block.extras.pool.slug" [name]="block.extras.pool.name"></app-mining-pool>
<ng-container *ngIf="block.extras.pool.minerSlug && block.extras.pool.minerName; else minerName">
<app-mining-pool [slug]="block.extras.pool.minerSlug" [name]="block.extras.pool.minerName" [showName]="false"></app-mining-pool>
<span class="miner-name">{{ block.extras.pool.minerName }}</span>
<app-mining-pool [slug]="block.extras.pool.slug" [name]="block.extras.pool.name"></app-mining-pool>
</ng-container>
<ng-template #minerName>
<span class="miner-name" *ngIf="block.extras.pool.minerNames?.length > 1 && block.extras.pool.minerNames[1] != ''">
@if (block.extras.pool.minerNames[1].length > 16) {
{{ block.extras.pool.minerNames[1].slice(0, 15) }}…
} @else {
{{ block.extras.pool.minerNames[1] }}
}
</span>
<app-mining-pool [slug]="block.extras.pool.slug" [name]="block.extras.pool.name"></app-mining-pool>
</ng-template>
</a>
</td>
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">

View file

@ -79,8 +79,14 @@
<div class="animated" *ngIf="block.extras?.pool != undefined && showPools">
<a [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-pool'" class="badge" [class.miner-name]="block.extras.pool.minerNames?.length > 1 && block.extras.pool.minerNames[1] != ''" [routerLink]="[('/mining/pool/' + block.extras.pool.slug) | relativeUrl]">
<ng-container *ngIf="block.extras.pool.minerNames?.length > 1 && block.extras.pool.minerNames[1] != ''; else centralisedPool">
<app-mining-pool [slug]="block.extras.pool.slug" [name]="block.extras.pool.minerNames[1]" [logoStyle]="'filter: grayscale(100%) brightness(1.5);'" [showName]="false"></app-mining-pool>
{{ block.extras.pool.minerNames[1] }}
<ng-container *ngIf="block.extras.pool.minerSlug && block.extras.pool.minerName; else minerName">
<app-mining-pool [slug]="block.extras.pool.minerSlug" [name]="block.extras.pool.minerName" [showName]="false"></app-mining-pool>
{{ block.extras.pool.minerName }}
</ng-container>
<ng-template #minerName>
<app-mining-pool [slug]="block.extras.pool.slug" [name]="block.extras.pool.minerNames[1]" [logoStyle]="'filter: grayscale(100%) brightness(1.5);'" [showName]="false"></app-mining-pool>
{{ block.extras.pool.minerNames[1] }}
</ng-template>
</ng-container>
<ng-template #centralisedPool>
<app-mining-pool [slug]="block.extras.pool.slug" [name]="block.extras.pool.name"></app-mining-pool>

View file

@ -57,8 +57,14 @@
<div class="animated" *ngIf="chainTip.stale?.extras?.pool != undefined">
<a class="badge" [class.miner-name]="chainTip.stale.extras.pool.minerNames?.length > 1 && chainTip.stale.extras.pool.minerNames[1] != ''" [routerLink]="[('/mining/pool/' + chainTip.stale.extras.pool.slug) | relativeUrl]">
<ng-container *ngIf="chainTip.stale.extras.pool.minerNames?.length > 1 && chainTip.stale.extras.pool.minerNames[1] != ''; else staleBlockCentralisedPool">
<app-mining-pool [slug]="chainTip.stale.extras.pool.slug" [name]="chainTip.stale.extras.pool.name" [logoStyle]="'filter: grayscale(100%) brightness(1.5);'" [showName]="false"></app-mining-pool>
{{ chainTip.stale.extras.pool.minerNames[1] }}
<ng-container *ngIf="chainTip.stale.extras.pool.minerSlug && chainTip.stale.extras.pool.minerName; else minerName">
<app-mining-pool [slug]="chainTip.stale.extras.pool.minerSlug" [name]="chainTip.stale.extras.pool.minerName" [showName]="false"></app-mining-pool>
{{ chainTip.stale.extras.pool.minerName }}
</ng-container>
<ng-template #minerName>
<app-mining-pool [slug]="chainTip.stale.extras.pool.slug" [name]="chainTip.stale.extras.pool.name" [logoStyle]="'filter: grayscale(100%) brightness(1.5);'" [showName]="false"></app-mining-pool>
{{ chainTip.stale.extras.pool.minerNames[1] }}
</ng-template>
</ng-container>
<ng-template #staleBlockCentralisedPool>
<app-mining-pool [slug]="chainTip.stale.extras.pool.slug" [name]="chainTip.stale.extras.pool.name"></app-mining-pool>
@ -103,8 +109,14 @@
<div class="animated" *ngIf="chainTip.canonical?.extras?.pool != undefined">
<a class="badge" [class.miner-name]="chainTip.canonical.extras.pool.minerNames?.length > 1 && chainTip.canonical.extras.pool.minerNames[1] != ''" [routerLink]="[('/mining/pool/' + chainTip.canonical.extras.pool.slug) | relativeUrl]">
<ng-container *ngIf="chainTip.canonical.extras.pool.minerNames?.length > 1 && chainTip.canonical.extras.pool.minerNames[1] != ''; else canonicalBlockCentralisedPool">
<app-mining-pool [slug]="chainTip.canonical.extras.pool.slug" [name]="chainTip.canonical.extras.pool.name" [logoStyle]="'filter: grayscale(100%) brightness(1.5);'" [showName]="false"></app-mining-pool>
{{ chainTip.canonical.extras.pool.minerNames[1] }}
<ng-container *ngIf="chainTip.canonical.extras.pool.minerSlug && chainTip.canonical.extras.pool.minerName; else minerName">
<app-mining-pool [slug]="chainTip.canonical.extras.pool.minerSlug" [name]="chainTip.canonical.extras.pool.minerName" [showName]="false"></app-mining-pool>
{{ chainTip.canonical.extras.pool.minerName }}
</ng-container>
<ng-template #minerName>
<app-mining-pool [slug]="chainTip.canonical.extras.pool.slug" [name]="chainTip.canonical.extras.pool.name" [logoStyle]="'filter: grayscale(100%) brightness(1.5);'" [showName]="false"></app-mining-pool>
{{ chainTip.canonical.extras.pool.minerNames[1] }}
</ng-template>
</ng-container>
<ng-template #canonicalBlockCentralisedPool>
<app-mining-pool [slug]="chainTip.canonical.extras.pool.slug" [name]="chainTip.canonical.extras.pool.name"></app-mining-pool>

View file

@ -333,14 +333,21 @@
@if (pool) {
<td class="wrap-cell">
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, pool.slug]" class="badge" style="color: var(--fg);padding:0;">
<span class="miner-name" *ngIf="pool.minerNames?.length > 1 && pool.minerNames[1] != ''">
@if (pool.minerNames[1].length > 16) {
{{ pool.minerNames[1].slice(0, 15) }}…
} @else {
{{ pool.minerNames[1] }}
}
</span>
<app-mining-pool [slug]="pool.slug" [name]="pool.name"></app-mining-pool>
<ng-container *ngIf="pool.minerSlug && pool.minerName; else minerName">
<app-mining-pool [slug]="pool.minerSlug" [name]="pool.minerName" [showName]="false"></app-mining-pool>
<span class="miner-name">{{ pool.minerName }}</span>
<app-mining-pool [slug]="pool.slug" [name]="pool.name"></app-mining-pool>
</ng-container>
<ng-template #minerName>
<span class="miner-name" *ngIf="pool.minerNames?.length > 1 && pool.minerNames[1] != ''">
@if (pool.minerNames[1].length > 16) {
{{ pool.minerNames[1].slice(0, 15) }}…
} @else {
{{ pool.minerNames[1] }}
}
</span>
<app-mining-pool [slug]="pool.slug" [name]="pool.name"></app-mining-pool>
</ng-template>
</a>
</td>
} @else {

View file

@ -92,6 +92,10 @@ export class TransactionDetailsComponent implements OnChanges {
}
calculateAcceleratorSavings(block: BlockExtended): void {
if (this.network !== '') {
this.acceleratorSavingsSats = 0;
return;
}
const minBlockRate = block?.extras?.feeRange?.[0];
if (minBlockRate !== undefined) {
const vsize = this.tx.weight / 4;
@ -100,7 +104,7 @@ export class TransactionDetailsComponent implements OnChanges {
}
get showAcceleratorSavingsMsg(): boolean {
return this.acceleratorSavingsSats > 0 && this.cpfpInfo !== null && this.cpfpInfo !== undefined && !this.hasCpfp;
return this.network === '' && this.acceleratorSavingsSats > 0 && this.cpfpInfo !== null && this.cpfpInfo !== undefined && !this.hasCpfp;
}
onAccelerateClicked(): void {

View file

@ -45,6 +45,8 @@ export interface Pool {
id: number;
name: string;
slug: string;
minerName?: string;
minerSlug?: string;
minerNames: string[] | null;
}

View file

@ -330,7 +330,7 @@
</ng-template>
<ng-template type="why-block-timestamps-dont-always-increase">
<p>Block validation rules do not strictly require that a block's timestamp be more recent than the timestamp of the block preceding it. Without a central authority, it's impossible to know what the exact correct time is. Instead, the Bitcoin protocol requires that a block's timestamp meet certain requirements. One of those requirements is that a block's timestamp cannot be older than the median timestamp of the 12 blocks that came before it. See more details <a href="https://en.bitcoin.it/wiki/Block_timestamp" target="_blank">here</a>.</p><p>As a result, timestamps are only accurate to within an hour or so, which sometimes results in blocks with timestamps that appear out of order.</p>
<p>Block validation rules do not strictly require that a block's timestamp be more recent than the timestamp of the block preceding it. Without a central authority, it's impossible to know what the exact correct time is. Instead, the Bitcoin protocol requires that a block's timestamp meet certain requirements. One of those requirements is that <a href="https://en.bitcoin.it/wiki/Block_timestamp" target="_blank">a block's timestamp cannot be older than the median timestamp of the 12 blocks that came before it</a>.</p><p>As a result, timestamps are only accurate to within an hour or so, which sometimes results in blocks with timestamps that appear out of order.</p>
</ng-template>
<ng-template type="why-dont-fee-ranges-match">

View file

@ -224,6 +224,8 @@ export interface BlockExtension {
id: number;
name: string;
slug: string;
minerName?: string;
minerSlug?: string;
minerNames: string[] | null;
}
orphans?: {

View file

@ -2,13 +2,14 @@ import { Injectable } from '@angular/core';
import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http';
import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators, PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights,
RbfTree, BlockAudit, CurrentPegs, AuditStatus, FederationAddress, FederationUtxo, RecentPeg, PegsVolume, AccelerationInfo, TestMempoolAcceptResult, WalletAddress, Treasury, SubmitPackageResult, ChainTip, StaleTip } from '@interfaces/node-api.interface';
import { BehaviorSubject, Observable, catchError, filter, map, of, shareReplay, take, tap } from 'rxjs';
import { BehaviorSubject, Observable, catchError, filter, map, of, shareReplay, switchMap, take, tap } from 'rxjs';
import { StateService } from '@app/services/state.service';
import { Transaction } from '@interfaces/electrs.interface';
import { Conversion } from '@app/services/price.service';
import { StorageService } from '@app/services/storage.service';
import { WebsocketResponse } from '@interfaces/websocket.interface';
import { TxAuditStatus } from '@components/transaction/transaction.component';
import { MinersService } from '@app/services/miners.service';
@Injectable({
providedIn: 'root'
@ -24,7 +25,8 @@ export class ApiService {
constructor(
private httpClient: HttpClient,
private stateService: StateService,
private storageService: StorageService
private storageService: StorageService,
private minersService: MinersService,
) {
this.apiBaseUrl = ''; // use relative URL by default
if (!stateService.isBrowser) { // except when inside AU SSR process
@ -173,7 +175,9 @@ export class ApiService {
}
getStaleTips$(height?: number): Observable<StaleTip[]> {
return this.httpClient.get<StaleTip[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/stale-tips' + (height !== undefined ? `/${height}` : ``));
return this.httpClient.get<StaleTip[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/stale-tips' + (height !== undefined ? `/${height}` : ``)).pipe(
switchMap((staleTips) => this.minersService.applyStaleTipsMinerDetails$(staleTips))
);
}
liquidPegs$(): Observable<CurrentPegs> {
@ -308,6 +312,8 @@ export class ApiService {
return this.httpClient.get<BlockExtended[]>(
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${slug}/blocks` +
(fromHeight !== undefined ? `/${fromHeight}` : '')
).pipe(
switchMap((blocks) => this.minersService.applyBlocksMinerDetails$(blocks))
);
}
@ -315,11 +321,15 @@ export class ApiService {
return this.httpClient.get<BlockExtended[]>(
this.apiBaseUrl + this.apiBasePath + `/api/v1/blocks` +
(from !== undefined ? `/${from}` : ``)
).pipe(
switchMap((blocks) => this.minersService.applyBlocksMinerDetails$(blocks))
);
}
getBlock$(hash: string): Observable<BlockExtended> {
return this.httpClient.get<BlockExtended>(this.apiBaseUrl + this.apiBasePath + '/api/v1/block/' + hash);
return this.httpClient.get<BlockExtended>(this.apiBaseUrl + this.apiBasePath + '/api/v1/block/' + hash).pipe(
switchMap((block) => this.minersService.applyBlockMinerDetails$(block))
);
}
getBlockDataFromTimestamp$(timestamp: number): Observable<any> {

View file

@ -0,0 +1,74 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { catchError, map, shareReplay } from 'rxjs/operators';
import { BlockExtended, StaleTip } from '@interfaces/node-api.interface';
import { StateService } from '@app/services/state.service';
interface Miner {
name: string;
slug: string;
tags: string[];
}
@Injectable({
providedIn: 'root'
})
export class MinersService {
private apiBaseUrl = '';
private miners$: Observable<Miner[]>;
constructor(
private httpClient: HttpClient,
private stateService: StateService,
) {
if (!stateService.isBrowser) {
this.apiBaseUrl = this.stateService.env.NGINX_PROTOCOL + '://' + this.stateService.env.NGINX_HOSTNAME + ':' + this.stateService.env.NGINX_PORT;
}
this.miners$ = this.httpClient.get<Miner[]>(this.apiBaseUrl + '/resources/miners.json').pipe(
catchError(() => of([])),
shareReplay(1)
);
}
public applyBlockMinerDetails$(block: BlockExtended): Observable<BlockExtended> {
if (!block?.extras?.pool?.minerNames?.length) {
return of(block);
}
return this.miners$.pipe(map((miners) => this.applyBlockMinerDetails(block, miners)));
}
public applyBlocksMinerDetails$(blocks: BlockExtended[]): Observable<BlockExtended[]> {
if (!blocks?.some((block) => block?.extras?.pool?.minerNames?.length)) {
return of(blocks);
}
return this.miners$.pipe(map((miners) => blocks.map((block) => this.applyBlockMinerDetails(block, miners))));
}
public applyStaleTipsMinerDetails$(staleTips: StaleTip[]): Observable<StaleTip[]> {
if (!staleTips?.some((staleTip) => staleTip.stale?.extras?.pool?.minerNames?.length || staleTip.canonical?.extras?.pool?.minerNames?.length)) {
return of(staleTips);
}
return this.miners$.pipe(
map((miners) => staleTips.map((staleTip) => {
this.applyBlockMinerDetails(staleTip.stale, miners);
this.applyBlockMinerDetails(staleTip.canonical, miners);
return staleTip;
}))
);
}
private applyBlockMinerDetails(block: BlockExtended, miners: Miner[]): BlockExtended {
const minerNames = block?.extras?.pool?.minerNames;
if (!minerNames?.length) {
return block;
}
const miner = miners.find((minerEntry) => minerEntry.tags.some((tag) => minerNames.includes(tag)));
if (miner?.name && miner.slug) {
block.extras.pool.minerName = miner.name;
block.extras.pool.minerSlug = miner.slug;
}
return block;
}
}

View file

@ -3,12 +3,13 @@ import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
import { WebsocketResponse } from '@interfaces/websocket.interface';
import { StateService } from '@app/services/state.service';
import { Transaction } from '@interfaces/electrs.interface';
import { firstValueFrom, Subscription } from 'rxjs';
import { firstValueFrom, of, Subscription } from 'rxjs';
import { ApiService } from '@app/services/api.service';
import { take } from 'rxjs/operators';
import { catchError, take } from 'rxjs/operators';
import { TransferState, makeStateKey } from '@angular/core';
import { CacheService } from '@app/services/cache.service';
import { uncompressDeltaChange, uncompressTx } from '@app/shared/common.utils';
import { MinersService } from '@app/services/miners.service';
const OFFLINE_RETRY_AFTER_MS = 2000;
const OFFLINE_PING_CHECK_AFTER_MS = 30000;
@ -51,6 +52,7 @@ export class WebsocketService {
private apiService: ApiService,
private transferState: TransferState,
private cacheService: CacheService,
private minersService: MinersService,
) {
if (!this.stateService.isBrowser) {
// @ts-ignore
@ -356,9 +358,14 @@ export class WebsocketService {
if (response.blocks && response.blocks.length) {
const blocks = response.blocks;
this.stateService.resetBlocks(blocks);
const maxHeight = blocks.reduce((max, block) => Math.max(max, block.height), this.stateService.latestBlockHeight);
this.stateService.updateChainTip(maxHeight);
this.minersService.applyBlocksMinerDetails$(blocks).pipe(
take(1),
catchError(() => of(blocks))
).subscribe((mappedBlocks) => {
this.stateService.resetBlocks(mappedBlocks);
const maxHeight = mappedBlocks.reduce((max, block) => Math.max(max, block.height), this.stateService.latestBlockHeight);
this.stateService.updateChainTip(maxHeight);
});
}
if (response.tx) {
@ -371,9 +378,14 @@ export class WebsocketService {
if (response.block) {
if (response.block.height === this.stateService.latestBlockHeight + 1) {
this.stateService.updateChainTip(response.block.height);
this.stateService.addBlock(response.block);
this.stateService.txConfirmed$.next([response.txConfirmed, response.block]);
this.minersService.applyBlockMinerDetails$(response.block).pipe(
take(1),
catchError(() => of(response.block))
).subscribe((block) => {
this.stateService.updateChainTip(block.height);
this.stateService.addBlock(block);
this.stateService.txConfirmed$.next([response.txConfirmed, block]);
});
} else if (response.block.height > this.stateService.latestBlockHeight + 1) {
reinitBlocks = true;
}

View file

@ -0,0 +1,8 @@
[
{
"name": "GoMining",
"tags": ["GM", "GoMining"],
"link": "https://gomining.com/",
"slug": "gomining"
}
]