Compute decoded tx CPFP data in the backend
This commit is contained in:
parent
d852c48370
commit
2987f86cd3
@ -12,14 +12,14 @@ import backendInfo from '../backend-info';
|
|||||||
import transactionUtils from '../transaction-utils';
|
import transactionUtils from '../transaction-utils';
|
||||||
import { IEsploraApi } from './esplora-api.interface';
|
import { IEsploraApi } from './esplora-api.interface';
|
||||||
import loadingIndicators from '../loading-indicators';
|
import loadingIndicators from '../loading-indicators';
|
||||||
import { MempoolTransactionExtended, TransactionExtended } from '../../mempool.interfaces';
|
import { TransactionExtended } from '../../mempool.interfaces';
|
||||||
import logger from '../../logger';
|
import logger from '../../logger';
|
||||||
import blocks from '../blocks';
|
import blocks from '../blocks';
|
||||||
import bitcoinClient from './bitcoin-client';
|
import bitcoinClient from './bitcoin-client';
|
||||||
import difficultyAdjustment from '../difficulty-adjustment';
|
import difficultyAdjustment from '../difficulty-adjustment';
|
||||||
import transactionRepository from '../../repositories/TransactionRepository';
|
import transactionRepository from '../../repositories/TransactionRepository';
|
||||||
import rbfCache from '../rbf-cache';
|
import rbfCache from '../rbf-cache';
|
||||||
import { calculateMempoolTxCpfp } from '../cpfp';
|
import { calculateMempoolTxCpfp, calculateLocalTxCpfp } from '../cpfp';
|
||||||
import { handleError } from '../../utils/api';
|
import { handleError } from '../../utils/api';
|
||||||
|
|
||||||
class BitcoinRoutes {
|
class BitcoinRoutes {
|
||||||
@ -50,6 +50,7 @@ class BitcoinRoutes {
|
|||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks-bulk/:from', this.getBlocksByBulk.bind(this))
|
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks-bulk/:from', this.getBlocksByBulk.bind(this))
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks-bulk/:from/:to', this.getBlocksByBulk.bind(this))
|
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks-bulk/:from/:to', this.getBlocksByBulk.bind(this))
|
||||||
.post(config.MEMPOOL.API_URL_PREFIX + 'prevouts', this.$getPrevouts)
|
.post(config.MEMPOOL.API_URL_PREFIX + 'prevouts', this.$getPrevouts)
|
||||||
|
.post(config.MEMPOOL.API_URL_PREFIX + 'cpfp', this.getCpfpLocalTx)
|
||||||
// Temporarily add txs/package endpoint for all backends until esplora supports it
|
// Temporarily add txs/package endpoint for all backends until esplora supports it
|
||||||
.post(config.MEMPOOL.API_URL_PREFIX + 'txs/package', this.$submitPackage)
|
.post(config.MEMPOOL.API_URL_PREFIX + 'txs/package', this.$submitPackage)
|
||||||
;
|
;
|
||||||
@ -829,11 +830,11 @@ class BitcoinRoutes {
|
|||||||
try {
|
try {
|
||||||
const outpoints = req.body;
|
const outpoints = req.body;
|
||||||
if (!Array.isArray(outpoints) || outpoints.some((item) => !/^[a-fA-F0-9]{64}$/.test(item.txid) || typeof item.vout !== 'number')) {
|
if (!Array.isArray(outpoints) || outpoints.some((item) => !/^[a-fA-F0-9]{64}$/.test(item.txid) || typeof item.vout !== 'number')) {
|
||||||
return res.status(400).json({ error: 'Invalid input format' });
|
return res.status(400).json({ message: 'Invalid input format' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outpoints.length > 100) {
|
if (outpoints.length > 100) {
|
||||||
return res.status(400).json({ error: 'Too many prevouts requested' });
|
return res.status(400).json({ message: 'Too many prevouts requested' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = Array(outpoints.length).fill(null);
|
const result = Array(outpoints.length).fill(null);
|
||||||
@ -842,12 +843,14 @@ class BitcoinRoutes {
|
|||||||
for (let i = 0; i < outpoints.length; i++) {
|
for (let i = 0; i < outpoints.length; i++) {
|
||||||
const outpoint = outpoints[i];
|
const outpoint = outpoints[i];
|
||||||
let prevout: IEsploraApi.Vout | null = null;
|
let prevout: IEsploraApi.Vout | null = null;
|
||||||
let tx: MempoolTransactionExtended | null = null;
|
let unconfirmed: boolean | null = null;
|
||||||
|
|
||||||
const mempoolTx = memPool[outpoint.txid];
|
const mempoolTx = memPool[outpoint.txid];
|
||||||
if (mempoolTx) {
|
if (mempoolTx) {
|
||||||
|
if (outpoint.vout < mempoolTx.vout.length) {
|
||||||
prevout = mempoolTx.vout[outpoint.vout];
|
prevout = mempoolTx.vout[outpoint.vout];
|
||||||
tx = mempoolTx;
|
unconfirmed = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const rawPrevout = await bitcoinClient.getTxOut(outpoint.txid, outpoint.vout, false);
|
const rawPrevout = await bitcoinClient.getTxOut(outpoint.txid, outpoint.vout, false);
|
||||||
if (rawPrevout) {
|
if (rawPrevout) {
|
||||||
@ -858,11 +861,12 @@ class BitcoinRoutes {
|
|||||||
scriptpubkey_type: transactionUtils.translateScriptPubKeyType(rawPrevout.scriptPubKey.type),
|
scriptpubkey_type: transactionUtils.translateScriptPubKeyType(rawPrevout.scriptPubKey.type),
|
||||||
scriptpubkey_address: rawPrevout.scriptPubKey && rawPrevout.scriptPubKey.address ? rawPrevout.scriptPubKey.address : '',
|
scriptpubkey_address: rawPrevout.scriptPubKey && rawPrevout.scriptPubKey.address ? rawPrevout.scriptPubKey.address : '',
|
||||||
};
|
};
|
||||||
|
unconfirmed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevout) {
|
if (prevout) {
|
||||||
result[i] = { prevout, tx };
|
result[i] = { prevout, unconfirmed };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -872,6 +876,30 @@ class BitcoinRoutes {
|
|||||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getCpfpLocalTx(req: Request, res: Response) {
|
||||||
|
try {
|
||||||
|
const tx = req.body;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!tx || typeof tx !== "object" ||
|
||||||
|
!tx.txid || typeof tx.txid !== "string" ||
|
||||||
|
typeof tx.weight !== "number" ||
|
||||||
|
typeof tx.sigops !== "number" ||
|
||||||
|
typeof tx.fee !== "number" ||
|
||||||
|
!Array.isArray(tx.vin) ||
|
||||||
|
!Array.isArray(tx.vout)
|
||||||
|
) {
|
||||||
|
return res.status(400).json({ message: 'Invalid transaction format: missing or incorrect fields' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const cpfpInfo = calculateLocalTxCpfp(tx, mempool.getMempool());
|
||||||
|
res.json(cpfpInfo);
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new BitcoinRoutes();
|
export default new BitcoinRoutes();
|
||||||
|
@ -222,6 +222,34 @@ export function calculateMempoolTxCpfp(tx: MempoolTransactionExtended, mempool:
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes an unbroadcasted transaction and a copy of the current mempool, and calculates an estimate
|
||||||
|
* of the CPFP data if the transaction were to enter the mempool. This only returns potential ancerstors
|
||||||
|
* and effective fee rate, and does not update the CPFP data of other transactions in the cluster.
|
||||||
|
*/
|
||||||
|
export function calculateLocalTxCpfp(tx: MempoolTransactionExtended, mempool: { [txid: string]: MempoolTransactionExtended }): CpfpInfo {
|
||||||
|
const ancestorMap = new Map<string, GraphTx>();
|
||||||
|
const graphTx = convertToGraphTx(tx, memPool.getSpendMap());
|
||||||
|
ancestorMap.set(tx.txid, graphTx);
|
||||||
|
|
||||||
|
const allRelatives = expandRelativesGraph(mempool, ancestorMap, memPool.getSpendMap());
|
||||||
|
const relativesMap = initializeRelatives(allRelatives);
|
||||||
|
const cluster = calculateCpfpCluster(tx.txid, relativesMap);
|
||||||
|
|
||||||
|
let totalVsize = 0;
|
||||||
|
let totalFee = 0;
|
||||||
|
for (const tx of cluster.values()) {
|
||||||
|
totalVsize += tx.vsize;
|
||||||
|
totalFee += tx.fees.base;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
ancestors: Array.from(cluster.get(tx.txid)?.ancestors.values() || []).map(ancestor => ({ txid: ancestor.txid, weight: ancestor.weight, fee: ancestor.fees.base })),
|
||||||
|
effectiveFeePerVsize: totalFee / totalVsize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a root transaction and a list of in-mempool ancestors,
|
* Given a root transaction and a list of in-mempool ancestors,
|
||||||
* Calculate the CPFP cluster
|
* Calculate the CPFP cluster
|
||||||
|
@ -51,6 +51,12 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if (errorCpfpInfo) {
|
||||||
|
<div class="alert alert-mempool">
|
||||||
|
<span><strong>Error loading CPFP data</strong>. Reason: {{ errorCpfpInfo }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<app-transaction-details
|
<app-transaction-details
|
||||||
[network]="stateService.network"
|
[network]="stateService.network"
|
||||||
[tx]="transaction"
|
[tx]="transaction"
|
||||||
@ -61,11 +67,14 @@
|
|||||||
[filters]="filters"
|
[filters]="filters"
|
||||||
[hasEffectiveFeeRate]="false"
|
[hasEffectiveFeeRate]="false"
|
||||||
[cpfpInfo]="null"
|
[cpfpInfo]="null"
|
||||||
[hasCpfp]="false"
|
|
||||||
[ETA$]="ETA$"
|
[ETA$]="ETA$"
|
||||||
|
[hasEffectiveFeeRate]="hasEffectiveFeeRate"
|
||||||
|
[cpfpInfo]="cpfpInfo"
|
||||||
|
[hasCpfp]="hasCpfp"
|
||||||
(toggleCpfp$)="this.showCpfpDetails = !this.showCpfpDetails"
|
(toggleCpfp$)="this.showCpfpDetails = !this.showCpfpDetails"
|
||||||
></app-transaction-details>
|
></app-transaction-details>
|
||||||
|
|
||||||
|
<app-cpfp-info *ngIf="showCpfpDetails" [cpfpInfo]="cpfpInfo" [tx]="transaction"></app-cpfp-info>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<ng-container *ngIf="flowEnabled; else flowPlaceholder">
|
<ng-container *ngIf="flowEnabled; else flowPlaceholder">
|
||||||
@ -188,7 +197,9 @@
|
|||||||
@if (isLoading) {
|
@if (isLoading) {
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="spinner-border text-light mt-2 mb-2"></div>
|
<div class="spinner-border text-light mt-2 mb-2"></div>
|
||||||
<h3 i18n="transaction.error.loading-prevouts">Loading transaction prevouts</h3>
|
<h3 i18n="transaction.error.loading-prevouts">
|
||||||
|
Loading {{ isLoadingPrevouts ? 'transaction prevouts' : isLoadingCpfpInfo ? 'CPFP' : '' }}
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
@ -13,6 +13,7 @@ import { SeoService } from '../../services/seo.service';
|
|||||||
import { seoDescriptionNetwork } from '@app/shared/common.utils';
|
import { seoDescriptionNetwork } from '@app/shared/common.utils';
|
||||||
import { ApiService } from '../../services/api.service';
|
import { ApiService } from '../../services/api.service';
|
||||||
import { RelativeUrlPipe } from '@app/shared/pipes/relative-url/relative-url.pipe';
|
import { RelativeUrlPipe } from '@app/shared/pipes/relative-url/relative-url.pipe';
|
||||||
|
import { CpfpInfo } from '../../interfaces/node-api.interface';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-transaction-raw',
|
selector: 'app-transaction-raw',
|
||||||
@ -23,10 +24,13 @@ export class TransactionRawComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
pushTxForm: UntypedFormGroup;
|
pushTxForm: UntypedFormGroup;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
isLoadingPrevouts: boolean;
|
||||||
|
isLoadingCpfpInfo: boolean;
|
||||||
offlineMode: boolean = false;
|
offlineMode: boolean = false;
|
||||||
transaction: Transaction;
|
transaction: Transaction;
|
||||||
error: string;
|
error: string;
|
||||||
errorPrevouts: string;
|
errorPrevouts: string;
|
||||||
|
errorCpfpInfo: string;
|
||||||
hasPrevouts: boolean;
|
hasPrevouts: boolean;
|
||||||
missingPrevouts: string[];
|
missingPrevouts: string[];
|
||||||
isLoadingBroadcast: boolean;
|
isLoadingBroadcast: boolean;
|
||||||
@ -46,6 +50,10 @@ export class TransactionRawComponent implements OnInit, OnDestroy {
|
|||||||
flowEnabled: boolean;
|
flowEnabled: boolean;
|
||||||
adjustedVsize: number;
|
adjustedVsize: number;
|
||||||
filters: Filter[] = [];
|
filters: Filter[] = [];
|
||||||
|
hasEffectiveFeeRate: boolean;
|
||||||
|
fetchCpfp: boolean;
|
||||||
|
cpfpInfo: CpfpInfo | null;
|
||||||
|
hasCpfp: boolean = false;
|
||||||
showCpfpDetails = false;
|
showCpfpDetails = false;
|
||||||
ETA$: Observable<ETA | null>;
|
ETA$: Observable<ETA | null>;
|
||||||
mempoolBlocksSubscription: Subscription;
|
mempoolBlocksSubscription: Subscription;
|
||||||
@ -78,6 +86,7 @@ export class TransactionRawComponent implements OnInit, OnDestroy {
|
|||||||
try {
|
try {
|
||||||
const tx = decodeRawTransaction(this.pushTxForm.get('txRaw').value, this.stateService.network);
|
const tx = decodeRawTransaction(this.pushTxForm.get('txRaw').value, this.stateService.network);
|
||||||
await this.fetchPrevouts(tx);
|
await this.fetchPrevouts(tx);
|
||||||
|
await this.fetchCpfpInfo(tx);
|
||||||
this.processTransaction(tx);
|
this.processTransaction(tx);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.error = error.message;
|
this.error = error.message;
|
||||||
@ -100,8 +109,9 @@ export class TransactionRawComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
this.missingPrevouts = [];
|
this.missingPrevouts = [];
|
||||||
|
this.isLoadingPrevouts = true;
|
||||||
|
|
||||||
const prevouts: { prevout: Vout, tx?: any }[] = await firstValueFrom(this.apiService.getPrevouts$(prevoutsToFetch));
|
const prevouts: { prevout: Vout, unconfirmed: boolean }[] = await firstValueFrom(this.apiService.getPrevouts$(prevoutsToFetch));
|
||||||
|
|
||||||
if (prevouts?.length !== prevoutsToFetch.length) {
|
if (prevouts?.length !== prevoutsToFetch.length) {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
@ -121,27 +131,57 @@ export class TransactionRawComponent implements OnInit, OnDestroy {
|
|||||||
throw new Error(`Some prevouts do not exist or are already spent (${this.missingPrevouts.length})`);
|
throw new Error(`Some prevouts do not exist or are already spent (${this.missingPrevouts.length})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transaction.fee = transaction.vin.some(input => input.is_coinbase)
|
||||||
|
? 0
|
||||||
|
: transaction.vin.reduce((fee, input) => {
|
||||||
|
return fee + (input.prevout?.value || 0);
|
||||||
|
}, 0) - transaction.vout.reduce((sum, output) => sum + output.value, 0);
|
||||||
|
transaction.feePerVsize = transaction.fee / (transaction.weight / 4);
|
||||||
|
transaction.sigops = countSigops(transaction);
|
||||||
|
|
||||||
this.hasPrevouts = true;
|
this.hasPrevouts = true;
|
||||||
|
this.isLoadingPrevouts = false;
|
||||||
|
this.fetchCpfp = prevouts.some(prevout => prevout?.unconfirmed);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.errorPrevouts = error.message;
|
this.errorPrevouts = error?.error?.message || error?.message;
|
||||||
|
this.isLoadingPrevouts = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchCpfpInfo(transaction: Transaction): Promise<void> {
|
||||||
|
// Fetch potential cpfp data if all prevouts were parsed successfully and at least one of them is unconfirmed
|
||||||
|
if (this.hasPrevouts && this.fetchCpfp) {
|
||||||
|
try {
|
||||||
|
this.isLoadingCpfpInfo = true;
|
||||||
|
const cpfpInfo: CpfpInfo = await firstValueFrom(this.apiService.getCpfpLocalTx$({
|
||||||
|
txid: transaction.txid,
|
||||||
|
weight: transaction.weight,
|
||||||
|
sigops: transaction.sigops,
|
||||||
|
fee: transaction.fee,
|
||||||
|
vin: transaction.vin,
|
||||||
|
vout: transaction.vout
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (cpfpInfo && cpfpInfo.ancestors.length > 0) {
|
||||||
|
const { ancestors, effectiveFeePerVsize } = cpfpInfo;
|
||||||
|
transaction.effectiveFeePerVsize = effectiveFeePerVsize;
|
||||||
|
this.cpfpInfo = { ancestors, effectiveFeePerVsize };
|
||||||
|
this.hasCpfp = true;
|
||||||
|
this.hasEffectiveFeeRate = true;
|
||||||
|
}
|
||||||
|
this.isLoadingCpfpInfo = false;
|
||||||
|
} catch (error) {
|
||||||
|
this.errorCpfpInfo = error?.error?.message || error?.message;
|
||||||
|
this.isLoadingCpfpInfo = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
processTransaction(tx: Transaction): void {
|
processTransaction(tx: Transaction): void {
|
||||||
this.transaction = tx;
|
this.transaction = tx;
|
||||||
|
|
||||||
if (this.hasPrevouts) {
|
|
||||||
this.transaction.fee = this.transaction.vin.some(input => input.is_coinbase)
|
|
||||||
? 0
|
|
||||||
: this.transaction.vin.reduce((fee, input) => {
|
|
||||||
return fee + (input.prevout?.value || 0);
|
|
||||||
}, 0) - this.transaction.vout.reduce((sum, output) => sum + output.value, 0);
|
|
||||||
this.transaction.feePerVsize = this.transaction.fee / (this.transaction.weight / 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.transaction.flags = getTransactionFlags(this.transaction, null, null, null, this.stateService.network);
|
this.transaction.flags = getTransactionFlags(this.transaction, null, null, null, this.stateService.network);
|
||||||
this.filters = this.transaction.flags ? toFilters(this.transaction.flags).filter(f => f.txPage) : [];
|
this.filters = this.transaction.flags ? toFilters(this.transaction.flags).filter(f => f.txPage) : [];
|
||||||
this.transaction.sigops = countSigops(this.transaction);
|
|
||||||
if (this.transaction.sigops >= 0) {
|
if (this.transaction.sigops >= 0) {
|
||||||
this.adjustedVsize = Math.max(this.transaction.weight / 4, this.transaction.sigops * 5);
|
this.adjustedVsize = Math.max(this.transaction.weight / 4, this.transaction.sigops * 5);
|
||||||
}
|
}
|
||||||
@ -155,16 +195,15 @@ export class TransactionRawComponent implements OnInit, OnDestroy {
|
|||||||
this.setGraphSize();
|
this.setGraphSize();
|
||||||
|
|
||||||
this.ETA$ = combineLatest([
|
this.ETA$ = combineLatest([
|
||||||
this.stateService.mempoolTxPosition$.pipe(startWith(null)),
|
|
||||||
this.stateService.mempoolBlocks$.pipe(startWith(null)),
|
this.stateService.mempoolBlocks$.pipe(startWith(null)),
|
||||||
this.stateService.difficultyAdjustment$.pipe(startWith(null)),
|
this.stateService.difficultyAdjustment$.pipe(startWith(null)),
|
||||||
]).pipe(
|
]).pipe(
|
||||||
map(([position, mempoolBlocks, da]) => {
|
map(([mempoolBlocks, da]) => {
|
||||||
return this.etaService.calculateETA(
|
return this.etaService.calculateETA(
|
||||||
this.stateService.network,
|
this.stateService.network,
|
||||||
this.transaction,
|
this.transaction,
|
||||||
mempoolBlocks,
|
mempoolBlocks,
|
||||||
position,
|
null,
|
||||||
da,
|
da,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
@ -177,7 +216,7 @@ export class TransactionRawComponent implements OnInit, OnDestroy {
|
|||||||
if (this.transaction) {
|
if (this.transaction) {
|
||||||
this.stateService.markBlock$.next({
|
this.stateService.markBlock$.next({
|
||||||
txid: this.transaction.txid,
|
txid: this.transaction.txid,
|
||||||
txFeePerVSize: this.transaction.feePerVsize,
|
txFeePerVSize: this.transaction.effectiveFeePerVsize || this.transaction.feePerVsize,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -214,7 +253,15 @@ export class TransactionRawComponent implements OnInit, OnDestroy {
|
|||||||
this.errorBroadcast = null;
|
this.errorBroadcast = null;
|
||||||
this.successBroadcast = false;
|
this.successBroadcast = false;
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
|
this.isLoadingPrevouts = false;
|
||||||
|
this.isLoadingCpfpInfo = false;
|
||||||
|
this.isLoadingBroadcast = false;
|
||||||
this.adjustedVsize = null;
|
this.adjustedVsize = null;
|
||||||
|
this.showCpfpDetails = false;
|
||||||
|
this.hasCpfp = false;
|
||||||
|
this.fetchCpfp = false;
|
||||||
|
this.cpfpInfo = null;
|
||||||
|
this.hasEffectiveFeeRate = false;
|
||||||
this.filters = [];
|
this.filters = [];
|
||||||
this.hasPrevouts = false;
|
this.hasPrevouts = false;
|
||||||
this.missingPrevouts = [];
|
this.missingPrevouts = [];
|
||||||
|
@ -569,6 +569,10 @@ export class ApiService {
|
|||||||
return this.httpClient.post(this.apiBaseUrl + this.apiBasePath + '/api/v1/prevouts', outpoints);
|
return this.httpClient.post(this.apiBaseUrl + this.apiBasePath + '/api/v1/prevouts', outpoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCpfpLocalTx$(tx: any): Observable<CpfpInfo> {
|
||||||
|
return this.httpClient.post<CpfpInfo>(this.apiBaseUrl + this.apiBasePath + '/api/v1/cpfp', tx);
|
||||||
|
}
|
||||||
|
|
||||||
// Cache methods
|
// Cache methods
|
||||||
async setBlockAuditLoaded(hash: string) {
|
async setBlockAuditLoaded(hash: string) {
|
||||||
this.blockAuditLoaded[hash] = true;
|
this.blockAuditLoaded[hash] = true;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user