Mining operations implementation

This commit is contained in:
everoddandeven 2024-09-30 23:12:00 +02:00
parent 0d78976e1d
commit 3ca4c45923
3 changed files with 461 additions and 62 deletions

View file

@ -342,18 +342,26 @@ export class DaemonService {
public async submitBlock(... blockBlobData: string[]): Promise<void> {
const response = await this.callRpc(new SubmitBlockRequest(blockBlobData));
if (response.error) {
if (!response.message) {
throw new Error(`Error code: ${response.code}`);
if (response.result && typeof response.result.status == 'string' && response.result.status != 'OK') {
if (response.result.status == 'BUSY') {
throw new CoreIsBusyError();
}
throw new Error(response.message);
throw new Error(response.result.status);
}
}
public async generateBlocks(amountOfBlocks: number, walletAddress: string, prevBlock: string = '', startingNonce: number): Promise<GeneratedBlocks> {
const response = await this.callRpc(new GenerateBlocksRequest(amountOfBlocks, walletAddress, prevBlock, startingNonce));
if(response.result && response.result.status != 'OK') {
if (response.result.status == 'BUSY') {
throw new CoreIsBusyError();
}
throw new Error(response.result.status);
}
return GeneratedBlocks.parse(response.result);
}

View file

@ -11,76 +11,339 @@
</div>
</div>
<div *ngIf="!coreBusy && daemonRunning" class="tab-content" id="pills-tabContent">
<div class="tab-pane fade show active" id="pills-miner-data" role="tabpanel" aria-labelledby="pills-miner-data-tab" tabindex="0">
<div class="row d-flex justify-content-center">
@for(card of cards; track card.header) {
<div class="card text-bg-dark m-3 text-center" style="max-width: 18rem;">
<div class="card-header">{{card.header}}</div>
<div class="card-body">
<h5 class="card-title">{{card.content}}</h5>
</div>
<div *ngIf="daemonRunning" class="tab-content" id="pills-tabContent">
<div class="tab-pane fade show active" id="pills-mining-status" role="tabpanel" aria-labelledby="pills-mining-status-tab" tabindex="0">
<div *ngIf="miningStatusLoading">
<!-- Placeholder per il caricamento -->
<div class="card">
<div class="card-body">
<h5 class="card-title placeholder-glow">
<span class="placeholder col-6"></span>
</h5>
<p class="card-text placeholder-glow">
<span class="placeholder col-7"></span>
<span class="placeholder col-4"></span>
<span class="placeholder col-4"></span>
<span class="placeholder col-6"></span>
<span class="placeholder col-8"></span>
</p>
</div>
</div>
</div>
<div *ngIf="miningStatus" class="card">
<div class="card-header bg-primary text-white">
<h4>Mining Status</h4>
</div>
<div class="card-body">
<h5 class="card-title">{{ miningStatus.active ? 'Mining Active' : 'Mining Inactive' }}</h5>
<ul class="list-group list-group-flush">
<li class="list-group-item">
<strong>Address:</strong> {{ miningStatus.address }}
</li>
<li class="list-group-item">
<strong>Background Idle Threshold:</strong> {{ miningStatus.bgIdleThreshold }}%
</li>
<li class="list-group-item">
<strong>Background Min Idle Seconds:</strong> {{ miningStatus.bgMinIdleSeconds }} seconds
</li>
<li class="list-group-item">
<strong>Background Target:</strong> {{ miningStatus.bgTarget }}
</li>
<li class="list-group-item">
<strong>Block Reward:</strong> {{ miningStatus.blockReward }}
</li>
<li class="list-group-item">
<strong>Block Target:</strong> {{ miningStatus.blockTarget }}
</li>
<li class="list-group-item">
<strong>Difficulty:</strong> {{ miningStatus.difficulty }} (Top 64: {{ miningStatus.difficultyTop64 }})
</li>
<li class="list-group-item">
<strong>Is Background Mining Enabled:</strong> {{ miningStatus.isBackgroundMiningEnabled ? 'Yes' : 'No' }}
</li>
<li class="list-group-item">
<strong>PoW Algorithm:</strong> {{ miningStatus.powAlgorithm }}
</li>
<li class="list-group-item">
<strong>Speed:</strong> {{ miningStatus.speed }} H/s
</li>
<li class="list-group-item">
<strong>Threads Count:</strong> {{ miningStatus.threadsCount }}
</li>
<li class="list-group-item">
<strong>Wide Difficulty:</strong> {{ miningStatus.wideDifficulty }}
</li>
</ul>
</div>
</div>
</div>
<div class="tab-pane fade" id="pills-miner-data" role="tabpanel" aria-labelledby="pills-miner-data-tab" tabindex="0">
<div *ngIf="coreBusy" 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>
Core is busy: miner data is not available during blockchain sync
</div>
</div>
<div *ngIf="!coreBusy && daemonRunning" class="row d-flex justify-content-center">
@for(card of cards; track card.header) {
<div class="card text-bg-dark m-3 text-center" style="max-width: 18rem;">
<div class="card-header">{{card.header}}</div>
<div class="card-body">
<h5 class="card-title">{{card.content}}</h5>
</div>
}
</div>
}
</div>
</div>
</div>
<div class="tab-pane fade" id="pills-alternate-chains" role="tabpanel" aria-labelledby="pills-alternate-chains-tab" tabindex="0">
<div class="m-3">
<table
id="chainsTable"
data-toggle="chainsTable"
data-toolbar="#toolbar"
data-height="460"
>
<thead>
<tr>
<th data-field="blockHash">Block Hash</th>
<th data-field="height">Height</th>
<th data-field="length">Length</th>
<th data-field="mainChainParentBlock">Main Chain Parent Block</th>
<th data-field="wideDifficulty">Wide Difficulty</th>
</tr>
</thead>
</table>
</div>
<div class="tab-pane fade" id="pills-alternate-chains" role="tabpanel" aria-labelledby="pills-alternate-chains-tab" tabindex="0">
<div class="m-3">
<table
id="chainsTable"
data-toggle="chainsTable"
data-toolbar="#toolbar"
data-height="460"
>
<thead>
<tr>
<th data-field="blockHash">Block Hash</th>
<th data-field="height">Height</th>
<th data-field="length">Length</th>
<th data-field="mainChainParentBlock">Main Chain Parent Block</th>
<th data-field="wideDifficulty">Wide Difficulty</th>
</tr>
</thead>
</table>
</div>
<div class="tab-pane fade" id="pills-block-template" role="tabpanel" aria-labelledby="pills-block-template-tab" tabindex="0">
<div *ngIf="getBlockTemplateError != ''" 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>
{{ getBlockTemplateError }}
</div>
</div>
<div class="tab-pane fade" id="pills-block-template" role="tabpanel" aria-labelledby="pills-block-template-tab" tabindex="0">
<div class="input-group flex-nowrap m-4">
<span class="input-group-text" id="addon-wrapping">Address</span>
<input type="text" class="form-control" aria-label="Address" aria-describedby="addon-wrapping">
<div class="row g-5 p-2">
<div class="cold-md-7 col-lg-12">
<div class="row gy-3">
<div class="col-sm-9">
<label for="get-block-template-address" class="form-label">Address</label>
<input type="text" class="form-control" id="get-block-template-address" placeholder="" [(ngModel)]="getBlockTemplateAddress" [ngModelOptions]="{standalone: true}" required="">
<small class="text-body-secondary">Address of wallet to receive coinbase transactions if block is successfully mined</small>
</div>
<div class="col-sm-3">
<label for="get-block-header-reserve-size" class="form-label">Reserve size</label>
<input type="number" class="form-control" min="0" id="get-block-header-reserve-size" placeholder="" [(ngModel)]="getBlockTemplateReserveSize" [ngModelOptions]="{standalone: true}">
</div>
</div>
</div>
</div>
<hr class="my-4">
<button *ngIf="!gettingBlockTemplate" class="w-100 btn btn-primary btn-lg" type="button" (click)="getBlockTemplate()">Get Block Template</button>
<button *ngIf="gettingBlockTemplate" class="w-100 btn btn-primary btn-lg" type="button" disabled>Getting Block Template ...</button>
</div>
<div class="tab-pane fade" id="pills-calc-pow" role="tabpanel" aria-labelledby="pills-calc-pow-tab" tabindex="0">
<div *ngIf="calcPowError != ''" 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>
{{ calcPowError }}
</div>
</div>
<div *ngIf="calculatedPowHash != ''" 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>
Calculated PoW Hash: {{ calculatedPowHash }}
</div>
</div>
<div class="row g-5 p-2">
<div class="cold-md-7 col-lg-12">
<div class="row gy-3">
<h4 class="mb-3">Calculate PoW hash for a block candidate</h4>
<div class="col-sm-6">
<label for="calc-pow-major-version" class="form-label">Major version</label>
<input type="number" class="form-control" min="0" id="calc-pow-major-version" placeholder="" [(ngModel)]="calcPowMajorVersion" [ngModelOptions]="{standalone: true}">
<small class="text-body-secondary">The major version of the monero protocol at this block height</small>
</div>
<div class="col-sm-6">
<label for="calc-pow-height" class="form-label">Height</label>
<input type="number" class="form-control" min="0" id="calc-pow-height" placeholder="" [(ngModel)]="calcPowHeight" [ngModelOptions]="{standalone: true}">
</div>
<div class="col-sm-12">
<label for="calc-pow-blob-data" class="form-label">Blob data</label>
<textarea [(ngModel)]="calcPowBlobData" [ngModelOptions]="{standalone: true}" type="text" class="form-control" id="calc-pow-blob-data" placeholder=""
rows="15" cols="15" ></textarea>
<div class="invalid-feedback">
Invalid blob data.
</div>
</div>
<div class="col-sm-12">
<label for="calc-pow-seed" class="form-label">Seed</label>
<input type="text" class="form-control" id="calc-pow-seed" placeholder="" [(ngModel)]="calcPowSeed" [ngModelOptions]="{standalone: true}" required="">
</div>
</div>
</div>
</div>
<hr class="my-4">
<button *ngIf="!gettingCalcPow" class="w-100 btn btn-primary btn-lg" type="button" (click)="calcPowHash()">Calculate PoW Hash</button>
<button *ngIf="gettingCalcPow" class="w-100 btn btn-primary btn-lg" type="button" disabled>Calculating PoW Hash ...</button>
</div>
<div class="tab-pane fade" id="pills-submit-block" role="tabpanel" aria-labelledby="pills-submit-block-tab" tabindex="0">
<div *ngIf="submitBlockError != ''" 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>
{{ submitBlockError }}
</div>
</div>
<div *ngIf="submitBlockSuccess" 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 submitted block
</div>
</div>
<div class="row g-5 p-2">
<div class="cold-md-7 col-lg-12">
<div class="col-md-7 col-lg-12">
<h4 class="mb-3">Submit a mined block to the network</h4>
<form class="needs-validation" novalidate="">
<div class="row g-3">
<div class="input-group flex-nowrap m-4">
<span class="input-group-text" id="addon-wrapping">Reserve Size</span>
<input type="number" class="form-control" aria-label="Reserve Size" aria-describedby="addon-wrapping">
</div>
<div class="col-12">
<label for="submit-block-blob-data" class="form-label">Tx Ids</label>
<textarea [(ngModel)]="submitBlockBlobDataJsonString" [ngModelOptions]="{standalone: true}" type="text" [class]="!modifiedSubmitBlockBlobData ? 'form-control' : validBlobData() ? 'form-control' : 'form-control is-invalid'" id="tx_ids" placeholder="[
'0707e6bdfedc053771512f1bc27c62731ae9e8f2443db64ce742f4e57f5cf8d393de28551e441a0000000002fb830a01ffbf830a018cfe88bee283060274c0aae2ef5730e680308d9c00b6da59187ad0352efe3c71d36eeeb28782f29f2501bd56b952c3ddc3e350c2631d3a5086cac172c56893831228b17de296ff4669de020200000000'
]"
rows="15" cols="15" ></textarea>
<div class="invalid-feedback">
Invalid blob data.
</div>
<small class="text-body-secondary">list of block blobs which have been mined. See get_block_template to get a blob on which to mine</small>
</div>
</div>
<div class="input-group m-4">
<button class="btn btn-primary" type="button" id="getBlockTemplateButton">Get Block Template</button>
</form>
</div>
</div>
</div>
<div class="tab-pane fade" id="pills-disabled" role="tabpanel" aria-labelledby="pills-disabled-tab" tabindex="0">...</div>
</div>
<div *ngIf="coreBusy && daemonRunning" class="cover-container d-flex w-100 h-100 p-3 mx-auto flex-column text-bg-dark rounded-3 m-4 text-center">
<hr class="my-4">
<button *ngIf="!submittingBlock" class="w-100 btn btn-primary btn-lg" type="button" (click)="submitBlock()">Submit Block</button>
<button *ngIf="submittingBlock" class="w-100 btn btn-primary btn-lg" type="button" disabled>Submitting Block ...</button>
</div>
<div class="tab-pane fade" id="pills-generate-blocks" role="tabpanel" aria-labelledby="pills-generate-blocks-tab" tabindex="0">
<div *ngIf="generateBlocksError != ''" 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>
{{ generateBlocksError }}
</div>
</div>
<div *ngIf="generatingBlocks">
<!-- Placeholder per il caricamento -->
<div class="card">
<div class="card-body">
<h5 class="card-title placeholder-glow">
<span class="placeholder col-6"></span>
</h5>
<p class="card-text placeholder-glow">
<span class="placeholder col-7"></span>
<span class="placeholder col-4"></span>
<span class="placeholder col-4"></span>
<span class="placeholder col-6"></span>
<span class="placeholder col-8"></span>
</p>
</div>
</div>
</div>
<div *ngIf="generatedBlocks" class="card">
<div class="card-header bg-success text-white">
<h4>Generated Blocks</h4>
</div>
<div class="card-body">
<h5 class="card-title">Status: {{ generatedBlocks.status }}</h5>
<ul class="list-group list-group-flush">
<li class="list-group-item">
<strong>Height:</strong> {{ generatedBlocks.height }}
</li>
<li class="list-group-item">
<strong>Untrusted:</strong> {{ generatedBlocks.untrusted ? 'Yes' : 'No' }}
</li>
<li class="list-group-item">
<strong>Blocks:</strong>
<ul>
<li *ngFor="let block of generatedBlocks.blocks">{{ block }}</li>
</ul>
</li>
</ul>
</div>
</div>
<hr *ngIf="generatedBlocks" class="my-4">
<div class="row g-5 p-2">
<div class="cold-md-7 col-lg-12">
<div class="row gy-3">
<h4 class="mb-3">Calculate PoW hash for a block candidate</h4>
<div class="col-sm-6">
<label for="generate-blocks-starting-nonce" class="form-label">Starting Nonce</label>
<input type="number" class="form-control" min="0" id="generate-blocks-starting-nonce" placeholder="" [(ngModel)]="generateStartingNonce" [ngModelOptions]="{standalone: true}">
<small class="text-body-secondary">Increased by miner until it finds a matching result that solves a block</small>
</div>
<div class="col-sm-6">
<label for="generate-blocks-amount-of-blocks" class="form-label">Amount of blocks</label>
<input type="number" class="form-control" min="0" id="generate-blocks-amount-of-blocks" placeholder="" [(ngModel)]="generateBlocksAmountOfBlocks" [ngModelOptions]="{standalone: true}">
<small class="text-body-secondary">Number of blocks to be generated</small>
</div>
<div class="col-sm-12">
<label for="generate-blocks-address" class="form-label">Wallet Address</label>
<input type="text" class="form-control" id="generate-blocks-address" placeholder="" [(ngModel)]="generateBlocksAddress" [ngModelOptions]="{standalone: true}" required="">
<small class="text-body-secondary">Address to receive the coinbase reward</small>
</div>
<div class="col-sm-12">
<label for="generate-blocks-prev-block" class="form-label">Prev Block</label>
<input type="text" class="form-control" id="generate-blocks-prev-block" placeholder="" [(ngModel)]="generateBlockPrevBlock" [ngModelOptions]="{standalone: true}" required="">
</div>
</div>
</div>
</div>
<hr class="my-4">
<button *ngIf="!generatingBlocks" class="w-100 btn btn-primary btn-lg" type="button" (click)="generateBlocks()">Generate Blocks</button>
<button *ngIf="generatingBlocks" class="w-100 btn btn-primary btn-lg" type="button" disabled>Generating Blocks ...</button>
</div>
<div class="tab-pane fade" id="pills-disabled" role="tabpanel" aria-labelledby="pills-disabled-tab" tabindex="0">...</div>
<main class="px-3">
<h1><i class="bi bi-exclamation-diamond m-4"></i> Core is busy</h1>
<p class="lead">Mining capabilities are not available during daemon sync.</p>
<!--
<p class="lead">
<a href="#" class="btn btn-lg btn-light fw-bold border-white bg-white">Learn more</a>
</p>
-->
</main>
<!--
<footer class="mt-auto text-white-50">
<p>Cover template for <a href="https://getbootstrap.com/" class="text-white">Bootstrap</a>, by <a href="https://twitter.com/mdo" class="text-white">mdo</a>.</p>
</footer>
-->
</div>
<app-daemon-not-running></app-daemon-not-running>

View file

@ -7,6 +7,7 @@ import { NavbarLink } from '../../shared/components/navbar/navbar.model';
import { MineableTxBacklog } from '../../../common/MineableTxBacklog';
import { Chain } from '../../../common/Chain';
import { CoreIsBusyError } from '../../../common/error';
import { BlockTemplate, GeneratedBlocks, MiningStatus } from '../../../common';
@Component({
selector: 'app-mining',
@ -18,6 +19,39 @@ export class MiningComponent implements AfterViewInit {
public readonly navbarLinks: NavbarLink[];
public coreBusy: boolean;
private minerData?: MinerData;
public miningStatus?: MiningStatus;
public miningStatusLoading?: boolean = false;
public gettingBlockTemplate: boolean = false;
public getBlockTemplateAddress: string = '';
public getBlockTemplateReserveSize: number = 0;
public getBlockTemplateError: string = '';
public blockTemplate?: BlockTemplate;
public submittingBlock: boolean = false;
public submitBlockError: string = '';
public submitBlockSuccess: boolean = false;
public submitBlockBlobDataJsonString: string = '';
public get modifiedSubmitBlockBlobData(): boolean {
return this.submitBlockBlobDataJsonString != '';
}
public gettingCalcPow: boolean = false;
public calcPowBlobData: string = '';
public calcPowMajorVersion: number = 0;
public calcPowHeight: number = 0;
public calcPowSeed: string = '';
public calcPowError: string = '';
public calculatedPowHash: string = '';
public generatedBlocks?: GeneratedBlocks;
public generatingBlocks: boolean = false;
public generateBlocksError: string = '';
public generateBlocksAmountOfBlocks: number = 0;
public generateBlocksAddress: string = '';
public generateBlockPrevBlock: string = '';
public generateStartingNonce: number = 0;
private majorVersion: number;
private height: number;
@ -45,7 +79,8 @@ export class MiningComponent implements AfterViewInit {
this.coreBusy = false;
this.navbarLinks = [
new NavbarLink('pills-miner-data-tab', '#pills-miner-data', 'miner-data', true, 'Miner Data'),
new NavbarLink('pills-mining-status-tab', '#pills-mining-status', 'mining-status', true, 'Status'),
new NavbarLink('pills-miner-data-tab', '#pills-miner-data', 'miner-data', false, 'Miner Data'),
new NavbarLink('pills-alternate-chains-tab', '#pills-alternate-chains', 'alternate-chains', false, 'Alternate Chains'),
new NavbarLink('pills-block-template-tab', '#pills-block-template', 'block-template', false, 'Block Template'),
new NavbarLink('pills-generate-blocks-tab', '#pills-generate-blocks', 'generate-blocks', false, 'Generate Blocks'),
@ -95,7 +130,74 @@ export class MiningComponent implements AfterViewInit {
});
}
private async getMiningStatus(): Promise<void> {
this.miningStatusLoading = true;
try {
this.miningStatus = await this.daemonService.miningStatus();
} catch(error) {
console.error(error);
this.miningStatus = undefined;
}
this.miningStatusLoading = false;
}
public async getBlockTemplate(): Promise<void> {
this.gettingBlockTemplate = true;
try {
this.blockTemplate = await this.daemonService.getBlockTemplate(this.getBlockTemplateAddress, this.getBlockTemplateReserveSize);
this.getBlockTemplateError = '';
} catch(error) {
this.getBlockTemplateError = `${error}`;
}
this.gettingBlockTemplate = false;
}
public async submitBlock(): Promise<void> {
if (!this.validBlobData()) {
return;
}
this.submittingBlock = true;
try {
const blobData: string[] = JSON.parse(this.submitBlockBlobDataJsonString);
await this.daemonService.submitBlock(...blobData);
this.submitBlockError = '';
this.submitBlockSuccess = true;
}
catch(error) {
console.error(error);
this.submitBlockError = `${error}`;
}
this.submittingBlock = false;
}
public validBlobData(): boolean {
try {
const parsed: any[] = JSON.parse(this.submitBlockBlobDataJsonString);
if (!Array.isArray(parsed)) {
throw new Error();
}
parsed.forEach((blob) => {
if (typeof blob != 'string') {
return false;
}
})
return true;
} catch (error) {
return false;
}
}
private async load(): Promise<void> {
await this.getMiningStatus();
try {
const running = await this.daemonService.isRunning();
@ -161,6 +263,32 @@ export class MiningComponent implements AfterViewInit {
return chains;
}
public async calcPowHash() {
this.gettingCalcPow = true;
try {
this.calculatedPowHash = await this.daemonService.calculatePoWHash(this.calcPowMajorVersion, this.calcPowHeight, this.calcPowBlobData, this.calcPowSeed)
} catch(error) {
this.calcPowError = `${error}`;
}
this.gettingCalcPow = false;
}
public async generateBlocks(): Promise<void> {
this.generatingBlocks = true;
try {
this.generatedBlocks = await this.daemonService.generateBlocks(this.generateBlocksAmountOfBlocks, this.generateBlocksAddress, this.generateBlockPrevBlock, this.generateStartingNonce);
this.generateBlocksError = '';
}
catch(error) {
this.generateBlocksError = `${error}`;
}
this.generatingBlocks = false;
}
}
class Card {