From 84b0375c0c6fe3356b7ddba8b07782ec96ba8b48 Mon Sep 17 00:00:00 2001 From: softsimon Date: Tue, 27 Apr 2021 02:13:48 +0400 Subject: [PATCH] Fix crash issues related to new unconfirmed transactions in bitcoind mode. fixes #391 --- backend/src/api/bitcoin/bitcoin-api.ts | 3 ++ backend/src/api/websocket-handler.ts | 13 ++++- .../transaction/transaction.component.ts | 51 ++++++++++++------- 3 files changed, 47 insertions(+), 20 deletions(-) diff --git a/backend/src/api/bitcoin/bitcoin-api.ts b/backend/src/api/bitcoin/bitcoin-api.ts index 2abebeb07..48119d709 100644 --- a/backend/src/api/bitcoin/bitcoin-api.ts +++ b/backend/src/api/bitcoin/bitcoin-api.ts @@ -147,6 +147,9 @@ class BitcoinApi implements AbstractBitcoinApi { esploraTransaction = await this.$calculateFeeFromInputs(esploraTransaction, addPrevout); } else { esploraTransaction = await this.$appendMempoolFeeData(esploraTransaction); + if (addPrevout) { + esploraTransaction = await this.$calculateFeeFromInputs(esploraTransaction, addPrevout); + } } return esploraTransaction; diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 74a06a588..135411a3d 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -34,7 +34,7 @@ class WebsocketHandler { this.wss.on('connection', (client: WebSocket) => { client.on('error', logger.info); - client.on('message', (message: string) => { + client.on('message', async (message: string) => { try { const parsedMessage: WebsocketResponse = JSON.parse(message); const response = {}; @@ -53,7 +53,16 @@ class WebsocketHandler { if (parsedMessage['watch-mempool']) { const tx = memPool.getMempool()[client['track-tx']]; if (tx) { - response['tx'] = tx; + if (config.MEMPOOL.BACKEND !== 'esplora') { + try { + const fullTx = await transactionUtils.$getTransactionExtended(tx.txid, true); + response['tx'] = fullTx; + } catch (e) { + logger.debug('Error finding transaction in mempool: ' + e.message || e); + } + } else { + response['tx'] = tx; + } } else { client['track-mempool-tx'] = parsedMessage['track-tx']; } diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index 770bcf993..65a8acc86 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -1,9 +1,9 @@ import { Component, OnInit, OnDestroy } from '@angular/core'; import { ElectrsApiService } from '../../services/electrs-api.service'; import { ActivatedRoute, ParamMap } from '@angular/router'; -import { switchMap, filter, catchError } from 'rxjs/operators'; +import { switchMap, filter, catchError, retryWhen, delay } from 'rxjs/operators'; import { Transaction, Block } from '../../interfaces/electrs.interface'; -import { of, merge, Subscription, Observable } from 'rxjs'; +import { of, merge, Subscription, Observable, Subject } from 'rxjs'; import { StateService } from '../../services/state.service'; import { WebsocketService } from '../../services/websocket.service'; import { AudioService } from 'src/app/services/audio.service'; @@ -27,9 +27,11 @@ export class TransactionComponent implements OnInit, OnDestroy { latestBlock: Block; transactionTime = -1; subscription: Subscription; + fetchCpfpSubscription: Subscription; rbfTransaction: undefined | Transaction; cpfpInfo: CpfpInfo | null; showCpfpDetails = false; + fetchCpfp$ = new Subject(); constructor( private route: ActivatedRoute, @@ -99,28 +101,14 @@ export class TransactionComponent implements OnInit, OnDestroy { if (this.tx.status.confirmed) { this.stateService.markBlock$.next({ blockHeight: tx.status.block_height }); } else { - if (tx.effectiveFeePerVsize) { + if (tx.cpfpChecked) { this.stateService.markBlock$.next({ txFeePerVSize: tx.effectiveFeePerVsize }); this.cpfpInfo = { ancestors: tx.ancestors, bestDescendant: tx.bestDescendant, }; } else { - this.apiService.getCpfpinfo$(this.tx.txid) - .subscribe((cpfpInfo) => { - const lowerFeeParents = cpfpInfo.ancestors.filter((parent) => (parent.fee / (parent.weight / 4)) < tx.feePerVsize); - let totalWeight = tx.weight + lowerFeeParents.reduce((prev, val) => prev + val.weight, 0); - let totalFees = tx.fee + lowerFeeParents.reduce((prev, val) => prev + val.fee, 0); - - if (cpfpInfo.bestDescendant) { - totalWeight += cpfpInfo.bestDescendant.weight; - totalFees += cpfpInfo.bestDescendant.fee; - } - - this.tx.effectiveFeePerVsize = totalFees / (totalWeight / 4); - this.stateService.markBlock$.next({ txFeePerVSize: this.tx.effectiveFeePerVsize }); - this.cpfpInfo = cpfpInfo; - }); + this.fetchCpfp$.next(this.tx.txid); } } }, @@ -129,6 +117,32 @@ export class TransactionComponent implements OnInit, OnDestroy { this.isLoadingTx = false; }); + this.fetchCpfpSubscription = this.fetchCpfp$ + .pipe( + switchMap((txId) => this.apiService.getCpfpinfo$(txId) + .pipe( + retryWhen((errors) => errors.pipe(delay(2000))) + ) + ), + ) + .subscribe((cpfpInfo) => { + if (!this.tx) { + return; + } + const lowerFeeParents = cpfpInfo.ancestors.filter((parent) => (parent.fee / (parent.weight / 4)) < this.tx.feePerVsize); + let totalWeight = this.tx.weight + lowerFeeParents.reduce((prev, val) => prev + val.weight, 0); + let totalFees = this.tx.fee + lowerFeeParents.reduce((prev, val) => prev + val.fee, 0); + + if (cpfpInfo.bestDescendant) { + totalWeight += cpfpInfo.bestDescendant.weight; + totalFees += cpfpInfo.bestDescendant.fee; + } + + this.tx.effectiveFeePerVsize = totalFees / (totalWeight / 4); + this.stateService.markBlock$.next({ txFeePerVSize: this.tx.effectiveFeePerVsize }); + this.cpfpInfo = cpfpInfo; + }); + this.stateService.blocks$ .subscribe(([block, txConfirmed]) => { this.latestBlock = block; @@ -209,6 +223,7 @@ export class TransactionComponent implements OnInit, OnDestroy { ngOnDestroy() { this.subscription.unsubscribe(); + this.fetchCpfpSubscription.unsubscribe(); this.leaveTransaction(); } }