mirror of
https://github.com/mempool/mempool.git
synced 2025-01-18 21:32:55 +01:00
Liquid: Add support for peg outs display
This commit is contained in:
parent
639fc3dd5f
commit
73e6045549
@ -163,6 +163,10 @@ class ElementsParser {
|
||||
unspentAsTip = [];
|
||||
}
|
||||
|
||||
// Get the peg-out addresses that need to be scanned
|
||||
const redeemAddresses = await this.$getRedeemAddressesToScan();
|
||||
if (redeemAddresses.length > 0) logger.debug(`Found ${redeemAddresses.length} peg-out addresses to scan`);
|
||||
|
||||
// The slow way: parse the block to look for the spending tx
|
||||
logger.debug(`${spentAsTip.length} / ${utxos.length} Federation UTXOs are spent as of tip`);
|
||||
|
||||
@ -170,7 +174,7 @@ class ElementsParser {
|
||||
const block: IBitcoinApi.Block = await bitcoinSecondClient.getBlock(blockHash, 2);
|
||||
const nbUtxos = spentAsTip.length;
|
||||
await DB.query('START TRANSACTION;');
|
||||
await this.$parseBitcoinBlock(block, spentAsTip, unspentAsTip, auditProgress.confirmedTip);
|
||||
await this.$parseBitcoinBlock(block, spentAsTip, unspentAsTip, auditProgress.confirmedTip, redeemAddresses);
|
||||
await DB.query(`COMMIT;`);
|
||||
logger.debug(`Watched for spending of ${nbUtxos} Federation UTXOs in block ${auditProgress.lastBlockAudit} / ${auditProgress.confirmedTip}`);
|
||||
|
||||
@ -210,12 +214,14 @@ class ElementsParser {
|
||||
return {spentAsTip, unspentAsTip};
|
||||
}
|
||||
|
||||
protected async $parseBitcoinBlock(block: IBitcoinApi.Block, spentAsTip: any[], unspentAsTip: any[], confirmedTip: number) {
|
||||
protected async $parseBitcoinBlock(block: IBitcoinApi.Block, spentAsTip: any[], unspentAsTip: any[], confirmedTip: number, redeemAddresses: string[] = []) {
|
||||
let mightRedeemInThisBlock = false; // If a Federation UTXO is spent in this block, we might find a peg-out address in the outputs...
|
||||
for (const tx of block.tx) {
|
||||
// Check if the Federation UTXOs that was spent as of tip are spent in this block
|
||||
for (const input of tx.vin) {
|
||||
const txo = spentAsTip.find(txo => txo.txid === input.txid && txo.txindex === input.vout);
|
||||
if (txo) {
|
||||
mightRedeemInThisBlock = true;
|
||||
await DB.query(`UPDATE federation_txos SET unspent = 0, lastblockupdate = ?, lasttimeupdate = ? WHERE txid = ? AND txindex = ?`, [block.height, block.time, txo.txid, txo.txindex]);
|
||||
// Remove the TXO from the utxo array
|
||||
spentAsTip.splice(spentAsTip.indexOf(txo), 1);
|
||||
@ -241,6 +247,13 @@ class ElementsParser {
|
||||
logger.debug(`Added new Federation UTXO ${tx.txid}:${output.n} of ${output.value * 100000000} sats belonging to ${output.scriptPubKey.address} (Federation change address).`);
|
||||
}
|
||||
}
|
||||
if (mightRedeemInThisBlock && output.scriptPubKey.address && redeemAddresses.includes(output.scriptPubKey.address)) {
|
||||
const query_add_redeem = `UPDATE elements_pegs SET bitcointxid = ?, bitcoinindex = ? WHERE bitcoinaddress = ?`;
|
||||
const params_add_redeem: (string | number)[] = [tx.txid, output.n, output.scriptPubKey.address];
|
||||
await DB.query(query_add_redeem, params_add_redeem);
|
||||
redeemAddresses.splice(redeemAddresses.indexOf(output.scriptPubKey.address), 1);
|
||||
logger.debug(`Added redeem txid ${tx.txid}:${output.n} to peg-out address ${output.scriptPubKey.address}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -283,9 +296,15 @@ class ElementsParser {
|
||||
return rows[0]['number'];
|
||||
}
|
||||
|
||||
///////////// DATA QUERY //////////////
|
||||
protected async $getRedeemAddressesToScan(): Promise<string[]> {
|
||||
const query = `SELECT bitcoinaddress FROM elements_pegs where amount < 0 AND bitcoinaddress != '' AND bitcointxid = '';`;
|
||||
const [rows]: any[] = await DB.query(query);
|
||||
return rows.map((row: any) => row.bitcoinaddress);
|
||||
}
|
||||
|
||||
public async $getAuditStatus(): Promise<any> {
|
||||
///////////// DATA QUERY //////////////
|
||||
|
||||
public async $getAuditStatus(): Promise<any> {
|
||||
const lastBlockAudit = await this.$getLastBlockAudit();
|
||||
const bitcoinBlocksToSync = await this.$getBitcoinBlockchainState();
|
||||
return {
|
||||
@ -382,6 +401,12 @@ class ElementsParser {
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
// Get the 300 most recent pegouts from the federation
|
||||
public async $getRecentPegouts(): Promise<any> {
|
||||
const query = `SELECT txid, txindex, amount, bitcoinaddress, bitcointxid, bitcoinindex, datetime AS blocktime FROM elements_pegs WHERE amount < 0 ORDER BY blocktime DESC LIMIT 300;`;
|
||||
const [rows] = await DB.query(query);
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
|
||||
export default new ElementsParser();
|
||||
|
@ -19,6 +19,7 @@ class LiquidRoutes {
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs/month', this.$getElementsPegsByMonth)
|
||||
.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/pegouts', this.$getPegOuts)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/addresses', this.$getFederationAddresses)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/addresses/previous-month', this.$getFederationAddressesOneMonthAgo)
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/utxos', this.$getFederationUtxos)
|
||||
@ -176,6 +177,18 @@ class LiquidRoutes {
|
||||
}
|
||||
}
|
||||
|
||||
private async $getPegOuts(req: Request, res: Response) {
|
||||
try {
|
||||
const recentPegOuts = await elementsParser.$getRecentPegouts();
|
||||
res.header('Pragma', 'public');
|
||||
res.header('Cache-control', 'public');
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
|
||||
res.json(recentPegOuts);
|
||||
} catch (e) {
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new LiquidRoutes();
|
||||
|
@ -15,6 +15,7 @@ import { WebsocketService } from '../../../services/websocket.service';
|
||||
export class RecentPegsListComponent implements OnInit {
|
||||
@Input() widget: boolean = false;
|
||||
@Input() recentPegIns$: Observable<RecentPeg[]>;
|
||||
@Input() recentPegOuts$: Observable<RecentPeg[]>;
|
||||
|
||||
env: Env;
|
||||
isLoading = true;
|
||||
@ -103,6 +104,7 @@ export class RecentPegsListComponent implements OnInit {
|
||||
txid: utxo.pegtxid,
|
||||
txindex: utxo.pegindex,
|
||||
amount: utxo.amount,
|
||||
bitcoinaddress: utxo.bitcoinaddress,
|
||||
bitcointxid: utxo.txid,
|
||||
bitcoinindex: utxo.txindex,
|
||||
blocktime: utxo.pegblocktime,
|
||||
@ -110,9 +112,30 @@ export class RecentPegsListComponent implements OnInit {
|
||||
})),
|
||||
share()
|
||||
);
|
||||
|
||||
this.recentPegOuts$ = this.auditUpdated$.pipe(
|
||||
filter(auditUpdated => auditUpdated === true),
|
||||
throttleTime(40000),
|
||||
switchMap(_ => this.apiService.recentPegOuts$()),
|
||||
share()
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
this.recentPegs$ = this.recentPegIns$;
|
||||
this.recentPegs$ = combineLatest([
|
||||
this.recentPegIns$,
|
||||
this.recentPegOuts$
|
||||
]).pipe(
|
||||
map(([recentPegIns, recentPegOuts]) => {
|
||||
return [
|
||||
...recentPegIns,
|
||||
...recentPegOuts
|
||||
].sort((a, b) => {
|
||||
return b.blocktime - a.blocktime;
|
||||
});
|
||||
}),
|
||||
share()
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
|
@ -26,7 +26,7 @@
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<app-recent-pegs-stats></app-recent-pegs-stats>
|
||||
<app-recent-pegs-list [recentPegIns$]="recentPegIns$" [widget]="true"></app-recent-pegs-list>
|
||||
<app-recent-pegs-list [recentPegIns$]="recentPegIns$" [recentPegOuts$]="recentPegOuts$"[widget]="true"></app-recent-pegs-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -19,6 +19,7 @@ export class ReservesAuditDashboardComponent implements OnInit {
|
||||
currentReserves$: Observable<CurrentPegs>;
|
||||
federationUtxos$: Observable<FederationUtxo[]>;
|
||||
recentPegIns$: Observable<RecentPeg[]>;
|
||||
recentPegOuts$: Observable<RecentPeg[]>;
|
||||
federationAddresses$: Observable<FederationAddress[]>;
|
||||
federationAddressesOneMonthAgo$: Observable<any>;
|
||||
liquidPegsMonth$: Observable<any>;
|
||||
@ -110,6 +111,7 @@ export class ReservesAuditDashboardComponent implements OnInit {
|
||||
txid: utxo.pegtxid,
|
||||
txindex: utxo.pegindex,
|
||||
amount: utxo.amount,
|
||||
bitcoinaddress: utxo.bitcoinaddress,
|
||||
bitcointxid: utxo.txid,
|
||||
bitcoinindex: utxo.txindex,
|
||||
blocktime: utxo.pegblocktime,
|
||||
@ -118,6 +120,13 @@ export class ReservesAuditDashboardComponent implements OnInit {
|
||||
share()
|
||||
);
|
||||
|
||||
this.recentPegOuts$ = this.auditUpdated$.pipe(
|
||||
filter(auditUpdated => auditUpdated === true),
|
||||
throttleTime(40000),
|
||||
switchMap(_ => this.apiService.recentPegOuts$()),
|
||||
share()
|
||||
);
|
||||
|
||||
this.federationAddresses$ = this.auditUpdated$.pipe(
|
||||
filter(auditUpdated => auditUpdated === true),
|
||||
throttleTime(40000),
|
||||
|
@ -101,8 +101,9 @@ export interface FederationUtxo {
|
||||
|
||||
export interface RecentPeg {
|
||||
txid: string;
|
||||
txindex: number; // input #0 for peg-ins
|
||||
txindex: number;
|
||||
amount: number;
|
||||
bitcoinaddress: string;
|
||||
bitcointxid: string;
|
||||
bitcoinindex: number;
|
||||
blocktime: number;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http';
|
||||
import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators,
|
||||
PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights, RbfTree, BlockAudit, Acceleration, AccelerationHistoryParams, CurrentPegs, AuditStatus, FederationAddress, FederationUtxo } from '../interfaces/node-api.interface';
|
||||
PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights, RbfTree, BlockAudit, Acceleration, AccelerationHistoryParams, CurrentPegs, AuditStatus, FederationAddress, FederationUtxo, RecentPeg } from '../interfaces/node-api.interface';
|
||||
import { BehaviorSubject, Observable, catchError, filter, of, shareReplay, take, tap } from 'rxjs';
|
||||
import { StateService } from './state.service';
|
||||
import { IBackendInfo, WebsocketResponse } from '../interfaces/websocket.interface';
|
||||
@ -206,6 +206,10 @@ export class ApiService {
|
||||
return this.httpClient.get<FederationUtxo[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/reserves/utxos');
|
||||
}
|
||||
|
||||
recentPegOuts$(): Observable<RecentPeg[]> {
|
||||
return this.httpClient.get<RecentPeg[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/pegouts');
|
||||
}
|
||||
|
||||
federationAddressesOneMonthAgo$(): Observable<any> {
|
||||
return this.httpClient.get<FederationAddress[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/reserves/addresses/previous-month');
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user