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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user