Liquid: improve recent pegs pagination data query

This commit is contained in:
natsoni 2024-02-12 11:34:47 +01:00
parent 33ac4056d8
commit 99aa6c9ed3
No known key found for this signature in database
GPG key ID: C65917583181743B
8 changed files with 74 additions and 90 deletions

View file

@ -412,10 +412,10 @@ class ElementsParser {
return rows[0]; return rows[0];
} }
// Get recent pegouts from the federation (3 months old) // Get recent pegs in / out
public async $getRecentPegouts(): Promise<any> { public async $getPegsList(count: number = 0): Promise<any> {
const query = `SELECT txid, txindex, amount, bitcoinaddress, bitcointxid, bitcoinindex, datetime AS blocktime FROM elements_pegs WHERE amount < 0 AND datetime > UNIX_TIMESTAMP(TIMESTAMPADD(DAY, -90, CURRENT_TIMESTAMP())) ORDER BY blocktime;`; const query = `SELECT txid, txindex, amount, bitcoinaddress, bitcointxid, bitcoinindex, datetime AS blocktime FROM elements_pegs ORDER BY block DESC LIMIT 15 OFFSET ?;`;
const [rows] = await DB.query(query); const [rows] = await DB.query(query, [count]);
return rows; return rows;
} }
@ -428,6 +428,12 @@ class ElementsParser {
pegOutQuery[0][0] pegOutQuery[0][0]
]; ];
} }
// Get the total pegs number
public async $getPegsCount(): Promise<any> {
const [rows] = await DB.query(`SELECT COUNT(*) AS pegs_count FROM elements_pegs;`);
return rows[0];
}
} }
export default new ElementsParser(); export default new ElementsParser();

View file

@ -17,10 +17,11 @@ class LiquidRoutes {
app app
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs', this.$getElementsPegs) .get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs', this.$getElementsPegs)
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs/month', this.$getElementsPegsByMonth) .get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs/month', this.$getElementsPegsByMonth)
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs/list/:count', this.$getPegsList)
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs/volume', this.$getPegsVolumeDaily) .get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs/volume', this.$getPegsVolumeDaily)
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs/count', this.$getPegsCount)
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves', this.$getFederationReserves) .get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves', this.$getFederationReserves)
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/month', this.$getFederationReservesByMonth) .get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/month', this.$getFederationReservesByMonth)
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegouts', this.$getPegOuts)
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/addresses', this.$getFederationAddresses) .get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/addresses', this.$getFederationAddresses)
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/addresses/total', this.$getFederationAddressesNumber) .get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/addresses/total', this.$getFederationAddressesNumber)
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/utxos', this.$getFederationUtxos) .get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/utxos', this.$getFederationUtxos)
@ -178,13 +179,13 @@ class LiquidRoutes {
} }
} }
private async $getPegOuts(req: Request, res: Response) { private async $getPegsList(req: Request, res: Response) {
try { try {
const recentPegOuts = await elementsParser.$getRecentPegouts(); const recentPegs = await elementsParser.$getPegsList(parseInt(req.params?.count));
res.header('Pragma', 'public'); res.header('Pragma', 'public');
res.header('Cache-control', 'public'); res.header('Cache-control', 'public');
res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString()); res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
res.json(recentPegOuts); res.json(recentPegs);
} catch (e) { } catch (e) {
res.status(500).send(e instanceof Error ? e.message : e); res.status(500).send(e instanceof Error ? e.message : e);
} }
@ -202,6 +203,18 @@ class LiquidRoutes {
} }
} }
private async $getPegsCount(req: Request, res: Response) {
try {
const pegsCount = await elementsParser.$getPegsCount();
res.header('Pragma', 'public');
res.header('Cache-control', 'public');
res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
res.json(pegsCount);
} catch (e) {
res.status(500).send(e instanceof Error ? e.message : e);
}
}
} }
export default new LiquidRoutes(); export default new LiquidRoutes();

View file

@ -15,7 +15,7 @@
<th class="output text-left" *ngIf="!widget" i18n="liquid.fund-redemption-tx">Fund / Redemption Tx</th> <th class="output text-left" *ngIf="!widget" i18n="liquid.fund-redemption-tx">Fund / Redemption Tx</th>
<th class="address text-left" *ngIf="!widget" i18n="liquid.bitcoin-address">BTC Address</th> <th class="address text-left" *ngIf="!widget" i18n="liquid.bitcoin-address">BTC Address</th>
</thead> </thead>
<tbody *ngIf="recentPegs$ | async as pegs; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''"> <tbody *ngIf="recentPegsList$ | async as pegs; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''">
<ng-container *ngIf="widget; else regularRows"> <ng-container *ngIf="widget; else regularRows">
<tr *ngFor="let peg of pegs | slice:0:5"> <tr *ngFor="let peg of pegs | slice:0:5">
<td class="transaction text-left widget"> <td class="transaction text-left widget">
@ -39,7 +39,7 @@
</tr> </tr>
</ng-container> </ng-container>
<ng-template #regularRows> <ng-template #regularRows>
<tr *ngFor="let peg of pegs | slice:(page - 1) * pageSize:page * pageSize"> <tr *ngFor="let peg of pegs;">
<td class="transaction text-left"> <td class="transaction text-left">
<ng-container *ngIf="peg.amount > 0"> <ng-container *ngIf="peg.amount > 0">
<a [routerLink]="['/tx' | relativeUrl, peg.txid]" [fragment]="'vin=' + peg.txindex"> <a [routerLink]="['/tx' | relativeUrl, peg.txid]" [fragment]="'vin=' + peg.txindex">
@ -117,8 +117,8 @@
</ng-template> </ng-template>
</table> </table>
<ngb-pagination *ngIf="!widget && recentPegs$ | async as pegs" class="pagination-container float-right mt-2" [class]="isLoading ? 'disabled' : ''" <ngb-pagination *ngIf="!widget && pegsCount$ | async as pegsCount" class="pagination-container float-right mt-2" [class]="isLoading || isPegCountLoading ? 'disabled' : ''"
[collectionSize]="pegs.length" [rotate]="true" [maxSize]="maxSize" [pageSize]="15" [(page)]="page" [collectionSize]="pegsCount" [rotate]="true" [maxSize]="maxSize" [pageSize]="15" [(page)]="page"
(pageChange)="pageChange(page)" [boundaryLinks]="true" [ellipses]="false"> (pageChange)="pageChange(page)" [boundaryLinks]="true" [ellipses]="false">
</ngb-pagination> </ngb-pagination>

View file

@ -1,9 +1,9 @@
import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core'; import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core';
import { Observable, Subject, combineLatest, of, timer } from 'rxjs'; import { BehaviorSubject, Observable, Subject, combineLatest, of, timer } from 'rxjs';
import { delayWhen, filter, map, share, shareReplay, switchMap, takeUntil, tap, throttleTime } from 'rxjs/operators'; import { delayWhen, filter, map, share, shareReplay, switchMap, takeUntil, tap, throttleTime } from 'rxjs/operators';
import { ApiService } from '../../../services/api.service'; import { ApiService } from '../../../services/api.service';
import { Env, StateService } from '../../../services/state.service'; import { Env, StateService } from '../../../services/state.service';
import { AuditStatus, CurrentPegs, FederationUtxo, RecentPeg } from '../../../interfaces/node-api.interface'; import { AuditStatus, CurrentPegs, RecentPeg } from '../../../interfaces/node-api.interface';
import { WebsocketService } from '../../../services/websocket.service'; import { WebsocketService } from '../../../services/websocket.service';
import { SeoService } from '../../../services/seo.service'; import { SeoService } from '../../../services/seo.service';
@ -15,21 +15,22 @@ import { SeoService } from '../../../services/seo.service';
}) })
export class RecentPegsListComponent implements OnInit { export class RecentPegsListComponent implements OnInit {
@Input() widget: boolean = false; @Input() widget: boolean = false;
@Input() recentPegIns$: Observable<RecentPeg[]> = of([]); @Input() recentPegsList$: Observable<RecentPeg[]> = of([]);
@Input() recentPegOuts$: Observable<RecentPeg[]> = of([]);
env: Env; env: Env;
isLoading = true; isLoading = true;
isPegCountLoading = true;
page = 1; page = 1;
pageSize = 15; pageSize = 15;
maxSize = window.innerWidth <= 767.98 ? 3 : 5; maxSize = window.innerWidth <= 767.98 ? 3 : 5;
skeletonLines: number[] = []; skeletonLines: number[] = [];
auditStatus$: Observable<AuditStatus>; auditStatus$: Observable<AuditStatus>;
auditUpdated$: Observable<boolean>; auditUpdated$: Observable<boolean>;
federationUtxos$: Observable<FederationUtxo[]>;
recentPegs$: Observable<RecentPeg[]>;
lastReservesBlockUpdate: number = 0; lastReservesBlockUpdate: number = 0;
currentPeg$: Observable<CurrentPegs>; currentPeg$: Observable<CurrentPegs>;
pegsCount$: Observable<number>;
startingIndexSubject: BehaviorSubject<number> = new BehaviorSubject(0);
currentIndex: number = 0;
lastPegBlockUpdate: number = 0; lastPegBlockUpdate: number = 0;
lastPegAmount: string = ''; lastPegAmount: string = '';
isLoad: boolean = true; isLoad: boolean = true;
@ -93,53 +94,36 @@ export class RecentPegsListComponent implements OnInit {
share() share()
); );
this.federationUtxos$ = this.auditUpdated$.pipe( this.pegsCount$ = this.auditUpdated$.pipe(
filter(auditUpdated => auditUpdated === true), filter(auditUpdated => auditUpdated === true),
throttleTime(40000), tap(() => this.isPegCountLoading = true),
switchMap(_ => this.apiService.federationUtxos$()), switchMap(_ => this.apiService.pegsCount$()),
map((data) => data.pegs_count),
tap(() => this.isPegCountLoading = false),
share() share()
); );
this.recentPegIns$ = this.federationUtxos$.pipe( this.recentPegsList$ = combineLatest([
map(federationUtxos => federationUtxos.filter(utxo => utxo.pegtxid).map(utxo => { this.auditStatus$,
return { this.auditUpdated$,
txid: utxo.pegtxid, this.startingIndexSubject
txindex: utxo.pegindex,
amount: utxo.amount,
bitcoinaddress: utxo.bitcoinaddress,
bitcointxid: utxo.txid,
bitcoinindex: utxo.txindex,
blocktime: utxo.pegblocktime,
}
})),
share()
);
this.recentPegOuts$ = this.auditUpdated$.pipe(
filter(auditUpdated => auditUpdated === true),
throttleTime(40000),
switchMap(_ => this.apiService.recentPegOuts$()),
share()
);
}
this.recentPegs$ = combineLatest([
this.recentPegIns$,
this.recentPegOuts$
]).pipe( ]).pipe(
map(([recentPegIns, recentPegOuts]) => { filter(([auditStatus, auditUpdated, startingIndex]) => {
return [ const auditStatusCheck = auditStatus.isAuditSynced === true;
...recentPegIns, const auditUpdatedCheck = auditUpdated === true;
...recentPegOuts const startingIndexCheck = startingIndex !== this.currentIndex;
].sort((a, b) => { return auditStatusCheck && (auditUpdatedCheck || startingIndexCheck);
return b.blocktime - a.blocktime;
});
}), }),
filter(recentPegs => recentPegs.length > 0), tap(([_, __, startingIndex]) => {
tap(_ => this.isLoading = false), this.currentIndex = startingIndex;
this.isLoading = true;
}),
switchMap(([_, __, startingIndex]) => this.apiService.recentPegsList$(startingIndex)),
tap(() => this.isLoading = false),
share() share()
); );
}
} }
ngOnDestroy(): void { ngOnDestroy(): void {
@ -148,6 +132,7 @@ export class RecentPegsListComponent implements OnInit {
} }
pageChange(page: number): void { pageChange(page: number): void {
this.startingIndexSubject.next((page - 1) * 15);
this.page = page; this.page = page;
} }

View file

@ -26,7 +26,7 @@
<div class="card smaller"> <div class="card smaller">
<div class="card-body"> <div class="card-body">
<app-recent-pegs-stats [pegsVolume$]="pegsVolume$"></app-recent-pegs-stats> <app-recent-pegs-stats [pegsVolume$]="pegsVolume$"></app-recent-pegs-stats>
<app-recent-pegs-list [recentPegIns$]="recentPegIns$" [recentPegOuts$]="recentPegOuts$"[widget]="true"></app-recent-pegs-list> <app-recent-pegs-list [recentPegsList$]="recentPegsList$" [widget]="true"></app-recent-pegs-list>
</div> </div>
</div> </div>
</div> </div>

View file

@ -17,9 +17,7 @@ export class ReservesAuditDashboardComponent implements OnInit {
auditUpdated$: Observable<boolean>; auditUpdated$: Observable<boolean>;
currentPeg$: Observable<CurrentPegs>; currentPeg$: Observable<CurrentPegs>;
currentReserves$: Observable<CurrentPegs>; currentReserves$: Observable<CurrentPegs>;
federationUtxos$: Observable<FederationUtxo[]>; recentPegsList$: Observable<RecentPeg[]>;
recentPegIns$: Observable<RecentPeg[]>;
recentPegOuts$: Observable<RecentPeg[]>;
pegsVolume$: Observable<PegsVolume[]>; pegsVolume$: Observable<PegsVolume[]>;
federationAddresses$: Observable<FederationAddress[]>; federationAddresses$: Observable<FederationAddress[]>;
federationAddressesNumber$: Observable<number>; federationAddressesNumber$: Observable<number>;
@ -101,32 +99,10 @@ export class ReservesAuditDashboardComponent implements OnInit {
share() share()
); );
this.federationUtxos$ = this.auditUpdated$.pipe( this.recentPegsList$ = this.auditUpdated$.pipe(
filter(auditUpdated => auditUpdated === true), filter(auditUpdated => auditUpdated === true),
throttleTime(40000), throttleTime(40000),
switchMap(_ => this.apiService.federationUtxos$()), switchMap(_ => this.apiService.recentPegsList$()),
share()
);
this.recentPegIns$ = this.federationUtxos$.pipe(
map(federationUtxos => federationUtxos.filter(utxo => utxo.pegtxid).map(utxo => {
return {
txid: utxo.pegtxid,
txindex: utxo.pegindex,
amount: utxo.amount,
bitcoinaddress: utxo.bitcoinaddress,
bitcointxid: utxo.txid,
bitcoinindex: utxo.txindex,
blocktime: utxo.pegblocktime,
}
})),
share()
);
this.recentPegOuts$ = this.auditUpdated$.pipe(
filter(auditUpdated => auditUpdated === true),
throttleTime(40000),
switchMap(_ => this.apiService.recentPegOuts$()),
share() share()
); );

View file

@ -6,7 +6,7 @@
<div class="card-text"> <div class="card-text">
<div class="fee-text">{{ (+currentPeg.amount) / 100000000 | number: '1.2-2' }} <span>L-BTC</span></div> <div class="fee-text">{{ (+currentPeg.amount) / 100000000 | number: '1.2-2' }} <span>L-BTC</span></div>
<span class="fiat"> <span class="fiat">
<span>As of block&nbsp;<a [routerLink]="['/block', currentPeg.hash]">{{ currentPeg.lastBlockUpdate }}</a></span> <span><ng-container i18n="shared.as-of-block">As of block</ng-container>&nbsp;<a [routerLink]="['/block', currentPeg.hash]">{{ currentPeg.lastBlockUpdate }}</a></span>
</span> </span>
</div> </div>
</div> </div>
@ -15,7 +15,7 @@
<div class="card-text"> <div class="card-text">
<div class="fee-text">{{ (+currentReserves.amount) / 100000000 | number: '1.2-2' }} <span style="color: #b86d12;">BTC</span></div> <div class="fee-text">{{ (+currentReserves.amount) / 100000000 | number: '1.2-2' }} <span style="color: #b86d12;">BTC</span></div>
<span class="fiat"> <span class="fiat">
<span>As of block&nbsp;<a href="{{ env.MEMPOOL_WEBSITE_URL + '/block/' + currentReserves.hash }}" target="_blank" style="color:#b86d12">{{ currentReserves.lastBlockUpdate }}</a></span> <span><ng-container i18n="shared.as-of-block">As of block</ng-container>&nbsp;<a href="{{ env.MEMPOOL_WEBSITE_URL + '/block/' + currentReserves.hash }}" target="_blank" style="color:#b86d12">{{ currentReserves.lastBlockUpdate }}</a></span>
</span> </span>
</div> </div>
</div> </div>
@ -33,7 +33,7 @@
</div> </div>
</div> </div>
<div class="item"> <div class="item">
<h5 class="card-title" i18n="dashboard.btc-reserves">BTC Reserves</h5> <h5 class="card-title" i18n="dashboard.btc-holdings">BTC Holdings</h5>
<div class="card-text"> <div class="card-text">
<div class="skeleton-loader"></div> <div class="skeleton-loader"></div>
<div class="skeleton-loader"></div> <div class="skeleton-loader"></div>

View file

@ -200,8 +200,12 @@ export class ApiService {
return this.httpClient.get<FederationUtxo[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/reserves/utxos'); return this.httpClient.get<FederationUtxo[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/reserves/utxos');
} }
recentPegOuts$(): Observable<RecentPeg[]> { recentPegsList$(count: number = 0): Observable<RecentPeg[]> {
return this.httpClient.get<RecentPeg[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/pegouts'); return this.httpClient.get<RecentPeg[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/pegs/list/' + count);
}
pegsCount$(): Observable<any> {
return this.httpClient.get<number>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/pegs/count');
} }
federationAddressesNumber$(): Observable<any> { federationAddressesNumber$(): Observable<any> {