mirror of
https://github.com/mempool/mempool.git
synced 2026-08-13 12:33:11 +02:00
parse & display hasher
This commit is contained in:
parent
721a647564
commit
5a08039867
7 changed files with 87 additions and 15 deletions
|
|
@ -8,7 +8,7 @@ import { SeoService } from '@app/services/seo.service';
|
|||
import { OpenGraphService } from '@app/services/opengraph.service';
|
||||
import { BlockExtended, TransactionStripped } from '@interfaces/node-api.interface';
|
||||
import { ApiService } from '@app/services/api.service';
|
||||
import { seoDescriptionNetwork } from '@app/shared/common.utils';
|
||||
import { seoDescriptionNetwork, parseMinerName } from '@app/shared/common.utils';
|
||||
import { BlockOverviewGraphComponent } from '@components/block-overview-graph/block-overview-graph.component';
|
||||
import { ServicesApiServices } from '@app/services/services-api.service';
|
||||
|
||||
|
|
@ -99,6 +99,7 @@ export class BlockPreviewComponent implements OnInit, OnDestroy {
|
|||
filter((block: BlockExtended | void) => block != null),
|
||||
tap((block: BlockExtended) => {
|
||||
this.block = block;
|
||||
this.block.minerName = parseMinerName(block);
|
||||
this.blockHeight = block.height;
|
||||
|
||||
this.seoService.setTitle($localize`:@@block.component.browser-title:Block ${block.height}:BLOCK_HEIGHT:: ${block.id}:BLOCK_ID:`);
|
||||
|
|
|
|||
|
|
@ -182,11 +182,11 @@
|
|||
<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) }}…
|
||||
<span class="miner-name" *ngIf="block.minerName">
|
||||
@if (block.minerName.length > 16) {
|
||||
{{ block.minerName.slice(0, 15) }}…
|
||||
} @else {
|
||||
{{ block.extras.pool.minerNames[1] }}
|
||||
{{ block.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'">
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Component, OnInit, OnDestroy, ViewChildren, QueryList, ChangeDetectorRe
|
|||
import { Location } from '@angular/common';
|
||||
import { ActivatedRoute, ParamMap, Params, Router } from '@angular/router';
|
||||
import { ElectrsApiService } from '@app/services/electrs-api.service';
|
||||
import { switchMap, tap, throttleTime, catchError, map, shareReplay, startWith, filter, take } from 'rxjs/operators';
|
||||
import { switchMap, tap, throttleTime, catchError, map, shareReplay, startWith, filter } from 'rxjs/operators';
|
||||
import { Observable, of, Subscription, asyncScheduler, EMPTY, combineLatest, forkJoin } from 'rxjs';
|
||||
import { StateService } from '@app/services/state.service';
|
||||
import { SeoService } from '@app/services/seo.service';
|
||||
|
|
@ -12,7 +12,7 @@ import { Acceleration, BlockAudit, BlockExtended, TransactionStripped } from '@i
|
|||
import { ApiService } from '@app/services/api.service';
|
||||
import { BlockOverviewGraphComponent } from '@components/block-overview-graph/block-overview-graph.component';
|
||||
import { detectWebGL } from '@app/shared/graphs.utils';
|
||||
import { seoDescriptionNetwork } from '@app/shared/common.utils';
|
||||
import { seoDescriptionNetwork, parseMinerName } from '@app/shared/common.utils';
|
||||
import { PriceService, Price } from '@app/services/price.service';
|
||||
import { CacheService } from '@app/services/cache.service';
|
||||
import { ServicesApiServices } from '@app/services/services-api.service';
|
||||
|
|
@ -144,6 +144,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||
for (const block of blocks) {
|
||||
if (block.id === this.blockHash) {
|
||||
this.block = block;
|
||||
this.block.minerName = parseMinerName(block);
|
||||
if (block.extras) {
|
||||
block.extras.minFee = this.getMinBlockFee(block);
|
||||
block.extras.maxFee = this.getMaxBlockFee(block);
|
||||
|
|
@ -250,6 +251,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
this.updateAuditAvailableFromBlockHeight(block.height);
|
||||
this.block = block;
|
||||
this.block.minerName = parseMinerName(block);
|
||||
if (block.extras) {
|
||||
block.extras.minFee = this.getMinBlockFee(block);
|
||||
block.extras.maxFee = this.getMaxBlockFee(block);
|
||||
|
|
|
|||
|
|
@ -291,11 +291,11 @@
|
|||
@if (pool) {
|
||||
<td class="wrap-cell">
|
||||
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, pool.slug]" class="badge" style="color: #FFF;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) }}…
|
||||
<span class="miner-name" *ngIf="pool.minerName">
|
||||
@if (pool.minerName.length > 16) {
|
||||
{{ pool.minerName.slice(0, 15) }}…
|
||||
} @else {
|
||||
{{ pool.minerNames[1] }}
|
||||
{{ pool.minerName }}
|
||||
}
|
||||
</span>
|
||||
<img class="pool-logo" [src]="'/resources/mining-pools/' + pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + pool.name + ' mining pool'">
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import { AudioService } from '@app/services/audio.service';
|
|||
import { ApiService } from '@app/services/api.service';
|
||||
import { SeoService } from '@app/services/seo.service';
|
||||
import { StorageService } from '@app/services/storage.service';
|
||||
import { seoDescriptionNetwork } from '@app/shared/common.utils';
|
||||
import { parseMinerName, seoDescriptionNetwork } from '@app/shared/common.utils';
|
||||
import { getTransactionFlags, getUnacceleratedFeeRate } from '@app/shared/transaction.utils';
|
||||
import { Filter, TransactionFlags, toFilters } from '@app/shared/filters.utils';
|
||||
import { BlockExtended, CpfpInfo, RbfTree, MempoolPosition, DifficultyAdjustment, Acceleration, AccelerationPosition } from '@interfaces/node-api.interface';
|
||||
|
|
@ -43,6 +43,7 @@ export interface Pool {
|
|||
name: string;
|
||||
slug: string;
|
||||
minerNames: string[] | null;
|
||||
minerName?: string;
|
||||
}
|
||||
|
||||
export interface TxAuditStatus {
|
||||
|
|
@ -384,8 +385,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}),
|
||||
switchMap(({ hash, height }) => {
|
||||
const foundBlock = this.cacheService.getCachedBlock(height) || null;
|
||||
return foundBlock ? of(foundBlock.extras.pool) : this.apiService.getBlock$(hash).pipe(
|
||||
map(block => block.extras.pool),
|
||||
return foundBlock ? of({ ...foundBlock.extras.pool, minerName: parseMinerName(foundBlock) }) : this.apiService.getBlock$(hash).pipe(
|
||||
map(block => ({ ...block.extras.pool, minerName: parseMinerName(block) })),
|
||||
retry({ count: 3, delay: 2000 }),
|
||||
catchError(() => of(null))
|
||||
);
|
||||
|
|
@ -719,6 +720,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
block_time: block.timestamp,
|
||||
};
|
||||
this.pool = block.extras.pool;
|
||||
this.pool.minerName = parseMinerName(block);
|
||||
this.txChanged$.next(true);
|
||||
this.stateService.markBlock$.next({ blockHeight: block.height });
|
||||
if (this.tx.acceleration || (this.accelerationInfo && ['accelerating', 'completed_provisional', 'completed'].includes(this.accelerationInfo.status))) {
|
||||
|
|
|
|||
|
|
@ -213,6 +213,7 @@ export interface BlockExtension {
|
|||
|
||||
export interface BlockExtended extends Block {
|
||||
extras?: BlockExtension;
|
||||
minerName?: string;
|
||||
}
|
||||
|
||||
export interface BlockAudit extends BlockExtended {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { MempoolBlockDelta, MempoolBlockDeltaCompressed, MempoolDeltaChange, TransactionCompressed } from "../interfaces/websocket.interface";
|
||||
import { TransactionStripped } from "@interfaces/node-api.interface";
|
||||
import { BlockExtended, TransactionStripped } from "@interfaces/node-api.interface";
|
||||
import { AmountShortenerPipe } from "@app/shared/pipes/amount-shortener.pipe";
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
const amountShortenerPipe = new AmountShortenerPipe();
|
||||
|
|
@ -280,3 +280,69 @@ export function md5(inputString): string {
|
|||
}
|
||||
return rh(a)+rh(b)+rh(c)+rh(d);
|
||||
}
|
||||
|
||||
export function parseMinerName(block: BlockExtended): string | null {
|
||||
if (block.extras?.pool?.minerNames?.[1]) {
|
||||
return block.extras.pool.minerNames[1];
|
||||
}
|
||||
|
||||
if (!block.extras?.coinbaseRaw) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tag = extractMinerTag(block.extras.coinbaseRaw);
|
||||
return tag ? parseMinerTag(tag) : null;
|
||||
}
|
||||
|
||||
function extractMinerTag(hex: string): string | null {
|
||||
const bytes = hexToBytes(hex);
|
||||
let pos = 0;
|
||||
|
||||
// Skip block height
|
||||
const heightPushSize = bytes[pos];
|
||||
pos += heightPushSize + 1;
|
||||
if (pos >= bytes.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get miner tag bytes
|
||||
const tagPushSize = bytes[pos];
|
||||
pos++;
|
||||
|
||||
// Extract and decode tag bytes
|
||||
const tagBytes = bytes.slice(pos, pos + tagPushSize);
|
||||
return byteToUtf8(tagBytes);
|
||||
}
|
||||
|
||||
function hexToBytes(hex: string): number[] {
|
||||
const bytes: number[] = [];
|
||||
for (let i = 0; i < hex.length; i += 2) {
|
||||
bytes.push(parseInt(hex.slice(i, i + 2), 16));
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
function byteToUtf8(bytes: number[]): string {
|
||||
try {
|
||||
return new TextDecoder('utf-8', { fatal: true }).decode(new Uint8Array(bytes));
|
||||
} catch {
|
||||
return bytes
|
||||
.filter(byte => byte >= 32 && byte <= 126)
|
||||
.map(byte => String.fromCharCode(byte))
|
||||
.join('');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a miner tag string in the format "/<miner-tag>/(<hasher-tag>/)?"
|
||||
* Returns the hasher tag if present, removing any "Mined by " prefix
|
||||
*/
|
||||
function parseMinerTag(tag: string): string | null {
|
||||
const match = tag.match(/\/[^/]+\/([^/]+\/)/);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hasherTag = match[1].slice(0, -1).replace(/^Mined by /, '');
|
||||
return hasherTag;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue