Tx pool stats
Some checks are pending
Ubuntu 22.04 - x64 DEB Build / build (20) (push) Waiting to run
MacOS Build / build (20) (push) Waiting to run
MacOS 12 - x64 DMG Build / build (20) (push) Waiting to run
Ubuntu 22.04 - AppImage Build / build (20) (push) Waiting to run
Ubuntu 24.04 - x64 DEB Build / build (20) (push) Waiting to run
Windows Installer Build / build (20) (push) Waiting to run
Windows Portable Build / build (20) (push) Waiting to run

This commit is contained in:
everoddandeven 2024-10-31 18:57:47 +01:00
parent b2d4ff9230
commit de782eea51
8 changed files with 254 additions and 61 deletions

View file

@ -1,6 +1,6 @@
import { EventEmitter, Injectable, NgZone } from '@angular/core';
import { DaemonService } from './daemon.service';
import { BlockCount, BlockHeader, Chain, Connection, CoreIsBusyError, DaemonInfo, MinerData, MiningStatus, NetStats, NetStatsHistory, PeerInfo, ProcessStats, PublicNode, SyncInfo, TimeUtils, TxBacklogEntry, TxPool } from '../../../../common';
import { BlockCount, BlockHeader, Chain, Connection, CoreIsBusyError, DaemonInfo, MinerData, MiningStatus, NetStats, NetStatsHistory, PeerInfo, ProcessStats, PublicNode, SyncInfo, TimeUtils, TxBacklogEntry, TxPool, TxPoolStats } from '../../../../common';
import { ElectronService } from '../electron/electron.service';
@Injectable({
@ -64,6 +64,9 @@ export class DaemonDataService {
private _txPoolBacklog: TxBacklogEntry[] = [];
private _gettingTxPoolBackLog: boolean = false;
private _txPoolStats?: TxPoolStats;
private _gettingTxPoolStats: boolean = false;
public readonly syncStart: EventEmitter<{ first: boolean }> = new EventEmitter<{ first: boolean }>();
public readonly syncEnd: EventEmitter<void> = new EventEmitter<void>();
public readonly syncError: EventEmitter<Error> = new EventEmitter<Error>();
@ -242,6 +245,14 @@ export class DaemonDataService {
return this._gettingTxPoolBackLog;
}
public get txPoolStats(): TxPoolStats | undefined {
return this._txPoolStats;
}
public get gettingTxPoolStats(): boolean {
return this._gettingTxPoolStats;
}
public setRefreshTimeout(ms: number = 5000): void {
this.refreshTimeoutMs = ms;
}
@ -481,6 +492,15 @@ export class DaemonDataService {
this._transactionPool = undefined;
}
if (this._daemonInfo.synchronized) {
this._gettingTxPoolStats = true;
this._txPoolStats = await this.daemonService.getTransactionPoolStats();
this._gettingTxPoolStats = false;
}
else {
this._txPoolStats = undefined;
}
this._gettingConnections = true;
this._connections = await this.daemonService.getConnections();
this._gettingConnections = false;
@ -500,6 +520,7 @@ export class DaemonDataService {
this._gettingTransactionPool = false;
this._gettingConnections = false;
this._gettingPeerList = false;
this._gettingTxPoolStats = false;
this.syncError.emit(<Error>error);

View file

@ -43,7 +43,8 @@ import {
SetLogHashRateRequest,
SetLogCategoriesRequest,
GetTransactionPoolRequest,
GetPeerListRequest
GetPeerListRequest,
GetTransactionPoolStatsRequest
} from '../../../../common/request';
import { BlockTemplate } from '../../../../common/BlockTemplate';
import { GeneratedBlocks } from '../../../../common/GeneratedBlocks';
@ -78,7 +79,7 @@ import { TxInfo } from '../../../../common/TxInfo';
import { DaemonSettings } from '../../../../common/DaemonSettings';
import { MethodNotFoundError } from '../../../../common/error/MethodNotFoundError';
import { openDB, IDBPDatabase } from "idb"
import { PeerInfo, ProcessStats, TimeUtils, TxPool } from '../../../../common';
import { PeerInfo, ProcessStats, TimeUtils, TxPool, TxPoolStats } from '../../../../common';
import { MoneroInstallerService } from '../monero-installer/monero-installer.service';
@Injectable({
@ -1090,6 +1091,16 @@ export class DaemonService {
return TxPool.parse(response);
}
public async getTransactionPoolStats(): Promise<TxPoolStats> {
const response = await this.callRpc(new GetTransactionPoolStatsRequest());
if (typeof response.status == 'string' && response.status != 'OK') {
throw new Error(response.status);
}
return TxPoolStats.parse(response);
}
public async getTransactionPoolHashes(): Promise<string[]> {
const response = await this.callRpc(new GetTransactionPoolHashesRequest());
const txHashes: string[] = response.tx_hashes;

View file

@ -17,59 +17,111 @@
<h4 class="mb-3">Information about valid transactions seen by the node but not yet mined into a block, as well as spent key image information for the txpool in the node's memory</h4>
<br>
<h6 class="mb-3">Transactions</h6>
<div class="m-3">
<table
id="transactionsTable"
data-toggle="transactionsTable"
data-toolbar="#toolbar"
data-search="true"
data-pagination="true"
data-show-columns="true"
data-show-columns-search="true"
>
<thead>
<tr>
<th data-field="idHash">ID Hash</th>
<th data-field="relayed">Relayed</th>
<th data-field="blobSize">Blob Size</th>
<th data-field="doNotRelay">Do Not Relay</th>
<th data-field="doubleSpendSeen">Double Spend Seen</th>
<th data-field="fee">Fee</th>
<th data-field="keptByBlock">Kept By Block</th>
<th data-field="lastFailedHeight">Last Failed Height</th>
<th data-field="lastFailedIdHash">Last Failed Id Hash</th>
<th data-field="lastRelayedTime">Last Relayed Time</th>
<th data-field="maxUsedBlockHeight">Max Used Block Height</th>
<th data-field="maxUsedBlockIdHash">Max Used Block Id Hash</th>
<th data-field="receiveTime">Receive Time</th>
<th data-field="txBlob">Tx Blob</th>
</tr>
</thead>
</table>
<div class="card p-1">
<div class="card-header bg-primary text-white d-flex">
<h4>Statistics</h4>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<h6>Bytes</h6>
<ul class="list-group">
<li class="list-group-item"><strong>Bytes Max:</strong> {{ txPoolStats.bytesMax }} bytes</li>
<li class="list-group-item"><strong>Bytes Med:</strong> {{ txPoolStats.bytesMed }} units</li>
<li class="list-group-item"><strong>Bytes Min:</strong> {{ txPoolStats.bytesMin }}</li>
<li class="list-group-item"><strong>Bytes Total:</strong> {{ txPoolStats.bytesTotal }}</li>
</ul>
</div>
<div class="col-md-6">
<h6>Histo</h6>
<ul class="list-group">
<li class="list-group-item"><strong>Histo Txs:</strong> {{ txPoolStats.histo.txs }}</li>
<li class="list-group-item"><strong>Histo Bytes:</strong> {{ txPoolStats.histo.bytes }}</li>
<li class="list-group-item"><strong>Histo 98% (the time 98% of txes are "younger" than):</strong> {{ txPoolStats.histo98pc }}</li>
</ul>
</div>
</div>
<div class="row mt-4">
<div class="col-md-12">
<h6>Info</h6>
<ul class="list-group">
<li class="list-group-item"><strong>Transactions in pool for more than 10 minutes:</strong> {{ txPoolStats.num10m }}</li>
<li class="list-group-item"><strong>Double spend transactions:</strong> {{ txPoolStats.numDoubleSpends }}</li>
<li class="list-group-item"><strong>Failing transactions:</strong> {{ txPoolStats.numFailing }}</li>
<li class="list-group-item"><strong>Non-relayed transactions:</strong> {{ txPoolStats.numNotRelayed }}</li>
<li class="list-group-item"><strong>Oldest transaction in the pool:</strong> {{ txPoolStats.oldest }}</li>
<li class="list-group-item"><strong>Total number of transactions:</strong> {{ txPoolStats.txsTotal }}</li>
</ul>
</div>
</div>
<div class="row mt-4">
<div class="col-md-12">
<h6>Transactions in Pool</h6>
<li class="list-group-item">
<div class="m-3">
<table
id="transactionsTable"
data-toggle="transactionsTable"
data-toolbar="#toolbar"
data-search="true"
data-pagination="true"
data-show-columns="true"
data-show-columns-search="true"
>
<thead>
<tr>
<th data-field="idHash">ID Hash</th>
<th data-field="relayed">Relayed</th>
<th data-field="blobSize">Blob Size</th>
<th data-field="doNotRelay">Do Not Relay</th>
<th data-field="doubleSpendSeen">Double Spend Seen</th>
<th data-field="fee">Fee</th>
<th data-field="keptByBlock">Kept By Block</th>
<th data-field="lastFailedHeight">Last Failed Height</th>
<th data-field="lastFailedIdHash">Last Failed Id Hash</th>
<th data-field="lastRelayedTime">Last Relayed Time</th>
<th data-field="maxUsedBlockHeight">Max Used Block Height</th>
<th data-field="maxUsedBlockIdHash">Max Used Block Id Hash</th>
<th data-field="receiveTime">Receive Time</th>
<th data-field="txBlob">Tx Blob</th>
</tr>
</thead>
</table>
</div>
</li>
</div>
</div>
<div class="row mt-4">
<div class="col-md-12">
<h6>Spent Key Images in Pool</h6>
<li class="list-group-item">
<div class="m-3">
<table
id="spentKeyImagesTable"
data-toggle="spentKeyImagesTable"
data-toolbar="#toolbar"
data-search="true"
data-pagination="true"
data-show-columns="true"
data-show-columns-search="true"
>
<thead>
<tr>
<th data-field="idHash">ID Hash</th>
</tr>
</thead>
</table>
</div>
</li>
</div>
</div>
</div>
</div>
<hr class="my-4">
<h6 class="mb-3">Spent Key Images</h6>
<div class="m-3">
<table
id="spentKeyImagesTable"
data-toggle="spentKeyImagesTable"
data-toolbar="#toolbar"
data-search="true"
data-pagination="true"
data-show-columns="true"
data-show-columns-search="true"
>
<thead>
<tr>
<th data-field="idHash">ID Hash</th>
</tr>
</thead>
</table>
</div>
</div>
<div class="tab-pane fade" id="pills-relay-tx" role="tabpanel" aria-labelledby="pills-relay-tx-tab" tabindex="0">
@ -256,13 +308,13 @@
<form class="needs-validation" novalidate="">
<div class="row g-3">
<div class="col-md-4">
<div class="col-md-6">
<label for="height" class="form-label">Height</label>
<input type="number" min="0" class="form-control" id="height" placeholder="" [(ngModel)]="coinbaseTxSumHeight" [ngModelOptions]="{standalone: true}">
<small class="text-body-secondary">Block height from which getting the amounts</small>
</div>
<div class="col-md-4">
<div class="col-md-6">
<label for="count" class="form-label">Count</label>
<input type="number" min="0" class="form-control" id="count" placeholder="" [(ngModel)]="coinbaseTxSumCount" [ngModelOptions]="{standalone: true}">
<small class="text-body-secondary">Number of blocks to include in the sum</small>
@ -293,7 +345,7 @@
</div>
<div class="row g-5 p-2">
<div class="col-md-7 col-lg-10">
<div class="col-md-7 col-lg-12">
<h4 class="mb-3">Flush a list of transaction IDs</h4>
<form class="needs-validation" novalidate="">
<div class="row g-3">
@ -336,7 +388,7 @@
</div>
<div class="row g-5 p-2">
<div class="col-md-7 col-lg-10">
<div class="col-md-7 col-lg-12">
<h4 class="mb-3">Flush bad transactions / blocks from the cache</h4>
<form class="needs-validation" novalidate="">
<div class="row gy-3">

View file

@ -5,7 +5,7 @@ import { NavbarLink } from '../../shared/components/navbar/navbar.model';
import { TxBacklogEntry } from '../../../common/TxBacklogEntry';
import { SimpleBootstrapCard } from '../../shared/utils';
import { DaemonDataService } from '../../core/services';
import { FeeEstimate, SpentKeyImage, UnconfirmedTx } from '../../../common';
import { FeeEstimate, SpentKeyImage, TxPoolHisto, TxPoolStats, UnconfirmedTx } from '../../../common';
import { Subscription } from 'rxjs';
import { BasePageComponent } from '../base-page/base-page.component';
@ -76,6 +76,14 @@ export class TransactionsComponent extends BasePageComponent implements AfterVie
return this.daemonData.transactionPool.spentKeyImages;
}
public get txPoolStats(): TxPoolStats {
if (this.daemonData.txPoolStats) {
return this.daemonData.txPoolStats;
}
return new TxPoolStats(0, 0, 0, 0, 0, new TxPoolHisto(0, 0), 0, 0, 0, 0, 0, 0, 0)
}
constructor(private daemonData: DaemonDataService, private daemonService: DaemonService, navbarService: NavbarService, private ngZone: NgZone) {
super(navbarService);
@ -99,12 +107,21 @@ export class TransactionsComponent extends BasePageComponent implements AfterVie
this.ngZone.run(() => {
this.loadTables();
const onSyncEndSub: Subscription = this.daemonData.syncEnd.subscribe(() => this.loadTables());
const onSyncEndSub: Subscription = this.daemonData.syncEnd.subscribe(() => this.refresh());
this.subscriptions.push(onSyncEndSub);
});
}
private refresh(): void {
this.loadTables();
this.loadPoolStats();
}
private loadPoolStats(): void {
}
private loadTransactionsTable(): void {
this.loadTable('transactionsTable', this.unconfirmedTxs);
}

78
src/common/TxPoolStats.ts Normal file
View file

@ -0,0 +1,78 @@
export class TxPoolStats {
public readonly bytesMax: number;
public readonly bytesMed: number;
public readonly bytesMin: number;
public readonly bytesTotal: number;
public readonly feeTotal: number;
public readonly histo: TxPoolHisto;
public readonly histo98pc: number;
public readonly num10m: number;
public readonly numDoubleSpends: number;
public readonly numFailing: number;
public readonly numNotRelayed: number;
public readonly oldest: number;
public readonly txsTotal: number;
constructor(bytesMax: number, bytesMed: number, bytesMin: number, bytesTotal: number,
feeTotal: number, histo: TxPoolHisto, histo98pc: number, num10m: number,
numDoubleSpends: number, numFailing: number, numNotRelayed: number, oldest: number,
txsTotal: number
) {
this.bytesMax = bytesMax;
this.bytesMed = bytesMed;
this.bytesMin = bytesMin;
this.bytesTotal = bytesTotal;
this.feeTotal = feeTotal;
this.histo = histo;
this.histo98pc = histo98pc;
this.num10m = num10m;
this.numDoubleSpends = numDoubleSpends;
this.numFailing = numFailing;
this.numNotRelayed = numNotRelayed;
this.oldest = oldest;
this.txsTotal = txsTotal;
}
public static parse(txPoolStats: any): TxPoolStats {
const bytesMax: number = txPoolStats.bytes_max;
const bytesMed: number = txPoolStats.bytes_med;
const bytesMin: number = txPoolStats.bytes_min;
const bytesTotal: number = txPoolStats.bytes_total;
const feeTotal: number = txPoolStats.total_fee;
const histo: TxPoolHisto = TxPoolHisto.parse(txPoolStats.histo);
const histo98pc: number = txPoolStats.histo98pc;
const num10m: number = txPoolStats.num10m;
const numDoubleSpends: number = txPoolStats.num_double_spends;
const numFailing: number = txPoolStats.num_failing;
const numNotRelayed: number = txPoolStats.num_not_relayed;
const oldest: number = txPoolStats.oldest;
const txsTotal: number = txPoolStats.txs_total;
return new TxPoolStats(bytesMax, bytesMed, bytesMin, bytesTotal,
feeTotal, histo, histo98pc, num10m,
numDoubleSpends, numFailing, numNotRelayed, oldest,
txsTotal);
}
}
export class TxPoolHisto {
public readonly txs: number;
public readonly bytes: number;
constructor(txs: number, bytes: number) {
this.txs = txs;
this.bytes = bytes;
}
public static parse(object: any): TxPoolHisto {
const txs: number = object.txs;
const bytes: number = object.bytes;
return new TxPoolHisto(txs, bytes);
}
}

View file

@ -38,6 +38,7 @@ export { NetStatsHistory, NetStatsHistoryEntry } from './NetStatsHistory';
export { UnconfirmedTx } from './UnconfirmedTx';
export { SpentKeyImage } from './SpentKeyImage';
export { TxPool } from './TxPool';
export { TxPoolStats, TxPoolHisto } from './TxPoolStats';
export { ProcessStats } from './ProcessStats';
export * from './error';

View file

@ -0,0 +1,12 @@
import { RPCRequest } from "./RPCRequest";
export class GetTransactionPoolStatsRequest extends RPCRequest {
public override readonly method: string = 'get_transaction_pool_stats';
public override readonly restricted: boolean = false;
public toDictionary(): { [key: string]: any; } {
return { };
}
}

View file

@ -60,6 +60,7 @@ export { SetLogCategoriesRequest } from "./SetLogCategoriesRequest";
export { GetTransactionPoolRequest } from "./GetTransactionPoolRequest";
export { GetPeerListRequest } from "./GetPeerListRequest";
export { GetTransactionsRequest } from "./GetTransactionsRequest";
export { GetTransactionPoolStatsRequest } from "./GetTransactionPoolStatsRequest";
/**
* Restricted requests