Create difficulty chart component
This commit is contained in:
parent
e2e3546934
commit
7270b1ccac
@ -199,6 +199,7 @@ class Blocks {
|
||||
|
||||
const chunkSize = 10000;
|
||||
let totaIndexed = 0;
|
||||
let indexedThisRun = 0;
|
||||
while (currentBlockHeight >= lastBlockToIndex) {
|
||||
const endBlock = Math.max(0, lastBlockToIndex, currentBlockHeight - chunkSize + 1);
|
||||
|
||||
@ -207,9 +208,11 @@ class Blocks {
|
||||
if (missingBlockHeights.length <= 0) {
|
||||
logger.debug(`No missing blocks between #${currentBlockHeight} to #${endBlock}`);
|
||||
currentBlockHeight -= chunkSize;
|
||||
totaIndexed += chunkSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
totaIndexed += chunkSize - missingBlockHeights.length;
|
||||
logger.debug(`Indexing ${missingBlockHeights.length} blocks from #${currentBlockHeight} to #${endBlock}`);
|
||||
|
||||
for (const blockHeight of missingBlockHeights) {
|
||||
@ -219,8 +222,10 @@ class Blocks {
|
||||
try {
|
||||
if (totaIndexed % 100 === 0 || blockHeight === lastBlockToIndex) {
|
||||
const elapsedSeconds = Math.max(1, Math.round((new Date().getTime() / 1000) - startedAt));
|
||||
const blockPerSeconds = Math.round(totaIndexed / elapsedSeconds);
|
||||
logger.debug(`Indexing block #${blockHeight} | ~${blockPerSeconds} blocks/sec | total: ${totaIndexed} | elapsed: ${elapsedSeconds} seconds`);
|
||||
const blockPerSeconds = Math.max(1, Math.round(indexedThisRun / elapsedSeconds));
|
||||
const progress = Math.round(totaIndexed / indexingBlockAmount * 100);
|
||||
const timeLeft = Math.round((indexingBlockAmount - totaIndexed) / blockPerSeconds);
|
||||
logger.debug(`Indexing block #${blockHeight} | ~${blockPerSeconds} blocks/sec | total: ${totaIndexed}/${indexingBlockAmount} (${progress}%) | elapsed: ${elapsedSeconds} seconds | left: ~${timeLeft} seconds`);
|
||||
}
|
||||
const blockHash = await bitcoinApi.$getBlockHash(blockHeight);
|
||||
const block = await bitcoinApi.$getBlock(blockHash);
|
||||
@ -228,6 +233,7 @@ class Blocks {
|
||||
const blockExtended = await this.$getBlockExtended(block, transactions);
|
||||
await blocksRepository.$saveBlockInDatabase(blockExtended);
|
||||
++totaIndexed;
|
||||
++indexedThisRun;
|
||||
} catch (e) {
|
||||
logger.err(`Something went wrong while indexing blocks.` + e);
|
||||
}
|
||||
|
@ -259,10 +259,7 @@ class Server {
|
||||
;
|
||||
}
|
||||
|
||||
const indexingAvailable =
|
||||
['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) &&
|
||||
config.DATABASE.ENABLED === true;
|
||||
if (indexingAvailable) {
|
||||
if (Common.indexingEnabled()) {
|
||||
this.app
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/24h', routes.$getPools.bind(routes, '24h'))
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/3d', routes.$getPools.bind(routes, '3d'))
|
||||
@ -277,7 +274,9 @@ class Server {
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/blocks', routes.$getPoolBlocks)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/blocks/:height', routes.$getPoolBlocks)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId', routes.$getPool)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/:interval', routes.$getPool);
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/:interval', routes.$getPool)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty', routes.$getHistoricalDifficulty)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty/:interval', routes.$getHistoricalDifficulty);
|
||||
}
|
||||
|
||||
if (config.BISQ.ENABLED) {
|
||||
|
@ -223,6 +223,30 @@ class BlocksRepository {
|
||||
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return blocks difficulty
|
||||
*/
|
||||
public async $getBlocksDifficulty(interval: string | null): Promise<object[]> {
|
||||
interval = Common.getSqlInterval(interval);
|
||||
|
||||
const connection = await DB.pool.getConnection();
|
||||
|
||||
let query = `SELECT MIN(blockTimestamp) as timestamp, difficulty
|
||||
FROM blocks`;
|
||||
|
||||
if (interval) {
|
||||
query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
|
||||
}
|
||||
|
||||
query += ` GROUP BY difficulty
|
||||
ORDER BY blockTimestamp DESC`;
|
||||
|
||||
const [rows]: any[] = await connection.query(query);
|
||||
connection.release();
|
||||
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
|
||||
export default new BlocksRepository();
|
||||
|
@ -575,6 +575,18 @@ class Routes {
|
||||
}
|
||||
}
|
||||
|
||||
public async $getHistoricalDifficulty(req: Request, res: Response) {
|
||||
try {
|
||||
const stats = await BlocksRepository.$getBlocksDifficulty(req.params.interval ?? null);
|
||||
res.header('Pragma', 'public');
|
||||
res.header('Cache-control', 'public');
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
|
||||
res.json(stats);
|
||||
} catch (e) {
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
public async getBlock(req: Request, res: Response) {
|
||||
try {
|
||||
const result = await bitcoinApi.$getBlock(req.params.hash);
|
||||
|
@ -27,6 +27,7 @@ import { AssetGroupComponent } from './components/assets/asset-group/asset-group
|
||||
import { AssetsFeaturedComponent } from './components/assets/assets-featured/assets-featured.component';
|
||||
import { AssetsComponent } from './components/assets/assets.component';
|
||||
import { PoolComponent } from './components/pool/pool.component';
|
||||
import { DifficultyChartComponent } from './components/difficulty-chart/difficulty-chart.component';
|
||||
|
||||
let routes: Routes = [
|
||||
{
|
||||
@ -63,6 +64,10 @@ let routes: Routes = [
|
||||
path: 'blocks',
|
||||
component: LatestBlocksComponent,
|
||||
},
|
||||
{
|
||||
path: 'mining/difficulty',
|
||||
component: DifficultyChartComponent,
|
||||
},
|
||||
{
|
||||
path: 'mining/pools',
|
||||
component: PoolRankingComponent,
|
||||
@ -155,6 +160,10 @@ let routes: Routes = [
|
||||
path: 'blocks',
|
||||
component: LatestBlocksComponent,
|
||||
},
|
||||
{
|
||||
path: 'mining/difficulty',
|
||||
component: DifficultyChartComponent,
|
||||
},
|
||||
{
|
||||
path: 'mining/pools',
|
||||
component: PoolRankingComponent,
|
||||
@ -241,6 +250,10 @@ let routes: Routes = [
|
||||
path: 'blocks',
|
||||
component: LatestBlocksComponent,
|
||||
},
|
||||
{
|
||||
path: 'mining/difficulty',
|
||||
component: DifficultyChartComponent,
|
||||
},
|
||||
{
|
||||
path: 'mining/pools',
|
||||
component: PoolRankingComponent,
|
||||
|
@ -68,6 +68,7 @@ import { PushTransactionComponent } from './components/push-transaction/push-tra
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { AssetsFeaturedComponent } from './components/assets/assets-featured/assets-featured.component';
|
||||
import { AssetGroupComponent } from './components/assets/asset-group/asset-group.component';
|
||||
import { DifficultyChartComponent } from './components/difficulty-chart/difficulty-chart.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -118,6 +119,7 @@ import { AssetGroupComponent } from './components/assets/asset-group/asset-group
|
||||
AssetsNavComponent,
|
||||
AssetsFeaturedComponent,
|
||||
AssetGroupComponent,
|
||||
DifficultyChartComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule.withServerTransition({ appId: 'serverApp' }),
|
||||
|
@ -0,0 +1,8 @@
|
||||
<div class="container-xl">
|
||||
|
||||
<div *ngIf="difficultyObservable$ | async" class="" echarts [initOpts]="chartInitOptions" [options]="chartOptions"></div>
|
||||
<div class="text-center loadingGraphs" *ngIf="isLoading">
|
||||
<div class="spinner-border text-light"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
@ -0,0 +1,102 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { EChartsOption } from 'echarts';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
import { ApiService } from 'src/app/services/api.service';
|
||||
import { SeoService } from 'src/app/services/seo.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-difficulty-chart',
|
||||
templateUrl: './difficulty-chart.component.html',
|
||||
styleUrls: ['./difficulty-chart.component.scss'],
|
||||
styles: [`
|
||||
.loadingGraphs {
|
||||
position: absolute;
|
||||
top: 38%;
|
||||
left: calc(50% - 15px);
|
||||
z-index: 100;
|
||||
}
|
||||
`],
|
||||
})
|
||||
export class DifficultyChartComponent implements OnInit {
|
||||
chartOptions: EChartsOption = {};
|
||||
chartInitOptions = {
|
||||
renderer: 'svg'
|
||||
};
|
||||
|
||||
difficultyObservable$: Observable<any>;
|
||||
isLoading = true;
|
||||
|
||||
constructor(
|
||||
private seoService: SeoService,
|
||||
private apiService: ApiService,
|
||||
) {
|
||||
this.seoService.setTitle($localize`:@@mining.difficulty:Difficulty`);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.difficultyObservable$ = this.apiService.getHistoricalDifficulty$(undefined)
|
||||
.pipe(
|
||||
map(data => {
|
||||
return data.map(val => [val.timestamp, val.difficulty])
|
||||
}),
|
||||
tap(data => {
|
||||
this.prepareChartOptions(data);
|
||||
this.isLoading = false;
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
prepareChartOptions(data) {
|
||||
this.chartOptions = {
|
||||
title: {
|
||||
text: $localize`:@@mining.difficulty:Difficulty`,
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
color: '#FFF',
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: 'axis',
|
||||
},
|
||||
axisPointer: {
|
||||
type: 'line',
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'time',
|
||||
}
|
||||
],
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
fontSize: 11,
|
||||
formatter: function(val) {
|
||||
const diff = val / Math.pow(10, 12); // terra
|
||||
return diff.toString() + 'T';
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dotted',
|
||||
color: '#ffffff66',
|
||||
opacity: 0.25,
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: data,
|
||||
type: 'line',
|
||||
smooth: false,
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
},
|
||||
areaStyle: {}
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { EChartsOption, PieSeriesOption } from 'echarts';
|
||||
@ -23,7 +23,7 @@ import { StateService } from '../../services/state.service';
|
||||
}
|
||||
`],
|
||||
})
|
||||
export class PoolRankingComponent implements OnInit, OnDestroy {
|
||||
export class PoolRankingComponent implements OnInit {
|
||||
poolsWindowPreference: string;
|
||||
radioGroupForm: FormGroup;
|
||||
|
||||
@ -90,9 +90,6 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
}
|
||||
|
||||
formatPoolUI(pool: SinglePoolStats) {
|
||||
pool['blockText'] = pool.blockCount.toString() + ` (${pool.share}%)`;
|
||||
return pool;
|
||||
|
@ -129,12 +129,18 @@ export class ApiService {
|
||||
return this.httpClient.post<any>(this.apiBaseUrl + this.apiBasePath + '/api/tx', hexPayload, { responseType: 'text' as 'json'});
|
||||
}
|
||||
|
||||
listPools$(interval: string | null) : Observable<PoolsStats> {
|
||||
return this.httpClient.get<PoolsStats>(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pools/${interval}`);
|
||||
listPools$(interval: string | undefined) : Observable<PoolsStats> {
|
||||
return this.httpClient.get<PoolsStats>(
|
||||
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pools` +
|
||||
(interval !== undefined ? `/${interval}` : '')
|
||||
);
|
||||
}
|
||||
|
||||
getPoolStats$(poolId: number, interval: string | null): Observable<PoolStat> {
|
||||
return this.httpClient.get<PoolStat>(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${poolId}/${interval}`);
|
||||
getPoolStats$(poolId: number, interval: string | undefined): Observable<PoolStat> {
|
||||
return this.httpClient.get<PoolStat>(
|
||||
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${poolId}` +
|
||||
(interval !== undefined ? `/${interval}` : '')
|
||||
);
|
||||
}
|
||||
|
||||
getPoolBlocks$(poolId: number, fromHeight: number): Observable<BlockExtended[]> {
|
||||
@ -143,4 +149,11 @@ export class ApiService {
|
||||
(fromHeight !== undefined ? `/${fromHeight}` : '')
|
||||
);
|
||||
}
|
||||
|
||||
getHistoricalDifficulty$(interval: string | undefined): Observable<any[]> {
|
||||
return this.httpClient.get<any[]>(
|
||||
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/difficulty` +
|
||||
(interval !== undefined ? `/${interval}` : '')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user