diff --git a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts index 53c731e1f..71269da31 100644 --- a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts +++ b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts @@ -2,7 +2,7 @@ import { IEsploraApi } from './esplora-api.interface'; export interface AbstractBitcoinApi { $getRawMempool(): Promise; - $getRawTransaction(txId: string, skipConversion?: boolean, addPrevout?: boolean): Promise; + $getRawTransaction(txId: string, skipConversion?: boolean, addPrevout?: boolean, lazyPrevouts?: boolean): Promise; $getBlockHeightTip(): Promise; $getTxIdsForBlock(hash: string): Promise; $getBlockHash(height: number): Promise; diff --git a/backend/src/api/bitcoin/bitcoin-api.ts b/backend/src/api/bitcoin/bitcoin-api.ts index e52aaffaf..41671ede1 100644 --- a/backend/src/api/bitcoin/bitcoin-api.ts +++ b/backend/src/api/bitcoin/bitcoin-api.ts @@ -31,7 +31,8 @@ class BitcoinApi implements AbstractBitcoinApi { }; } - $getRawTransaction(txId: string, skipConversion = false, addPrevout = false): Promise { + + $getRawTransaction(txId: string, skipConversion = false, addPrevout = false, lazyPrevouts = false): Promise { // If the transaction is in the mempool we already converted and fetched the fee. Only prevouts are missing const txInMempool = mempool.getMempool()[txId]; if (txInMempool && addPrevout) { @@ -46,7 +47,7 @@ class BitcoinApi implements AbstractBitcoinApi { }); return transaction; } - return this.$convertTransaction(transaction, addPrevout); + return this.$convertTransaction(transaction, addPrevout, lazyPrevouts); }) .catch((e: Error) => { if (e.message.startsWith('The genesis block coinbase')) { @@ -126,7 +127,7 @@ class BitcoinApi implements AbstractBitcoinApi { const outSpends: IEsploraApi.Outspend[] = []; const tx = await this.$getRawTransaction(txId, true, false); for (let i = 0; i < tx.vout.length; i++) { - if (tx.status && tx.status.block_height == 0) { + if (tx.status && tx.status.block_height === 0) { outSpends.push({ spent: false }); @@ -145,7 +146,7 @@ class BitcoinApi implements AbstractBitcoinApi { return this.bitcoindClient.getNetworkHashPs(120, blockHeight); } - protected async $convertTransaction(transaction: IBitcoinApi.Transaction, addPrevout: boolean): Promise { + protected async $convertTransaction(transaction: IBitcoinApi.Transaction, addPrevout: boolean, lazyPrevouts = false): Promise { let esploraTransaction: IEsploraApi.Transaction = { txid: transaction.txid, version: transaction.version, @@ -192,7 +193,7 @@ class BitcoinApi implements AbstractBitcoinApi { } if (addPrevout) { - esploraTransaction = await this.$calculateFeeFromInputs(esploraTransaction); + esploraTransaction = await this.$calculateFeeFromInputs(esploraTransaction, false, lazyPrevouts); } else if (!transaction.confirmations) { esploraTransaction = await this.$appendMempoolFeeData(esploraTransaction); } @@ -268,20 +269,30 @@ class BitcoinApi implements AbstractBitcoinApi { return this.bitcoindClient.getRawMemPool(true); } - private async $calculateFeeFromInputs(transaction: IEsploraApi.Transaction): Promise { + + private async $calculateFeeFromInputs(transaction: IEsploraApi.Transaction, addPrevout: boolean, lazyPrevouts: boolean): Promise { if (transaction.vin[0].is_coinbase) { transaction.fee = 0; return transaction; } let totalIn = 0; - for (const vin of transaction.vin) { - const innerTx = await this.$getRawTransaction(vin.txid, false, false); - vin.prevout = innerTx.vout[vin.vout]; - this.addInnerScriptsToVin(vin); - totalIn += innerTx.vout[vin.vout].value; + + for (let i = 0; i < transaction.vin.length; i++) { + if (lazyPrevouts && i > 12) { + transaction.vin[i].lazy = true; + continue; + } + const innerTx = await this.$getRawTransaction(transaction.vin[i].txid, false, false); + transaction.vin[i].prevout = innerTx.vout[transaction.vin[i].vout]; + this.addInnerScriptsToVin(transaction.vin[i]); + totalIn += innerTx.vout[transaction.vin[i].vout].value; + } + if (lazyPrevouts && transaction.vin.length > 12) { + transaction.fee = -1; + } else { + const totalOut = transaction.vout.reduce((p, output) => p + output.value, 0); + transaction.fee = parseFloat((totalIn - totalOut).toFixed(8)); } - const totalOut = transaction.vout.reduce((p, output) => p + output.value, 0); - transaction.fee = parseFloat((totalIn - totalOut).toFixed(8)); return transaction; } diff --git a/backend/src/api/bitcoin/esplora-api.interface.ts b/backend/src/api/bitcoin/esplora-api.interface.ts index 73ccbd88f..f825c60f9 100644 --- a/backend/src/api/bitcoin/esplora-api.interface.ts +++ b/backend/src/api/bitcoin/esplora-api.interface.ts @@ -33,6 +33,8 @@ export namespace IEsploraApi { // Elements is_pegin?: boolean; issuance?: Issuance; + // Custom + lazy?: boolean; } interface Issuance { diff --git a/backend/src/api/transaction-utils.ts b/backend/src/api/transaction-utils.ts index 2e669d709..5b92cea5f 100644 --- a/backend/src/api/transaction-utils.ts +++ b/backend/src/api/transaction-utils.ts @@ -21,8 +21,8 @@ class TransactionUtils { }; } - public async $getTransactionExtended(txId: string, addPrevouts = false): Promise { - const transaction: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(txId, false, addPrevouts); + public async $getTransactionExtended(txId: string, addPrevouts = false, lazyPrevouts = false): Promise { + const transaction: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(txId, false, addPrevouts, lazyPrevouts); return this.extendTransaction(transaction); } diff --git a/backend/src/routes.ts b/backend/src/routes.ts index d65b4f836..cf28dd71d 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -778,9 +778,9 @@ class Routes { const endIndex = Math.min(startingIndex + 10, txIds.length); for (let i = startingIndex; i < endIndex; i++) { try { - const transaction = await transactionUtils.$getTransactionExtended(txIds[i], true); + const transaction = await transactionUtils.$getTransactionExtended(txIds[i], true, true); transactions.push(transaction); - loadingIndicators.setProgress('blocktxs-' + req.params.hash, (i + 1) / endIndex * 100); + loadingIndicators.setProgress('blocktxs-' + req.params.hash, (i - startingIndex + 1) / (endIndex - startingIndex) * 100); } catch (e) { logger.debug('getBlockTransactions error: ' + (e instanceof Error ? e.message : e)); } diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index c76fab3aa..9a2629f08 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -125,7 +125,7 @@ export class TransactionComponent implements OnInit, OnDestroy { }), switchMap(() => { let transactionObservable$: Observable; - if (history.state.data) { + if (history.state.data && history.state.data.fee !== -1) { transactionObservable$ = of(history.state.data); } else { transactionObservable$ = this.electrsApiService diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.html b/frontend/src/app/components/transactions-list/transactions-list.component.html index 1147810ca..1b34692a8 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.html +++ b/frontend/src/app/components/transactions-list/transactions-list.component.html @@ -60,7 +60,10 @@ - {{ vin.issuance ? 'Issuance' : 'UNKNOWN' }} + + + {{ vin.issuance ? 'Issuance' : 'UNKNOWN' }} + @@ -87,6 +90,7 @@ + @@ -141,7 +145,7 @@ - + @@ -261,9 +265,10 @@
-
+
{{ tx.fee / (tx.weight / 4) | feeRounding }} sat/vB  – {{ tx.fee | number }} sat
+
Show all inputs to reveal fee data
diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.scss b/frontend/src/app/components/transactions-list/transactions-list.component.scss index 6690337ce..11e013e23 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.scss +++ b/frontend/src/app/components/transactions-list/transactions-list.component.scss @@ -139,4 +139,10 @@ h2 { .addr-right { font-family: monospace; -} \ No newline at end of file +} + +.grey-info-text { + color:#6c757d; + font-style: italic; + font-size: 12px; +} diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.ts b/frontend/src/app/components/transactions-list/transactions-list.component.ts index b4ef42d84..025faf322 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.ts +++ b/frontend/src/app/components/transactions-list/transactions-list.component.ts @@ -175,6 +175,17 @@ export class TransactionsListComponent implements OnInit, OnChanges { } } + loadMoreInputs(tx: Transaction) { + tx['@vinLimit'] = false; + + this.electrsApiService.getTransaction$(tx.txid) + .subscribe((newTx) => { + tx.vin = newTx.vin; + tx.fee = newTx.fee; + this.ref.markForCheck(); + }); + } + ngOnDestroy() { this.outspendsSubscription.unsubscribe(); } diff --git a/frontend/src/app/interfaces/electrs.interface.ts b/frontend/src/app/interfaces/electrs.interface.ts index ecd0ac598..9c873d2eb 100644 --- a/frontend/src/app/interfaces/electrs.interface.ts +++ b/frontend/src/app/interfaces/electrs.interface.ts @@ -54,6 +54,8 @@ export interface Vin { // Elements is_pegin?: boolean; issuance?: Issuance; + // Custom + lazy?: boolean; } interface Issuance {