Merge pull request #474 from mempool/simon/bitcoind-tx-push-break-fix

Fix crash issues related to new unconfirmed transactions in bitcoind …
This commit is contained in:
wiz 2021-04-27 17:44:19 +09:00 committed by GitHub
commit 1000f4dd4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 47 additions and 20 deletions

View File

@ -147,6 +147,9 @@ class BitcoinApi implements AbstractBitcoinApi {
esploraTransaction = await this.$calculateFeeFromInputs(esploraTransaction, addPrevout); esploraTransaction = await this.$calculateFeeFromInputs(esploraTransaction, addPrevout);
} else { } else {
esploraTransaction = await this.$appendMempoolFeeData(esploraTransaction); esploraTransaction = await this.$appendMempoolFeeData(esploraTransaction);
if (addPrevout) {
esploraTransaction = await this.$calculateFeeFromInputs(esploraTransaction, addPrevout);
}
} }
return esploraTransaction; return esploraTransaction;

View File

@ -34,7 +34,7 @@ class WebsocketHandler {
this.wss.on('connection', (client: WebSocket) => { this.wss.on('connection', (client: WebSocket) => {
client.on('error', logger.info); client.on('error', logger.info);
client.on('message', (message: string) => { client.on('message', async (message: string) => {
try { try {
const parsedMessage: WebsocketResponse = JSON.parse(message); const parsedMessage: WebsocketResponse = JSON.parse(message);
const response = {}; const response = {};
@ -53,7 +53,16 @@ class WebsocketHandler {
if (parsedMessage['watch-mempool']) { if (parsedMessage['watch-mempool']) {
const tx = memPool.getMempool()[client['track-tx']]; const tx = memPool.getMempool()[client['track-tx']];
if (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 { } else {
client['track-mempool-tx'] = parsedMessage['track-tx']; client['track-mempool-tx'] = parsedMessage['track-tx'];
} }

View File

@ -1,9 +1,9 @@
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Component, OnInit, OnDestroy } from '@angular/core';
import { ElectrsApiService } from '../../services/electrs-api.service'; import { ElectrsApiService } from '../../services/electrs-api.service';
import { ActivatedRoute, ParamMap } from '@angular/router'; 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 { 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 { StateService } from '../../services/state.service';
import { WebsocketService } from '../../services/websocket.service'; import { WebsocketService } from '../../services/websocket.service';
import { AudioService } from 'src/app/services/audio.service'; import { AudioService } from 'src/app/services/audio.service';
@ -27,9 +27,11 @@ export class TransactionComponent implements OnInit, OnDestroy {
latestBlock: Block; latestBlock: Block;
transactionTime = -1; transactionTime = -1;
subscription: Subscription; subscription: Subscription;
fetchCpfpSubscription: Subscription;
rbfTransaction: undefined | Transaction; rbfTransaction: undefined | Transaction;
cpfpInfo: CpfpInfo | null; cpfpInfo: CpfpInfo | null;
showCpfpDetails = false; showCpfpDetails = false;
fetchCpfp$ = new Subject<string>();
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
@ -99,28 +101,14 @@ export class TransactionComponent implements OnInit, OnDestroy {
if (this.tx.status.confirmed) { if (this.tx.status.confirmed) {
this.stateService.markBlock$.next({ blockHeight: tx.status.block_height }); this.stateService.markBlock$.next({ blockHeight: tx.status.block_height });
} else { } else {
if (tx.effectiveFeePerVsize) { if (tx.cpfpChecked) {
this.stateService.markBlock$.next({ txFeePerVSize: tx.effectiveFeePerVsize }); this.stateService.markBlock$.next({ txFeePerVSize: tx.effectiveFeePerVsize });
this.cpfpInfo = { this.cpfpInfo = {
ancestors: tx.ancestors, ancestors: tx.ancestors,
bestDescendant: tx.bestDescendant, bestDescendant: tx.bestDescendant,
}; };
} else { } else {
this.apiService.getCpfpinfo$(this.tx.txid) this.fetchCpfp$.next(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;
});
} }
} }
}, },
@ -129,6 +117,32 @@ export class TransactionComponent implements OnInit, OnDestroy {
this.isLoadingTx = false; 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$ this.stateService.blocks$
.subscribe(([block, txConfirmed]) => { .subscribe(([block, txConfirmed]) => {
this.latestBlock = block; this.latestBlock = block;
@ -209,6 +223,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
ngOnDestroy() { ngOnDestroy() {
this.subscription.unsubscribe(); this.subscription.unsubscribe();
this.fetchCpfpSubscription.unsubscribe();
this.leaveTransaction(); this.leaveTransaction();
} }
} }