Network methods implementation

Peers methods implementation
Bans methods implementation
This commit is contained in:
everoddandeven 2024-10-08 22:45:24 +02:00
parent 5d4280bcf8
commit 5705b8e91b
18 changed files with 804 additions and 84 deletions

View file

@ -1,6 +1,6 @@
import { EventEmitter, Injectable } from '@angular/core'; import { EventEmitter, Injectable } from '@angular/core';
import { DaemonService } from './daemon.service'; import { DaemonService } from './daemon.service';
import { BlockCount, BlockHeader, Chain, CoreIsBusyError, DaemonInfo, MinerData, MiningStatus, NetStats, NetStatsHistory, PublicNode, SyncInfo } from '../../../../common'; import { BlockCount, BlockHeader, Chain, Connection, CoreIsBusyError, DaemonInfo, MinerData, MiningStatus, NetStats, NetStatsHistory, PeerInfo, PublicNode, SyncInfo, TxPool } from '../../../../common';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -12,6 +12,7 @@ export class DaemonDataService {
private _refreshing: boolean = false; private _refreshing: boolean = false;
private _firstRefresh: boolean = true; private _firstRefresh: boolean = true;
private _lastRefresh: number = Date.now(); private _lastRefresh: number = Date.now();
private _lastRefreshHeight: number = -1;
private _daemonRunning: boolean = false; private _daemonRunning: boolean = false;
private _daemonRestarting: boolean = false; private _daemonRestarting: boolean = false;
@ -48,6 +49,15 @@ export class DaemonDataService {
private _publicNodes: PublicNode[] = []; private _publicNodes: PublicNode[] = [];
private _gettingPublicNodes: boolean = false; private _gettingPublicNodes: boolean = false;
private _transactionPool?: TxPool;
private _gettingTransactionPool: boolean = false;
private _connections: Connection[] = [];
private _gettingConnections: boolean = false;
private _peerList: PeerInfo[] = [];
private _gettingPeerList: boolean = false;
public readonly syncStart: EventEmitter<void> = new EventEmitter<void>(); public readonly syncStart: EventEmitter<void> = new EventEmitter<void>();
public readonly syncEnd: EventEmitter<void> = new EventEmitter<void>(); public readonly syncEnd: EventEmitter<void> = new EventEmitter<void>();
public readonly syncError: EventEmitter<Error> = new EventEmitter<Error>(); public readonly syncError: EventEmitter<Error> = new EventEmitter<Error>();
@ -182,6 +192,30 @@ export class DaemonDataService {
return this._gettingPublicNodes; return this._gettingPublicNodes;
} }
public get transactionPool(): TxPool | undefined {
return this._transactionPool;
}
public get gettingTransactionPool(): boolean {
return this._gettingTransactionPool;
}
public get connections(): Connection[] {
return this._connections;
}
public get gettingConnections(): boolean {
return this._gettingConnections;
}
public get peerList(): PeerInfo[] {
return this._peerList;
}
public get gettingPeerList(): boolean {
return this._gettingPeerList;
}
public setRefreshTimeout(ms: number = 5000): void { public setRefreshTimeout(ms: number = 5000): void {
this.refreshTimeoutMs = ms; this.refreshTimeoutMs = ms;
} }
@ -324,12 +358,25 @@ export class DaemonDataService {
await this.refreshMinerData(); await this.refreshMinerData();
this._gettingPeerList = true;
this._peerList = await this.daemonService.getPeerList();
this._gettingPeerList = false;
this._gettingPublicNodes = true; this._gettingPublicNodes = true;
this._publicNodes = await this.daemonService.getPublicNodes(true, true); this._publicNodes = await this.daemonService.getPublicNodes(true, true);
this._gettingPublicNodes = false; this._gettingPublicNodes = false;
if (this._daemonInfo.synchronized && this._daemonInfo.txPoolSize > 0) {
this._gettingTransactionPool = true;
this._transactionPool = await this.daemonService.getTransactionPool();
this._gettingTransactionPool = false;
}
this._gettingConnections = true;
this._connections = await this.daemonService.getConnections();
this._gettingConnections = false;
this._lastRefreshHeight = this._daemonInfo.heightWithoutBootstrap;
this._lastRefresh = Date.now(); this._lastRefresh = Date.now();
} catch(error) { } catch(error) {
console.error(error); console.error(error);
@ -341,6 +388,9 @@ export class DaemonDataService {
this._gettingAltChains = false; this._gettingAltChains = false;
this._gettingNetStats = false; this._gettingNetStats = false;
this._gettingPublicNodes = false; this._gettingPublicNodes = false;
this._gettingTransactionPool = false;
this._gettingConnections = false;
this._gettingPeerList = false;
this.syncError.emit(<Error>error); this.syncError.emit(<Error>error);

View file

@ -41,7 +41,9 @@ import {
SetBootstrapDaemonRequest, SetBootstrapDaemonRequest,
SetLogLevelRequest, SetLogLevelRequest,
SetLogHashRateRequest, SetLogHashRateRequest,
SetLogCategoriesRequest SetLogCategoriesRequest,
GetTransactionPoolRequest,
GetPeerListRequest
} from '../../../../common/request'; } from '../../../../common/request';
import { BlockTemplate } from '../../../../common/BlockTemplate'; import { BlockTemplate } from '../../../../common/BlockTemplate';
import { GeneratedBlocks } from '../../../../common/GeneratedBlocks'; import { GeneratedBlocks } from '../../../../common/GeneratedBlocks';
@ -76,6 +78,7 @@ import { TxInfo } from '../../../../common/TxInfo';
import { DaemonSettings } from '../../../../common/DaemonSettings'; import { DaemonSettings } from '../../../../common/DaemonSettings';
import { MethodNotFoundError } from '../../../../common/error/MethodNotFoundError'; import { MethodNotFoundError } from '../../../../common/error/MethodNotFoundError';
import { openDB, IDBPDatabase } from "idb" import { openDB, IDBPDatabase } from "idb"
import { PeerInfo, TxPool } from '../../../../common';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -411,7 +414,12 @@ export class DaemonService {
public async getConnections(): Promise<Connection[]> { public async getConnections(): Promise<Connection[]> {
const response = await this.callRpc(new GetConnectionsRequest()); const response = await this.callRpc(new GetConnectionsRequest());
const connections: any[] = response.connections;
if (!response.result || !response.result.connections) {
return [];
}
const connections: any[] = response.result.connections;
const result: Connection[] = []; const result: Connection[] = [];
connections.forEach((connection: any) => result.push(Connection.parse(connection))) connections.forEach((connection: any) => result.push(Connection.parse(connection)))
@ -797,6 +805,23 @@ export class DaemonService {
return NetStats.parse(response); return NetStats.parse(response);
} }
public async getPeerList(): Promise<PeerInfo[]> {
const response = await this.callRpc(new GetPeerListRequest());
if (response.status != 'OK') {
throw new Error(response.status);
}
const peerList: PeerInfo[] = [];
const whiteList: any[] | undefined = response.white_list
const grayList: any[] | undefined = response.gray_list;
if (whiteList) whiteList.forEach((white: any) => peerList.push(PeerInfo.parse(white, 'white')));
if (grayList) grayList.forEach((gray: any) => peerList.push(PeerInfo.parse(gray, 'gray')));
return peerList;
}
public async getPublicNodes(whites: boolean = true, grays: boolean = false, includeBlocked: boolean = false): Promise<PublicNode[]> { public async getPublicNodes(whites: boolean = true, grays: boolean = false, includeBlocked: boolean = false): Promise<PublicNode[]> {
const response = await this.callRpc(new GetPublicNodesRequest(whites, grays, includeBlocked)); const response = await this.callRpc(new GetPublicNodesRequest(whites, grays, includeBlocked));
@ -810,6 +835,16 @@ export class DaemonService {
return nodes; return nodes;
} }
public async getTransactionPool(): Promise<TxPool> {
const response = await this.callRpc(new GetTransactionPoolRequest());
if (response.status != 'OK') {
throw new Error(response.status);
}
return TxPool.parse(response);
}
public async getTransactionPoolHashes(): Promise<string[]> { public async getTransactionPoolHashes(): Promise<string[]> {
const response = await this.callRpc(new GetTransactionPoolHashesRequest()); const response = await this.callRpc(new GetTransactionPoolHashesRequest());

View file

@ -11,21 +11,91 @@
</div> </div>
</div> </div>
<div [hidden]="!daemonRunning || daemonChangingStatus"> <div [hidden]="!daemonRunning || daemonStopping" class="tab-content" id="pills-tabContent">
<table <div class="tab-pane fade show active" id="pills-overview" role="tabpanel" aria-labelledby="pills-overview-tab" tabindex="0">
id="bansTable" <h4 class="mb-3">List of banned IPs</h4>
data-toggle="bansTable" <br>
data-toolbar="#toolbar" <table
data-height="460" id="bansTable"
> data-toggle="bansTable"
<thead> data-toolbar="#toolbar"
<tr> data-height="460"
<th data-field="host">Host</th> >
<th data-field="ip">Ip</th> <thead>
<th data-field="seconds">Seconds</th> <tr>
</tr> <th data-field="host">Host</th>
</thead> <th data-field="ip">Ip</th>
</table> <th data-field="seconds">Seconds</th>
</tr>
</thead>
</table>
<hr class="my-4">
<button *ngIf="!refreshingBansTable" class="w-100 btn btn-primary btn-lg" type="button" (click)="refreshBansTable()">Refresh Bans</button>
<button *ngIf="refreshingBansTable" class="w-100 btn btn-primary btn-lg" type="button" disabled>Refreshing Bans ...</button>
</div>
<div class="tab-pane fade" id="pills-set-bans" role="tabpanel" aria-labelledby="pills-set-bans-tab" tabindex="0">
<h4 class="mb-3">Ban another node by IP</h4>
<div class="row g-5 p-2">
<div class="col-md-7 col-lg-12">
<div *ngIf="setBansSuccess" class="alert alert-success d-flex align-items-center justify-content-center text-center" role="alert">
<h4><i class="bi bi-send-check m-2"></i></h4>&nbsp;&nbsp;
<div>
Successfully set bans
</div>
</div>
<div *ngIf="setBansError" class="alert alert-danger d-flex align-items-center justify-content-center text-center" role="alert">
<h4><i class="bi bi-exclamation-triangle m-2"></i></h4>&nbsp;&nbsp;
<div>
{{setBansError}}
</div>
</div>
<form class="needs-validation" novalidate="">
<div class="row g-3">
<div class="col-12">
<label for="bans" class="form-label">Bans</label>
<textarea [(ngModel)]="setBansBansJsonString" [ngModelOptions]="{standalone: true}" type="text" class="form-control" id="bans" placeholder="[
{
'host': <string>,
'ip': <number>,
'ban': <boolean>,
'seconds': <number>
},
... ,
{
'host': <string>,
'ip': <number>,
'ban': <boolean>,
'seconds': <number>
}
]"
rows="15" cols="15" ></textarea>
<div class="invalid-feedback">
Invalid bans provided.
</div>
<small class="text-body-secondary">List of nodes to ban</small>
</div>
</div>
</form>
</div>
</div>
<hr class="my-4">
<button *ngIf="!settingBans" class="w-100 btn btn-primary btn-lg" type="button" (click)="setBans()">Set Bans</button>
<button *ngIf="settingBans" class="w-100 btn btn-primary btn-lg" type="button" disabled>Setting Bans ...</button>
</div>
</div> </div>

View file

@ -1,8 +1,9 @@
import { AfterViewInit, Component, NgZone } from '@angular/core'; import { AfterViewInit, Component, NgZone } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { NavbarService } from '../../shared/components/navbar/navbar.service'; import { NavbarService } from '../../shared/components/navbar/navbar.service';
import { DaemonService } from '../../core/services/daemon/daemon.service'; import { DaemonService } from '../../core/services/daemon/daemon.service';
import { NavbarLink } from '../../shared/components/navbar/navbar.model'; import { NavbarLink } from '../../shared/components/navbar/navbar.model';
import { DaemonDataService } from '../../core/services';
import { Ban } from '../../../common';
@Component({ @Component({
@ -15,61 +16,85 @@ export class BansComponent implements AfterViewInit {
new NavbarLink('pills-overview-tab', '#pills-overview', 'pills-overview', true, 'Overview', true), new NavbarLink('pills-overview-tab', '#pills-overview', 'pills-overview', true, 'Overview', true),
new NavbarLink('pills-set-bans-tab', '#pills-set-bans', 'pills-set-bans', false, 'Set Bans', true) new NavbarLink('pills-set-bans-tab', '#pills-set-bans', 'pills-set-bans', false, 'Set Bans', true)
]; ];
public daemonRunning: boolean = false;
public refreshingBansTable: boolean = false;
public get daemonRunning(): boolean {
return this.daemonData.running;
}
public get daemonStopping(): boolean {
return this.daemonData.stopping;
}
public get daemonChangingStatus(): boolean { public get daemonChangingStatus(): boolean {
return this.daemonService.stopping || this.daemonService.starting; return this.daemonService.stopping || this.daemonService.starting;
} }
constructor(private router: Router, private daemonService: DaemonService, private navbarService: NavbarService, private ngZone: NgZone) { public settingBans: boolean = false;
this.router.events.subscribe((event) => { public setBansBansJsonString: string = '';
if (event instanceof NavigationEnd) { public setBansSuccess: boolean = false;
if (event.url != '/bans') return; public setBansError: string = '';
this.onNavigationEnd();
public get validBans(): boolean {
try {
const bans: any[] = JSON.parse(this.setBansBansJsonString);
if (!Array.isArray(bans)) {
return false;
} }
})
this.daemonService.isRunning().then((running) => { bans.forEach((ban) => Ban.parse(ban));
this.daemonRunning = running
});
this.daemonService.onDaemonStatusChanged.subscribe((running: boolean) => {
this.daemonRunning = running;
if (running) this.load();
});
return true;
}
catch(error) {
return false;
}
} }
ngAfterViewInit(): void { private get bans(): Ban[] {
this.navbarService.removeLinks(); if (!this.validBans) {
return [];
}
const bans: Ban[] = [];
const rawBans: any[] = [];
console.log('BansComponent AFTER VIEW INIT'); rawBans.forEach((rawBan) => bans.push(Ban.parse(rawBan)));
return bans;
}
constructor(private daemonData: DaemonDataService, private daemonService: DaemonService, private navbarService: NavbarService, private ngZone: NgZone) {
}
public ngAfterViewInit(): void {
this.navbarService.setLinks(this.navbarLinks);
this.ngZone.run(() => { this.ngZone.run(() => {
//const $ = require('jquery');
//const bootstrapTable = require('bootstrap-table');
const $table = $('#bansTable'); const $table = $('#bansTable');
$table.bootstrapTable({ $table.bootstrapTable({});
});
$table.bootstrapTable('refreshOptions', { $table.bootstrapTable('refreshOptions', {
classes: 'table table-bordered table-hover table-dark table-striped' classes: 'table table-bordered table-hover table-dark table-striped'
}); });
$table.bootstrapTable('showLoading'); $table.bootstrapTable('showLoading');
this.load(); this.refreshBansTable();
}); });
} }
private onNavigationEnd(): void { public async refreshBansTable(): Promise<void> {
this.navbarService.removeLinks();
}
private async load(): Promise<void> {
const $table = $('#bansTable'); const $table = $('#bansTable');
let _bans: Ban[] = [];
try {
_bans = await this.daemonService.getBans();
}
catch (error) {
console.error(error);
_bans = [];
}
const _bans = await this.daemonService.getBans();
const bans: any[] = []; const bans: any[] = [];
_bans.forEach((ban) => bans.push({ _bans.forEach((ban) => bans.push({
@ -82,8 +107,21 @@ export class BansComponent implements AfterViewInit {
} }
public async setBans() { public async setBans(): Promise<void> {
this.settingBans = true;
try {
await this.daemonService.setBans(...this.bans);
this.setBansError = '';
this.setBansSuccess = true;
}
catch (error) {
console.error(error);
this.setBansSuccess = false;
this.setBansError = `${error}`;
}
this.settingBans = false;
} }
} }

View file

@ -12,6 +12,7 @@
</div> </div>
<div *ngIf="daemonRunning && !daemonStopping" class="tab-content" id="pills-tabContent"> <div *ngIf="daemonRunning && !daemonStopping" class="tab-content" id="pills-tabContent">
<div class="tab-pane fade show active" id="pills-net-stats" role="tabpanel" aria-labelledby="pills-net-stats-tab" tabindex="0"> <div class="tab-pane fade show active" id="pills-net-stats" role="tabpanel" aria-labelledby="pills-net-stats-tab" tabindex="0">
<h2>Bytes In</h2> <h2>Bytes In</h2>
<canvas class="my-4 w-100" id="netStatsBytesInChart" width="900" height="380"></canvas> <canvas class="my-4 w-100" id="netStatsBytesInChart" width="900" height="380"></canvas>
@ -19,6 +20,91 @@
<h2>Bytes Out</h2> <h2>Bytes Out</h2>
<canvas class="my-4 w-100" id="netStatsBytesOutChart" width="900" height="380"></canvas> <canvas class="my-4 w-100" id="netStatsBytesOutChart" width="900" height="380"></canvas>
</div> </div>
<div class="tab-pane fade" id="pills-connections" role="tabpanel" aria-labelledby="pills-connections-tab" tabindex="0">
<h4 class="mb-3">Information about incoming and outgoing connections to your node</h4>
<br>
<div class="m-3">
<table
id="connectionsTable"
data-toggle="connectionsTable"
data-toolbar="#toolbar"
data-height="460"
>
<thead>
<tr>
<th data-field="connectionId">ID</th>
<th data-field="peerId">Peer ID</th>
<th data-field="address">Address</th>
<th data-field="host">Host</th>
<th data-field="ip">IP</th>
<th data-field="port">Port</th>
<th data-field="state">State</th>
<th data-field="avgDownload">Avg Download</th>
<th data-field="avgUpload">Avg Upload</th>
<th data-field="currentDownload">Current Download</th>
<th data-field="currentUpload">Current Upload</th>
<th data-field="height">Height</th>
<th data-field="incoming">Incoming</th>
<th data-field="liveTime">Live Time</th>
<th data-field="recvCount">Recv Count</th>
<th data-field="recvIdleTime">Recv Idle Time</th>
<th data-field="sendCount">Send Count</th>
<th data-field="sendIdleTime">Send Idle Time</th>
<th data-field="supportFlags">Support Flags</th>
</tr>
</thead>
</table>
</div>
</div>
<div class="tab-pane fade" id="pills-limit" role="tabpanel" aria-labelledby="pills-limit-tab" tabindex="0">
<h4 class="mb-4">Set daemon bandwidth limits</h4>
<br>
<div *ngIf="setLimitError != ''" class="alert alert-danger d-flex align-items-center justify-content-center text-center" role="alert">
<h4><i class="bi bi-exclamation-triangle m-2"></i></h4>&nbsp;&nbsp;
<div>
{{setLimitError}}
</div>
</div>
<div *ngIf="setLimitSuccess" class="alert alert-success d-flex align-items-center justify-content-center text-center" role="alert">
<h4><i class="bi bi-check-circle m-2"></i></h4>&nbsp;&nbsp;
<div>
Limit set to <strong>UP:</strong>{{setLimitResult?.limitUp}}, <strong>DOWN:</strong>{{setLimitResult?.limitDown}}
</div>
</div>
<div class="row g-5 p-2">
<div class="cold-md-7 col-lg-12">
<div class="row gy-3">
<div class="col-sm-6">
<label for="limit-down" class="form-label">Limit Down</label>
<input type="number" min="-1" class="form-control" id="limit-down" placeholder="" [(ngModel)]="limitDown" [ngModelOptions]="{standalone: true}">
<small class="text-body-secondary">Download limit in kBytes per second (-1 reset to default, 0 don't change the current limit)</small>
</div>
<div class="col-sm-6">
<label for="limit-up" class="form-label">Limit Up</label>
<input type="number" min="-1" class="form-control" id="limit-up" placeholder="" [(ngModel)]="limitUp" [ngModelOptions]="{standalone: true}">
<small class="text-body-secondary">Upload limit in kBytes per second (-1 reset to default, 0 don't change the current limit)</small>
</div>
</div>
</div>
</div>
<hr class="my-4">
<button *ngIf="!limiting" class="w-100 btn btn-primary btn-lg" type="button" (click)="setLimit()">Set Limit</button>
<button *ngIf="limiting" class="w-100 btn btn-primary btn-lg" type="button" disabled>Setting Limit ...</button>
</div>
</div> </div>
<app-daemon-not-running></app-daemon-not-running> <app-daemon-not-running></app-daemon-not-running>

View file

@ -1,16 +1,17 @@
import { AfterViewInit, Component } from '@angular/core'; import { AfterViewInit, Component, OnDestroy } from '@angular/core';
import { NavbarService } from '../../shared/components/navbar/navbar.service'; import { NavbarService } from '../../shared/components/navbar/navbar.service';
import { DaemonDataService, DaemonService } from '../../core/services'; import { DaemonDataService, DaemonService } from '../../core/services';
import { NavbarLink } from '../../shared/components/navbar/navbar.model'; import { NavbarLink } from '../../shared/components/navbar/navbar.model';
import { Chart, ChartData } from 'chart.js/auto' import { Chart, ChartData } from 'chart.js/auto'
import { NetStatsHistoryEntry } from '../../../common'; import { NetStatsHistoryEntry } from '../../../common';
import { Subscription } from 'rxjs';
@Component({ @Component({
selector: 'app-network', selector: 'app-network',
templateUrl: './network.component.html', templateUrl: './network.component.html',
styleUrl: './network.component.scss' styleUrl: './network.component.scss'
}) })
export class NetworkComponent implements AfterViewInit { export class NetworkComponent implements AfterViewInit, OnDestroy {
public readonly navbarLinks: NavbarLink[]; public readonly navbarLinks: NavbarLink[];
private netStatsBytesInChart?: Chart; private netStatsBytesInChart?: Chart;
@ -24,15 +25,29 @@ export class NetworkComponent implements AfterViewInit {
return this.daemonData.stopping; return this.daemonData.stopping;
} }
public limiting: boolean = false;
public limitUp: number = 0;
public limitDown: number = 0;
public setLimitResult?: { limitUp: number, limitDown: number };
public setLimitSuccess: boolean = false;
public setLimitError: string = '';
private subscriptions: Subscription[] = [];
constructor(private navbarService: NavbarService, private daemonService: DaemonService, private daemonData: DaemonDataService) { constructor(private navbarService: NavbarService, private daemonService: DaemonService, private daemonData: DaemonDataService) {
this.navbarLinks = [ this.navbarLinks = [
new NavbarLink('pills-net-stats-tab', '#pills-net-stats', 'pills-net-stats', false, 'Statistics'), new NavbarLink('pills-net-stats-tab', '#pills-net-stats', 'pills-net-stats', false, 'Statistics'),
new NavbarLink('pills-limits-tab', '#pills-limits', 'pills-limits', false, 'Limits') new NavbarLink('pills-connections-tab', '#pills-connections', 'connections', false, 'Connetions'),
new NavbarLink('pills-limits-tab', '#pills-limit', 'pills-limit', false, 'Limit')
]; ];
this.daemonData.netStatsRefreshEnd.subscribe(() => { this.subscriptions.push(this.daemonData.netStatsRefreshEnd.subscribe(() => {
this.refreshNetStatsHistory(); this.refreshNetStatsHistory();
}); }));
this.subscriptions.push(this.daemonData.syncEnd.subscribe(() => {
this.loadConnectionsTable();
}));
this.daemonService.onDaemonStatusChanged.subscribe((running: boolean) => { this.daemonService.onDaemonStatusChanged.subscribe((running: boolean) => {
if (!running) { if (!running) {
@ -47,6 +62,29 @@ export class NetworkComponent implements AfterViewInit {
public ngAfterViewInit(): void { public ngAfterViewInit(): void {
this.navbarService.setLinks(this.navbarLinks); this.navbarService.setLinks(this.navbarLinks);
this.initNetStatsHistoryChart(); this.initNetStatsHistoryChart();
this.initConnectionsTable();
}
public ngOnDestroy(): void {
this.subscriptions.forEach((sub) => {
sub.unsubscribe();
});
this.subscriptions = [];
}
private initConnectionsTable(): void {
const $table = $('#connectionsTable');
$table.bootstrapTable({});
$table.bootstrapTable('refreshOptions', {
classes: 'table table-bordered table-hover table-dark table-striped'
});
}
private loadConnectionsTable(): void {
const $table = $('#connectionsTable');
$table.bootstrapTable('load', this.daemonData.connections);
} }
private buildChartBytesInData(): ChartData { private buildChartBytesInData(): ChartData {
@ -159,4 +197,22 @@ export class NetworkComponent implements AfterViewInit {
} }
} }
public async setLimit(): Promise<void> {
this.limiting = true;
try {
this.setLimitResult = await this.daemonService.setLimit(this.limitUp, this.limitDown);
this.setLimitSuccess = true;
this.setLimitError = '';
}
catch (error) {
console.error(error);
this.setLimitResult = undefined;
this.setLimitSuccess = false;
this.setLimitError = `${error}`;
}
this.limiting = false;
}
} }

View file

@ -12,7 +12,38 @@
</div> </div>
<div *ngIf="daemonRunning && !daemonStopping" class="tab-content" id="pills-tabContent"> <div *ngIf="daemonRunning && !daemonStopping" class="tab-content" id="pills-tabContent">
<div class="tab-pane fade show active" id="pills-public-nodes" role="tabpanel" aria-labelledby="pills-public-nodes-tab" tabindex="0"> <div class="tab-pane fade show active" id="pills-peer-list" role="tabpanel" aria-labelledby="pills-peer-list-tab" tabindex="0">
<h4 class="mb-3">List of known peers</h4>
<div class="m-3">
<table
id="peerListTable"
data-toggle="peerListTable"
data-toolbar="#toolbar"
data-pagination="true"
data-height="460"
>
<thead>
<tr>
<th data-field="host">Host</th>
<th data-field="id">ID</th>
<th data-field="ip">IP</th>
<th data-field="lastSeen">Last Seen</th>
<th data-field="port">Port</th>
<th data-field="type">Type</th>
</tr>
</thead>
</table>
</div>
<hr class="my-4">
<button *ngIf="!refreshingPeerList" class="w-100 btn btn-primary btn-lg" type="button" (click)="refreshPeerListTable()">Refresh Peer List</button>
<button *ngIf="refreshingPeerList" class="w-100 btn btn-primary btn-lg" type="button" disabled>Refreshing Peer List ...</button>
</div>
<div class="tab-pane fade" id="pills-public-nodes" role="tabpanel" aria-labelledby="pills-public-nodes-tab" tabindex="0">
<h4 class="mb-3">Public peer information</h4> <h4 class="mb-3">Public peer information</h4>
<div class="m-3"> <div class="m-3">
<table <table

View file

@ -3,6 +3,7 @@ import { DaemonDataService, DaemonService } from '../../core/services';
import { NavbarLink } from '../../shared/components/navbar/navbar.model'; import { NavbarLink } from '../../shared/components/navbar/navbar.model';
import { NavbarService } from '../../shared/components/navbar/navbar.service'; import { NavbarService } from '../../shared/components/navbar/navbar.service';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { resolve } from 'path';
@Component({ @Component({
selector: 'app-peers', selector: 'app-peers',
@ -25,6 +26,8 @@ export class PeersComponent implements AfterViewInit, OnDestroy {
public limitOutPeersError: string = ''; public limitOutPeersError: string = '';
public limitOutPeersResult: number = 0; public limitOutPeersResult: number = 0;
public refreshingPeerList: boolean = false;
public get daemonRunning(): boolean { public get daemonRunning(): boolean {
return this.daemonData.running; return this.daemonData.running;
} }
@ -37,6 +40,7 @@ export class PeersComponent implements AfterViewInit, OnDestroy {
constructor(private daemonService: DaemonService, private daemonData: DaemonDataService, private navbarService: NavbarService, private ngZone: NgZone) { constructor(private daemonService: DaemonService, private daemonData: DaemonDataService, private navbarService: NavbarService, private ngZone: NgZone) {
this.navbarLinks = [ this.navbarLinks = [
new NavbarLink('pills-peer-list-tab', '#pills-peer-list', 'pills-peer-list', false, 'Peer List'),
new NavbarLink('pills-public-nodes-tab', '#pills-public-nodes', 'pills-public-nodes', false, 'Public Nodes'), new NavbarLink('pills-public-nodes-tab', '#pills-public-nodes', 'pills-public-nodes', false, 'Public Nodes'),
new NavbarLink('pills-in-peers-tab', '#pills-in-peers', 'pills-in-peers', false, 'In Peers'), new NavbarLink('pills-in-peers-tab', '#pills-in-peers', 'pills-in-peers', false, 'In Peers'),
new NavbarLink('pills-out-peers-tab', '#pills-out-peers', 'pills-out-peers', false, 'Out Peers') new NavbarLink('pills-out-peers-tab', '#pills-out-peers', 'pills-out-peers', false, 'Out Peers')
@ -48,21 +52,56 @@ export class PeersComponent implements AfterViewInit, OnDestroy {
this.ngZone.run(() => { this.ngZone.run(() => {
const $publicNodesTable = $('#publicNodesTable'); const $publicNodesTable = $('#publicNodesTable');
const $peerListTable = $('#peerListTable');
$publicNodesTable.bootstrapTable({}); $publicNodesTable.bootstrapTable({});
$publicNodesTable.bootstrapTable('refreshOptions', { $publicNodesTable.bootstrapTable('refreshOptions', {
classes: 'table table-bordered table-hover table-dark table-striped' classes: 'table table-bordered table-hover table-dark table-striped'
}); });
$peerListTable.bootstrapTable({});
$peerListTable.bootstrapTable('refreshOptions', {
classes: 'table table-bordered table-hover table-dark table-striped'
});
$publicNodesTable.bootstrapTable('load', this.daemonData.publicNodes); $publicNodesTable.bootstrapTable('load', this.daemonData.publicNodes);
$peerListTable.bootstrapTable('load', this.daemonData.peerList);
const sub = this.daemonData.syncEnd.subscribe(() => { const sub = this.daemonData.syncEnd.subscribe(() => {
$publicNodesTable.bootstrapTable('load', this.daemonData.publicNodes); $publicNodesTable.bootstrapTable('load', this.daemonData.publicNodes);
//$peerListTable.bootstrapTable('load', this.daemonData.peerList);
}); });
this.subscriptions.push(sub); this.subscriptions.push(sub);
}); });
} }
public async refreshPeerListTable(): Promise<void> {
this.refreshingPeerList = true;
try {
await new Promise<void>((resolve, reject) => {
setTimeout(() => {
this.ngZone.run(() => {
try {
const $peerListTable = $('#peerListTable');
$peerListTable.bootstrapTable('load', this.daemonData.peerList);
resolve();
}
catch(error) {
reject(error);
}
});
}, 1000);
});
}
catch(error) {
console.error(error);
}
this.refreshingPeerList = false;
}
public ngOnDestroy(): void { public ngOnDestroy(): void {
this.subscriptions.forEach((sub) => { this.subscriptions.forEach((sub) => {
sub.unsubscribe(); sub.unsubscribe();

View file

@ -11,9 +11,63 @@
</div> </div>
</div> </div>
<div *ngIf="daemonRunning" class="tab-content" id="pills-tabContent"> <div *ngIf="daemonRunning && !daemonStopping" class="tab-content" id="pills-tabContent">
<div class="tab-pane fade show active" id="pills-tx-pool" role="tabpanel" aria-labelledby="pills-tx-pool-tab" tabindex="0">
<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-height="460"
>
<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>
<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-height="460"
>
<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">
<div class="tab-pane fade show active" id="pills-relay-tx" role="tabpanel" aria-labelledby="pills-relay-tx-tab" tabindex="0">
<div class="row g-5 p-2"> <div class="row g-5 p-2">
<div class="col-md-7 col-lg-12"> <div class="col-md-7 col-lg-12">
<div *ngIf="relaySuccess" class="alert alert-success d-flex align-items-center justify-content-center text-center" role="alert"> <div *ngIf="relaySuccess" class="alert alert-success d-flex align-items-center justify-content-center text-center" role="alert">
@ -56,6 +110,7 @@
</div> </div>
</div> </div>
<button class="w-100 btn btn-primary btn-lg" type="button" [disabled]="!canRelay" [disabled]="!validTxIds()" (click)="onRelay()">Relay Tx</button> <button class="w-100 btn btn-primary btn-lg" type="button" [disabled]="!canRelay" [disabled]="!validTxIds()" (click)="onRelay()">Relay Tx</button>
</div> </div>
<div class="tab-pane fade" id="pills-send-raw-tx" role="tabpanel" aria-labelledby="pills-send-raw-tx-tab" tabindex="0"> <div class="tab-pane fade" id="pills-send-raw-tx" role="tabpanel" aria-labelledby="pills-send-raw-tx-tab" tabindex="0">
@ -95,12 +150,12 @@
<br> <br>
<small class="text-body-secondary">Stop relaying transaction to other nodes</small> <small class="text-body-secondary">Stop relaying transaction to other nodes</small>
</div> </div>
<hr class="my-4">
</div> </div>
</form> </form>
</div> </div>
</div> </div>
<hr class="my-4">
<button class="w-100 btn btn-primary btn-lg" type="button" (click)="sendRawTx()">Send Raw Tx</button> <button class="w-100 btn btn-primary btn-lg" type="button" (click)="sendRawTx()">Send Raw Tx</button>
</div> </div>
@ -152,7 +207,7 @@
</div> </div>
<div class="tab-pane fade" id="pills-flush-tx-pool" role="tabpanel" aria-labelledby="pills-flush-tx-pool-tab" tabindex="0"> <div class="tab-pane fade" id="pills-flush-tx-pool" role="tabpanel" aria-labelledby="pills-flush-tx-pool-tab" tabindex="0">
<div class="row g-5 m-2"> <div class="row g-5 p-2">
<div class="col-md-7 col-lg-10"> <div class="col-md-7 col-lg-10">
<h4 class="mb-3">Flush a list of transaction IDs</h4> <h4 class="mb-3">Flush a list of transaction IDs</h4>
<form class="needs-validation" novalidate=""> <form class="needs-validation" novalidate="">
@ -180,7 +235,7 @@
</div> </div>
<div class="tab-pane fade" id="pills-flush-cache" role="tabpanel" aria-labelledby="pills-flush-cache-tab" tabindex="0"> <div class="tab-pane fade" id="pills-flush-cache" role="tabpanel" aria-labelledby="pills-flush-cache-tab" tabindex="0">
<div class="row g-5 m-2"> <div class="row g-5 p-2">
<div class="col-md-7 col-lg-10"> <div class="col-md-7 col-lg-10">
<h4 class="mb-3">Flush a list of bad transaction IDs from cache</h4> <h4 class="mb-3">Flush a list of bad transaction IDs from cache</h4>
<form class="needs-validation" novalidate=""> <form class="needs-validation" novalidate="">
@ -207,6 +262,7 @@
<button class="w-100 btn btn-primary btn-lg" type="button" [disabled]="!canRelay" (click)="onFlushFromCache()">Flush Bad Txs</button> <button class="w-100 btn btn-primary btn-lg" type="button" [disabled]="!canRelay" (click)="onFlushFromCache()">Flush Bad Txs</button>
</div> </div>
</div> </div>

View file

@ -1,16 +1,20 @@
import { AfterViewInit, Component, NgZone } from '@angular/core'; import { AfterViewInit, Component, NgZone, OnDestroy } from '@angular/core';
import { DaemonService } from '../../core/services/daemon/daemon.service'; import { DaemonService } from '../../core/services/daemon/daemon.service';
import { NavbarService } from '../../shared/components/navbar/navbar.service'; import { NavbarService } from '../../shared/components/navbar/navbar.service';
import { NavbarLink } from '../../shared/components/navbar/navbar.model'; import { NavbarLink } from '../../shared/components/navbar/navbar.model';
import { TxBacklogEntry } from '../../../common/TxBacklogEntry'; import { TxBacklogEntry } from '../../../common/TxBacklogEntry';
import { SimpleBootstrapCard } from '../../shared/utils'; import { SimpleBootstrapCard } from '../../shared/utils';
import { DaemonDataService } from '../../core/services';
import { table } from 'console';
import { SpentKeyImage, UnconfirmedTx } from '../../../common';
import { Subscription } from 'rxjs';
@Component({ @Component({
selector: 'app-transactions', selector: 'app-transactions',
templateUrl: './transactions.component.html', templateUrl: './transactions.component.html',
styleUrl: './transactions.component.scss' styleUrl: './transactions.component.scss'
}) })
export class TransactionsComponent implements AfterViewInit { export class TransactionsComponent implements AfterViewInit, OnDestroy {
public readonly navbarLinks: NavbarLink[]; public readonly navbarLinks: NavbarLink[];
public canRelay: boolean; public canRelay: boolean;
@ -29,7 +33,13 @@ export class TransactionsComponent implements AfterViewInit {
return this.txIdsJsonString != ''; return this.txIdsJsonString != '';
} }
public daemonRunning: boolean = false; public get daemonRunning(): boolean {
return this.daemonData.running;
}
public get daemonStopping(): boolean {
return this.daemonData.stopping;
}
public rawTxJsonString: string = ''; public rawTxJsonString: string = '';
public sendRawTxDoNotRelay: boolean = false; public sendRawTxDoNotRelay: boolean = false;
@ -37,9 +47,28 @@ export class TransactionsComponent implements AfterViewInit {
public sendRawTxError: string = ''; public sendRawTxError: string = '';
public sendingRawTx: boolean = false; public sendingRawTx: boolean = false;
constructor(private daemonService: DaemonService, private navbarService: NavbarService, private ngZone: NgZone) { private get unconfirmedTxs(): UnconfirmedTx[] {
if (!this.daemonData.transactionPool) {
return [];
}
return this.daemonData.transactionPool.transactions;
}
private get spentKeyImages(): SpentKeyImage[] {
if (!this.daemonData.transactionPool) {
return [];
}
return this.daemonData.transactionPool.spentKeyImages;
}
private subscriptions: Subscription[] = [];
constructor(private daemonData: DaemonDataService, private daemonService: DaemonService, private navbarService: NavbarService, private ngZone: NgZone) {
this.navbarLinks = [ this.navbarLinks = [
new NavbarLink('pills-relay-tx-tab', '#pills-relay-tx', 'pills-relay-tx', true, 'Relay Tx'), new NavbarLink('pills-tx-pool-tab', '#pills-tx-pool', 'pills-tx-pool', true, 'Pool'),
new NavbarLink('pills-relay-tx-tab', '#pills-relay-tx', 'pills-relay-tx', false, 'Relay Tx'),
new NavbarLink('pills-send-raw-tx-tab', '#pills-send-raw-tx', 'pills-send-raw-tx', false, 'Send Raw Tx'), new NavbarLink('pills-send-raw-tx-tab', '#pills-send-raw-tx', 'pills-send-raw-tx', false, 'Send Raw Tx'),
new NavbarLink('pills-tx-backlog-tab', '#pills-tx-backlog', 'pills-tx-backlog', false, 'Tx Backlog'), new NavbarLink('pills-tx-backlog-tab', '#pills-tx-backlog', 'pills-tx-backlog', false, 'Tx Backlog'),
new NavbarLink('pills-coinbase-tx-sum-tab', '#pills-coinbase-tx-sum', 'pills-coinbase-tx-sum', false, 'Coinbase Tx Sum'), new NavbarLink('pills-coinbase-tx-sum-tab', '#pills-coinbase-tx-sum', 'pills-coinbase-tx-sum', false, 'Coinbase Tx Sum'),
@ -51,26 +80,63 @@ export class TransactionsComponent implements AfterViewInit {
this.txPoolBacklog = []; this.txPoolBacklog = [];
this.canRelay = false; this.canRelay = false;
this.daemonService.onDaemonStatusChanged.subscribe((running) => {
this.ngZone.run(() => {
this.daemonRunning = running;
});
});
this.daemonService.isRunning().then((value: boolean) => {
this.ngZone.run(() => {
this.daemonRunning = value;
});
});
} }
ngAfterViewInit(): void { public ngAfterViewInit(): void {
this.ngZone.run(() => {
this.navbarService.setLinks(this.navbarLinks); this.navbarService.setLinks(this.navbarLinks);
this.initTables();
this.subscriptions.push(this.daemonData.syncEnd.subscribe(() => {
this.refreshTables();
}));
this.load().then(() => { this.load().then(() => {
this.navbarService.enableLinks(); this.navbarService.enableLinks();
}).catch((error) => { }).catch((error) => {
console.error(error); console.error(error);
this.navbarService.disableLinks(); this.navbarService.disableLinks();
}) });
});
}
public ngOnDestroy(): void {
this.subscriptions.forEach((sub) => {
sub.unsubscribe();
});
this.subscriptions = [];
}
private initTable(id: string): void {
const $table = $(`#${id}`);
$table.bootstrapTable({});
$table.bootstrapTable('refreshOptions', {
classes: 'table table-bordered table-hover table-dark table-striped'
});
}
private initTables(): void {
this.initTable('spentKeyImagesTable');
this.initTable('transactionsTable');
}
private loadTransactionsTable(): void {
const $table = $('#transactionsTable');
$table.bootstrapTable('load', this.unconfirmedTxs);
}
private loadSpentKeyImagesTable(): void {
const $table = $('#spentKeyImagesTable');
$table.bootstrapTable('load', this.spentKeyImages);
}
private refreshTables(): void {
this.loadSpentKeyImagesTable();
this.loadTransactionsTable();
} }
private async load(): Promise<void> { private async load(): Promise<void> {

35
src/common/PeerInfo.ts Normal file
View file

@ -0,0 +1,35 @@
export class PeerInfo {
public readonly type: 'white' | 'gray';
public readonly host: number;
public readonly id: string;
public readonly ip: number;
public readonly lastSeen: number;
public readonly port: number;
constructor(type: 'white' | 'gray', host: number, id: string, ip: number, lastSeen: number, port: number) {
this.type = type;
this.host = host;
this.id = id;
this.ip = ip;
this.lastSeen = lastSeen;
this.port = port;
}
public static parse(peerInfo: any, type: 'white' | 'gray'): PeerInfo {
const host = peerInfo.host;
const id = peerInfo.id;
const ip = peerInfo.ip;
const lastSeen = peerInfo.last_seen;
const port = peerInfo.port;
return new PeerInfo(type, host, id, ip, lastSeen, port);
}
}
/**
* host - unsigned int; IP address in integer format
id - string; Peer id
ip - unsigned int; IP address in integer format
last_seen - unsigned int; unix time at which the peer has been seen for the last time
port - unsigned int; TCP port the peer is using to connect to monero network.
*/

View file

@ -0,0 +1,16 @@
export class SpentKeyImage {
public readonly idHash: string;
public readonly txsHashes: string[];
constructor(idHash: string, txsHashes: string[]) {
this.idHash = idHash;
this.txsHashes = txsHashes;
}
public static parse(spentKeyImage: any): SpentKeyImage {
const idHash = spentKeyImage.id_hash;
const txsHashes = spentKeyImage.txs_hashes;
return new SpentKeyImage(idHash, txsHashes);
}
}

24
src/common/TxPool.ts Normal file
View file

@ -0,0 +1,24 @@
import { SpentKeyImage } from "./SpentKeyImage";
import { UnconfirmedTx } from "./UnconfirmedTx";
export class TxPool {
public readonly transactions: UnconfirmedTx[];
public readonly spentKeyImages: SpentKeyImage[];
constructor(transactions: UnconfirmedTx[], spentKeyImages: SpentKeyImage[]) {
this.transactions = transactions;
this.spentKeyImages = spentKeyImages;
}
public static parse(txPool: any): TxPool {
const _transactions: any[] | undefined = txPool.transactions
const _spentKeyImages: any[] | undefined = txPool.spent_key_images;
const transactions: UnconfirmedTx[] = [];
const spentKeyImages: SpentKeyImage[] = [];
if (_transactions) _transactions.forEach((tx: any) => transactions.push(UnconfirmedTx.parse(tx)));
if (_spentKeyImages) _spentKeyImages.forEach((ski: any) => spentKeyImages.push(SpentKeyImage.parse(ski)));
return new TxPool(transactions, spentKeyImages);
}
}

View file

@ -0,0 +1,90 @@
export class UnconfirmedTx {
public readonly blobSize: number;
public readonly doNotRelay: boolean;
public readonly doubleSpendSeen: boolean;
public readonly fee: number;
public readonly idHash: string;
public readonly keptByBlock: boolean;
public readonly lastFailedHeight: number;
public readonly lastFailedIdHash: string;
public readonly lastRelayedTime: number;
public readonly maxUsedBlockHeight: number;
public readonly maxUsedBlockIdHash: string;
public readonly receiveTime: number;
public readonly relayed: boolean;
public readonly txBlob: string;
constructor(
blobSize: number,
doNotRelay: boolean,
doubleSpendSeen: boolean,
fee: number,
idHash: string,
keptByBlock: boolean,
lastFailedHeight: number,
lastFailedIdHash: string,
lastRelayedTime: number,
maxUsedBlockHeight: number,
maxUsedBlockIdHash: string,
receiveTime: number,
relayed: boolean,
txBlob: string
) {
this.blobSize = blobSize;
this.doNotRelay = doNotRelay;
this.doubleSpendSeen = doubleSpendSeen;
this.fee = fee;
this.idHash = idHash;
this.keptByBlock = keptByBlock;
this.lastFailedHeight = lastFailedHeight;
this.lastFailedIdHash = lastFailedIdHash;
this.lastRelayedTime = lastRelayedTime;
this.maxUsedBlockHeight = maxUsedBlockHeight;
this.maxUsedBlockIdHash = maxUsedBlockIdHash;
this.receiveTime = receiveTime;
this.relayed = relayed;
this.txBlob = txBlob;
}
public static parse(unconfirmedTx: any): UnconfirmedTx {
const blobSize = unconfirmedTx.blob_size;
const doNotRelay = unconfirmedTx.do_not_relay;
const doubleSpendSeen = unconfirmedTx.double_spend_seen;
const fee = unconfirmedTx.fee;
const idHash = unconfirmedTx.id_hash;
const keptByBlock = unconfirmedTx.kept_by_block;
const lastFailedHeight = unconfirmedTx.last_failed_height;
const lastFailedIdHash = unconfirmedTx.last_failed_id_hash;
const lastRelayedTime = unconfirmedTx.last_relayed_time;
const maxUsedBlockHeight = unconfirmedTx.max_used_block_height;
const maxUsedBlockIdHash = unconfirmedTx.max_used_block_id_hash;
const receiveTime = unconfirmedTx.receive_time;
const relayed = unconfirmedTx.relayed;
const txBlob = unconfirmedTx.tx_blob;
return new UnconfirmedTx(
blobSize, doNotRelay, doubleSpendSeen, fee, idHash, keptByBlock,
lastFailedHeight, lastFailedIdHash, lastRelayedTime, maxUsedBlockHeight,
maxUsedBlockIdHash, receiveTime, relayed, txBlob
);
}
}
/**
* blob_size - unsigned int; The size of the full transaction blob.
do_not_relay; boolean; States if this transaction should not be relayed
double_spend_seen - boolean; States if this transaction has been seen as double spend.
fee - unsigned int; The amount of the mining fee included in the transaction, in atomic units.
id_hash - string; The transaction ID hash.
kept_by_block - boolean; States if the tx was included in a block at least once (true) or not (false).
last_failed_height - unsigned int; If the transaction validation has previously failed, this tells at what height that occurred.
last_failed_id_hash - string; Like the previous, this tells the previous transaction ID hash.
last_relayed_time - unsigned int; Last unix time at which the transaction has been relayed.
max_used_block_height - unsigned int; Tells the height of the most recent block with an output used in this transaction.
max_used_block_id_hash - string; Tells the hash of the most recent block with an output used in this transaction.
receive_time - unsigned int; The Unix time that the transaction was first seen on the network by the node.
relayed - boolean; States if this transaction has been relayed
tx_blob - unsigned int; Hexadecimal blob representing the transaction.
*/

View file

@ -26,6 +26,7 @@ export { OutKey } from './OutKey';
export { Output } from './Output'; export { Output } from './Output';
export { OutputDistribution } from './OutputDistribution'; export { OutputDistribution } from './OutputDistribution';
export { Peer } from './Peer'; export { Peer } from './Peer';
export { PeerInfo } from './PeerInfo';
export { PublicNode } from './PublicNode'; export { PublicNode } from './PublicNode';
export { Span } from './Span'; export { Span } from './Span';
export { SyncInfo } from './SyncInfo'; export { SyncInfo } from './SyncInfo';
@ -34,6 +35,9 @@ export { TxInfo } from './TxInfo';
export { UpdateInfo } from './UpdateInfo'; export { UpdateInfo } from './UpdateInfo';
export { LogCategoryLevel, LogCategories } from './LogCategories'; export { LogCategoryLevel, LogCategories } from './LogCategories';
export { NetStatsHistory, NetStatsHistoryEntry } from './NetStatsHistory'; export { NetStatsHistory, NetStatsHistoryEntry } from './NetStatsHistory';
export { UnconfirmedTx } from './UnconfirmedTx';
export { SpentKeyImage } from './SpentKeyImage';
export { TxPool } from './TxPool';
export * from './error'; export * from './error';
export * from './request'; export * from './request';

View file

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

View file

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

View file

@ -57,6 +57,8 @@ export { SetBootstrapDaemonRequest } from "./SetBootstrapDaemonRequest";
export { SetLogLevelRequest } from "./SetLogLevelRequest"; export { SetLogLevelRequest } from "./SetLogLevelRequest";
export { SetLogHashRateRequest } from "./SetLogHashRateRequest"; export { SetLogHashRateRequest } from "./SetLogHashRateRequest";
export { SetLogCategoriesRequest } from "./SetLogCategoriesRequest"; export { SetLogCategoriesRequest } from "./SetLogCategoriesRequest";
export { GetTransactionPoolRequest } from "./GetTransactionPoolRequest";
export { GetPeerListRequest } from "./GetPeerListRequest";
/** /**
* Restricted requests * Restricted requests