From f062132636d9f9b0f9497177bc7e1faf7937aa07 Mon Sep 17 00:00:00 2001 From: junderw Date: Mon, 5 Sep 2022 23:13:45 +0900 Subject: [PATCH 01/17] Feature: Add endpoint for PSBT nonWitnessUtxo inclusion --- backend/src/api/bitcoin/bitcoin.routes.ts | 44 +++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/backend/src/api/bitcoin/bitcoin.routes.ts b/backend/src/api/bitcoin/bitcoin.routes.ts index 2c3fd9467..1b805d7f0 100644 --- a/backend/src/api/bitcoin/bitcoin.routes.ts +++ b/backend/src/api/bitcoin/bitcoin.routes.ts @@ -1,5 +1,6 @@ import { Application, Request, Response } from 'express'; import axios from 'axios'; +import * as bitcoinjs from 'bitcoinjs-lib'; import config from '../../config'; import websocketHandler from '../websocket-handler'; import mempool from '../mempool'; @@ -95,6 +96,7 @@ class BitcoinRoutes { .get(config.MEMPOOL.API_URL_PREFIX + 'mempool', this.getMempool) .get(config.MEMPOOL.API_URL_PREFIX + 'mempool/txids', this.getMempoolTxIds) .get(config.MEMPOOL.API_URL_PREFIX + 'mempool/recent', this.getRecentMempoolTransactions) + .post(config.MEMPOOL.API_URL_PREFIX + 'psbt/addparents', this.postPsbtCompletion) .get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId', this.getTransaction) .post(config.MEMPOOL.API_URL_PREFIX + 'tx', this.$postTransaction) .get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/hex', this.getRawTransaction) @@ -241,6 +243,48 @@ class BitcoinRoutes { } } + /** + * Takes the PSBT as text/plain body, parses it, and adds the full + * parent transaction to each input that doesn't already have it. + * This is used for BTCPayServer / Trezor users which need access to + * the full parent transaction even with segwit inputs. + * It will respond with a text/plain PSBT in the same format (hex|base64). + */ + private async postPsbtCompletion(req: Request, res: Response) { + res.setHeader('content-type', 'text/plain'); + try { + let psbt: bitcoinjs.Psbt; + let format: 'hex' | 'base64'; + try { + psbt = bitcoinjs.Psbt.fromBase64(req.body); + format = 'base64'; + } catch(e1) { + try { + psbt = bitcoinjs.Psbt.fromHex(req.body); + format = 'hex'; + } catch(e2) { + throw new Error(`Unable to parse PSBT`); + } + } + for (const [index, input] of psbt.data.inputs.entries()) { + if (!input.nonWitnessUtxo) { + // Buffer.from ensures it won't be modified in place by reverse() + const txid = Buffer.from(psbt.txInputs[index].hash).reverse().toString('hex'); + const transaction: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(txid, true); + if (!transaction.hex) { + throw new Error(`Couldn't get transaction hex for ${txid}`); + } + psbt.updateInput(index, { + nonWitnessUtxo: Buffer.from(transaction.hex, 'hex'), + }); + } + } + res.send(format === 'hex' ? psbt.toHex() : psbt.toBase64()); + } catch (e: any) { + res.status(500).send(e instanceof Error ? e.message : e); + } + } + private async getTransactionStatus(req: Request, res: Response) { try { const transaction = await transactionUtils.$getTransactionExtended(req.params.txId, true); From 9b1fc1e000560706b1406612dee0386557da7019 Mon Sep 17 00:00:00 2001 From: junderw Date: Sat, 10 Sep 2022 16:03:31 +0900 Subject: [PATCH 02/17] Fix response codes for various error states --- backend/src/api/bitcoin/bitcoin.routes.ts | 42 ++++++++++++++++++----- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/backend/src/api/bitcoin/bitcoin.routes.ts b/backend/src/api/bitcoin/bitcoin.routes.ts index 1b805d7f0..e783a4864 100644 --- a/backend/src/api/bitcoin/bitcoin.routes.ts +++ b/backend/src/api/bitcoin/bitcoin.routes.ts @@ -252,36 +252,62 @@ class BitcoinRoutes { */ private async postPsbtCompletion(req: Request, res: Response) { res.setHeader('content-type', 'text/plain'); + const notFoundError = `Couldn't get transaction hex for parent of input`; try { let psbt: bitcoinjs.Psbt; let format: 'hex' | 'base64'; + let isModified = false; try { psbt = bitcoinjs.Psbt.fromBase64(req.body); format = 'base64'; - } catch(e1) { + } catch (e1) { try { psbt = bitcoinjs.Psbt.fromHex(req.body); format = 'hex'; - } catch(e2) { + } catch (e2) { throw new Error(`Unable to parse PSBT`); } } for (const [index, input] of psbt.data.inputs.entries()) { if (!input.nonWitnessUtxo) { // Buffer.from ensures it won't be modified in place by reverse() - const txid = Buffer.from(psbt.txInputs[index].hash).reverse().toString('hex'); - const transaction: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(txid, true); - if (!transaction.hex) { - throw new Error(`Couldn't get transaction hex for ${txid}`); + const txid = Buffer.from(psbt.txInputs[index].hash) + .reverse() + .toString('hex'); + + let transaction: IEsploraApi.Transaction; + // If missing transaction, return 404 status error + try { + transaction = await bitcoinApi.$getRawTransaction(txid, true); + if (!transaction.hex) { + throw new Error(''); + } + } catch (err) { + throw new Error(`${notFoundError} #${index} @ ${txid}`); } + psbt.updateInput(index, { nonWitnessUtxo: Buffer.from(transaction.hex, 'hex'), }); + if (!isModified) { + isModified = true; + } } } - res.send(format === 'hex' ? psbt.toHex() : psbt.toBase64()); + if (isModified) { + res.send(format === 'hex' ? psbt.toHex() : psbt.toBase64()); + } else { + // Not modified + // 422 Unprocessable Entity + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + res.status(422).send(`Psbt had no missing nonUtxoWitnesses.`); + } } catch (e: any) { - res.status(500).send(e instanceof Error ? e.message : e); + if (e instanceof Error && new RegExp(notFoundError).test(e.message)) { + res.status(404).send(e.message); + } else { + res.status(500).send(e instanceof Error ? e.message : e); + } } } From bd4cf980bd9d8b60c6806f7fd816a357df7abf6f Mon Sep 17 00:00:00 2001 From: junderw Date: Sat, 10 Sep 2022 16:09:43 +0900 Subject: [PATCH 03/17] Spelling fix --- backend/src/api/bitcoin/bitcoin.routes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/api/bitcoin/bitcoin.routes.ts b/backend/src/api/bitcoin/bitcoin.routes.ts index e783a4864..e774a0ded 100644 --- a/backend/src/api/bitcoin/bitcoin.routes.ts +++ b/backend/src/api/bitcoin/bitcoin.routes.ts @@ -300,7 +300,7 @@ class BitcoinRoutes { // Not modified // 422 Unprocessable Entity // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 - res.status(422).send(`Psbt had no missing nonUtxoWitnesses.`); + res.status(422).send(`Psbt had no missing nonWitnessUtxos.`); } } catch (e: any) { if (e instanceof Error && new RegExp(notFoundError).test(e.message)) { From 997e8a462452a644b0ce135115864ddc8041bf86 Mon Sep 17 00:00:00 2001 From: hunicus <93150691+hunicus@users.noreply.github.com> Date: Sun, 13 Nov 2022 22:30:05 -0500 Subject: [PATCH 04/17] Create "advanced" + "self-hosted" faq categories And re-arrange questions: move all old "advanced" questions to "self-hosted", and move some "basic" questions to "advanced". --- .../src/app/docs/api-docs/api-docs-data.ts | 43 +++++++++++-------- .../app/docs/api-docs/api-docs.component.html | 16 +++---- .../app/docs/api-docs/api-docs.component.scss | 1 + 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/frontend/src/app/docs/api-docs/api-docs-data.ts b/frontend/src/app/docs/api-docs/api-docs-data.ts index 0b2f4b2ff..970d6ab73 100644 --- a/frontend/src/app/docs/api-docs/api-docs-data.ts +++ b/frontend/src/app/docs/api-docs/api-docs-data.ts @@ -8562,20 +8562,6 @@ export const faqData = [ fragment: "what-is-svb", title: "What is sat/vB?", }, - { - type: "endpoint", - category: "basics", - showConditions: bitcoinNetworks, - fragment: "what-is-full-mempool", - title: "What does it mean for the mempool to be \"full\"?", - }, - { - type: "endpoint", - category: "basics", - showConditions: bitcoinNetworks, - fragment: "why-empty-blocks", - title: "Why are there empty blocks?", - }, { type: "category", category: "help", @@ -8657,33 +8643,54 @@ export const faqData = [ type: "endpoint", category: "advanced", showConditions: bitcoinNetworks, + fragment: "what-is-full-mempool", + title: "What does it mean for the mempool to be \"full\"?", + }, + { + type: "endpoint", + category: "advanced", + showConditions: bitcoinNetworks, + fragment: "why-empty-blocks", + title: "Why are there empty blocks?", + }, + { + type: "category", + category: "self-hosting", + fragment: "self-hosting", + title: "Self-Hosting", + showConditions: bitcoinNetworks + }, + { + type: "endpoint", + category: "self-hosting", + showConditions: bitcoinNetworks, fragment: "who-runs-this-website", title: "Who runs this website?", }, { type: "endpoint", - category: "advanced", + category: "self-hosting", showConditions: bitcoinNetworks, fragment: "host-my-own-instance-raspberry-pi", title: "How can I host my own instance on a Raspberry Pi?", }, { type: "endpoint", - category: "advanced", + category: "self-hosting", showConditions: bitcoinNetworks, fragment: "host-my-own-instance-linux-server", title: "How can I host my own instance on a Linux server?", }, { type: "endpoint", - category: "advanced", + category: "self-hosting", showConditions: bitcoinNetworks, fragment: "install-mempool-with-docker", title: "Can I install Mempool using Docker?", }, { type: "endpoint", - category: "advanced", + category: "self-hosting", showConditions: bitcoinNetworks, fragment: "address-lookup-issues", title: "Why do I get an error for certain address lookups on my Mempool instance?", diff --git a/frontend/src/app/docs/api-docs/api-docs.component.html b/frontend/src/app/docs/api-docs/api-docs.component.html index e2524a27d..eb3c94d5e 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.html +++ b/frontend/src/app/docs/api-docs/api-docs.component.html @@ -163,14 +163,6 @@

There are feerate estimates on the top of the main dashboard you can use as a guide. See this FAQ for more on picking the right feerate.

- -

When a Bitcoin transaction is made, it is stored in a Bitcoin node's mempool before it is confirmed into a block. When the rate of incoming transactions exceeds the rate transactions are confirmed, the mempool grows in size.

The default maximum size of a Bitcoin node's mempool is 300MB, so when there are 300MB of transactions in the mempool, we say it's "full".

-
- - -

When a new block is found, mining pools send miners a block template with no transactions so they can start searching for the next block as soon as possible. They send a block template full of transactions right afterward, but a full block template is a bigger data transfer and takes slightly longer to reach miners.

In this intervening time, which is usually no more than 1-2 seconds, miners sometimes get lucky and find a new block using the empty block template.

-
-

If it's been a while and your transaction hasn't confirmed, your transaction is probably using a lower feerate relative to other transactions currently in the mempool. Depending on how you made your transaction, there may be ways to accelerate the process.

There's no need to panic—a Bitcoin transaction will always either confirm completely (or not at all) at some point. As long as you have your transaction's ID, you can always see where your funds are.

This site only provides data about the Bitcoin network—it cannot help you get your transaction confirmed quicker.

@@ -203,6 +195,14 @@ See the graphs page for aggregate trends over time: mempool size over time and incoming transaction velocity over time. + +

When a Bitcoin transaction is made, it is stored in a Bitcoin node's mempool before it is confirmed into a block. When the rate of incoming transactions exceeds the rate transactions are confirmed, the mempool grows in size.

The default maximum size of a Bitcoin node's mempool is 300MB, so when there are 300MB of transactions in the mempool, we say it's "full".

+
+ + +

When a new block is found, mining pools send miners a block template with no transactions so they can start searching for the next block as soon as possible. They send a block template full of transactions right afterward, but a full block template is a bigger data transfer and takes slightly longer to reach miners.

In this intervening time, which is usually no more than 1-2 seconds, miners sometimes get lucky and find a new block using the empty block template.

+
+ The official mempool.space website is operated by The Mempool Open Source Project. See more information on our About page. There are also many unofficial instances of this website operated by individual members of the Bitcoin community. diff --git a/frontend/src/app/docs/api-docs/api-docs.component.scss b/frontend/src/app/docs/api-docs/api-docs.component.scss index acfc209e5..53ac3bf32 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.scss +++ b/frontend/src/app/docs/api-docs/api-docs.component.scss @@ -172,6 +172,7 @@ h3 { border-radius: 0.25rem; font-family: monospace; float: right; + white-space: nowrap; } .endpoint-container .section-header table { From ef27aca6e4e48b8ec409c04cb74c800313b54f96 Mon Sep 17 00:00:00 2001 From: hunicus <93150691+hunicus@users.noreply.github.com> Date: Sun, 13 Nov 2022 22:34:15 -0500 Subject: [PATCH 05/17] Update faq: what is full mempool --- frontend/src/app/docs/api-docs/api-docs.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/docs/api-docs/api-docs.component.html b/frontend/src/app/docs/api-docs/api-docs.component.html index eb3c94d5e..9fc9aa8a0 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.html +++ b/frontend/src/app/docs/api-docs/api-docs.component.html @@ -196,7 +196,7 @@ -

When a Bitcoin transaction is made, it is stored in a Bitcoin node's mempool before it is confirmed into a block. When the rate of incoming transactions exceeds the rate transactions are confirmed, the mempool grows in size.

The default maximum size of a Bitcoin node's mempool is 300MB, so when there are 300MB of transactions in the mempool, we say it's "full".

+

When a Bitcoin transaction is made, it is stored in a Bitcoin node's mempool before it is confirmed into a block. When the rate of incoming transactions exceeds the rate transactions are confirmed, the mempool grows in size.

By default, Bitcoin Core allocates 300MB of memory for its mempool, so when a node's mempool grows big enough to use all 300MB of allocated memory, we say it's "full".

Once a node's mempool is using all of its allocated memory, it will start rejecting new transactions below a certain feerate threshold—so when this is the case, be extra sure to set a feerate that (at a minimum) exceeds that threshold. The current threshold feerate (and memory usage) are displayed right on Mempool's front page.

From ddcd3878487d28484657a6106eadc24d3ca9b0ab Mon Sep 17 00:00:00 2001 From: hunicus <93150691+hunicus@users.noreply.github.com> Date: Sun, 13 Nov 2022 22:36:46 -0500 Subject: [PATCH 06/17] Add faq: why timestamps don't always increase --- frontend/src/app/docs/api-docs/api-docs-data.ts | 7 +++++++ frontend/src/app/docs/api-docs/api-docs.component.html | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/frontend/src/app/docs/api-docs/api-docs-data.ts b/frontend/src/app/docs/api-docs/api-docs-data.ts index 970d6ab73..9b2fcac4b 100644 --- a/frontend/src/app/docs/api-docs/api-docs-data.ts +++ b/frontend/src/app/docs/api-docs/api-docs-data.ts @@ -8653,6 +8653,13 @@ export const faqData = [ fragment: "why-empty-blocks", title: "Why are there empty blocks?", }, + { + type: "endpoint", + category: "advanced", + showConditions: bitcoinNetworks, + fragment: "why-block-timestamps-dont-always-increase", + title: "Why don't block timestamps always increase?", + }, { type: "category", category: "self-hosting", diff --git a/frontend/src/app/docs/api-docs/api-docs.component.html b/frontend/src/app/docs/api-docs/api-docs.component.html index 9fc9aa8a0..35fe40362 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.html +++ b/frontend/src/app/docs/api-docs/api-docs.component.html @@ -203,6 +203,10 @@

When a new block is found, mining pools send miners a block template with no transactions so they can start searching for the next block as soon as possible. They send a block template full of transactions right afterward, but a full block template is a bigger data transfer and takes slightly longer to reach miners.

In this intervening time, which is usually no more than 1-2 seconds, miners sometimes get lucky and find a new block using the empty block template.

+ +

Block validation rules do not strictly require that a block's timestamp be more recent than the timestamp of the block preceding it. Without a central authority, it's impossible to know what the exact correct time is. Instead, the Bitcoin protocol requires that a block's timestamp meet certain requirements. One of those requirements is that a block's timestamp cannot be older than the median timestamp of the 12 blocks that came before it. See more details here.

As a result, timestamps are only accurate to within an hour or so, which sometimes results in blocks with timestamps that appear out of order.

+
+ The official mempool.space website is operated by The Mempool Open Source Project. See more information on our About page. There are also many unofficial instances of this website operated by individual members of the Bitcoin community. From 8bd3e14652431bb83ae95aec82b5a0aded38f165 Mon Sep 17 00:00:00 2001 From: hunicus <93150691+hunicus@users.noreply.github.com> Date: Sun, 13 Nov 2022 22:38:36 -0500 Subject: [PATCH 07/17] Add faq: why block fee ranges don't match tx fees --- frontend/src/app/docs/api-docs/api-docs-data.ts | 7 +++++++ frontend/src/app/docs/api-docs/api-docs.component.html | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/frontend/src/app/docs/api-docs/api-docs-data.ts b/frontend/src/app/docs/api-docs/api-docs-data.ts index 9b2fcac4b..8cbf03dfb 100644 --- a/frontend/src/app/docs/api-docs/api-docs-data.ts +++ b/frontend/src/app/docs/api-docs/api-docs-data.ts @@ -8660,6 +8660,13 @@ export const faqData = [ fragment: "why-block-timestamps-dont-always-increase", title: "Why don't block timestamps always increase?", }, + { + type: "endpoint", + category: "advanced", + showConditions: bitcoinNetworks, + fragment: "why-dont-fee-ranges-match", + title: "Why doesn't the fee range shown for a block match the feerates of transactions within the block?", + }, { type: "category", category: "self-hosting", diff --git a/frontend/src/app/docs/api-docs/api-docs.component.html b/frontend/src/app/docs/api-docs/api-docs.component.html index 35fe40362..5d11432a0 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.html +++ b/frontend/src/app/docs/api-docs/api-docs.component.html @@ -207,6 +207,12 @@

Block validation rules do not strictly require that a block's timestamp be more recent than the timestamp of the block preceding it. Without a central authority, it's impossible to know what the exact correct time is. Instead, the Bitcoin protocol requires that a block's timestamp meet certain requirements. One of those requirements is that a block's timestamp cannot be older than the median timestamp of the 12 blocks that came before it. See more details here.

As a result, timestamps are only accurate to within an hour or so, which sometimes results in blocks with timestamps that appear out of order.

+ +

Mempool aims to show you the effective feerate range for blocks—how much would you actually need to pay to get a transaction included in a block.

+

A transaction's effective feerate is not always the same as the feerate explicitly set for it. For example, if you see a 1 s/vb transaction in a block with a displayed feerate range of 5 s/vb to 72 s/vb, chances are that 1 s/vb transaction had a high-feerate child transaction that boosted its effective feerate to 5 s/vb or higher (this is how CPFP fee-bumping works). In such a case, it would be misleading to use 1 s/vb as the lower bound of the block's feerate range because it actually required more than 1 s/vb to confirm that transaction in that block.

+

For unconfirmed CPFP transactions, Mempool will show the effective feerate (along with descendent & ancestor transaction information) on the transaction page. For confirmed transactions, CPFP relationships are not stored, so this additional information is not shown.

+
+ The official mempool.space website is operated by The Mempool Open Source Project. See more information on our About page. There are also many unofficial instances of this website operated by individual members of the Bitcoin community. From 7c7273b696e24526eb5e9bc5b90474212fbab206 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Sun, 20 Nov 2022 19:23:51 +0900 Subject: [PATCH 08/17] Remove FTX from the price feeds --- backend/src/tasks/price-feeds/ftx-api.ts | 43 ------------------------ backend/src/tasks/price-updater.ts | 3 -- 2 files changed, 46 deletions(-) delete mode 100644 backend/src/tasks/price-feeds/ftx-api.ts diff --git a/backend/src/tasks/price-feeds/ftx-api.ts b/backend/src/tasks/price-feeds/ftx-api.ts deleted file mode 100644 index 193d3e881..000000000 --- a/backend/src/tasks/price-feeds/ftx-api.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { query } from '../../utils/axios-query'; -import priceUpdater, { PriceFeed, PriceHistory } from '../price-updater'; - -class FtxApi implements PriceFeed { - public name: string = 'FTX'; - public currencies: string[] = ['USD', 'BRZ', 'EUR', 'JPY', 'AUD']; - - public url: string = 'https://ftx.com/api/markets/BTC/'; - public urlHist: string = 'https://ftx.com/api/markets/BTC/{CURRENCY}/candles?resolution={GRANULARITY}'; - - constructor() { - } - - public async $fetchPrice(currency): Promise { - const response = await query(this.url + currency); - return response ? parseInt(response['result']['last'], 10) : -1; - } - - public async $fetchRecentPrice(currencies: string[], type: 'hour' | 'day'): Promise { - const priceHistory: PriceHistory = {}; - - for (const currency of currencies) { - if (this.currencies.includes(currency) === false) { - continue; - } - - const response = await query(this.urlHist.replace('{GRANULARITY}', type === 'hour' ? '3600' : '86400').replace('{CURRENCY}', currency)); - const pricesRaw = response ? response['result'] : []; - - for (const price of pricesRaw as any[]) { - const time = Math.round(price['time'] / 1000); - if (priceHistory[time] === undefined) { - priceHistory[time] = priceUpdater.getEmptyPricesObj(); - } - priceHistory[time][currency] = price['close']; - } - } - - return priceHistory; - } -} - -export default FtxApi; diff --git a/backend/src/tasks/price-updater.ts b/backend/src/tasks/price-updater.ts index 891e2bce3..e069e4db4 100644 --- a/backend/src/tasks/price-updater.ts +++ b/backend/src/tasks/price-updater.ts @@ -1,13 +1,11 @@ import * as fs from 'fs'; import path from "path"; -import { Common } from '../api/common'; import config from '../config'; import logger from '../logger'; import PricesRepository from '../repositories/PricesRepository'; import BitfinexApi from './price-feeds/bitfinex-api'; import BitflyerApi from './price-feeds/bitflyer-api'; import CoinbaseApi from './price-feeds/coinbase-api'; -import FtxApi from './price-feeds/ftx-api'; import GeminiApi from './price-feeds/gemini-api'; import KrakenApi from './price-feeds/kraken-api'; @@ -48,7 +46,6 @@ class PriceUpdater { this.latestPrices = this.getEmptyPricesObj(); this.feeds.push(new BitflyerApi()); // Does not have historical endpoint - this.feeds.push(new FtxApi()); this.feeds.push(new KrakenApi()); this.feeds.push(new CoinbaseApi()); this.feeds.push(new BitfinexApi()); From 7bafeefa9569e720469a5837b0ddd2303a793bad Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 22 Nov 2022 16:30:04 +0900 Subject: [PATCH 09/17] fix squashed tx flow diagram --- .../app/components/transaction/transaction.component.html | 2 +- .../src/app/components/transaction/transaction.component.ts | 5 ++++- .../components/tx-bowtie-graph/tx-bowtie-graph.component.ts | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index cd0cd716d..d478da053 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -204,7 +204,7 @@ Date: Mon, 7 Nov 2022 20:02:59 -0600 Subject: [PATCH 10/17] "show more" instead of "show all" txos in lists --- .../transactions-list.component.html | 12 ++++++------ .../transactions-list.component.ts | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) 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 96f792f82..80bec2d28 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.html +++ b/frontend/src/app/components/transactions-list/transactions-list.component.html @@ -20,7 +20,7 @@
- + inputRowLimit && tx['@vinLimit']"> + @@ -158,7 +158,7 @@
- +
- + outputRowLimit && tx['@voutLimit']"> + 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 9f1245532..e81a79c71 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.ts +++ b/frontend/src/app/components/transactions-list/transactions-list.component.ts @@ -18,6 +18,7 @@ import { ApiService } from '../../services/api.service'; export class TransactionsListComponent implements OnInit, OnChanges { network = ''; nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId; + showMoreIncrement = 40; @Input() transactions: Transaction[]; @Input() showConfirmations = false; @@ -218,6 +219,22 @@ export class TransactionsListComponent implements OnInit, OnChanges { }); } + showMoreInputs(tx: Transaction): void { + tx['@vinLimit'] = this.getVinLimit(tx, true); + } + + showMoreOutputs(tx: Transaction): void { + tx['@voutLimit'] = this.getVoutLimit(tx, true); + } + + getVinLimit(tx: Transaction, next = false): number { + return Math.min(Math.max(tx['@vinLimit'] || 0, this.inputRowLimit) + (next ? this.showMoreIncrement : 0), tx.vin.length); + } + + getVoutLimit(tx: Transaction, next = false): number { + return Math.min(Math.max(tx['@voutLimit'] || 0, this.outputRowLimit) + (next ? this.showMoreIncrement : 0), tx.vout.length); + } + ngOnDestroy(): void { this.outspendsSubscription.unsubscribe(); } From d1072863449dacc3f521982eebedad6aa7c9c7c8 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 22 Nov 2022 14:47:31 +0900 Subject: [PATCH 11/17] Load 1000 more inputs/outputs per click. Fix label i18n. --- .../transactions-list.component.html | 16 ++++++++++++++-- .../transactions-list.component.ts | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) 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 80bec2d28..cd2d58f2f 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.html +++ b/frontend/src/app/components/transactions-list/transactions-list.component.html @@ -148,7 +148,13 @@ @@ -259,7 +265,13 @@ 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 e81a79c71..0d9d60c95 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.ts +++ b/frontend/src/app/components/transactions-list/transactions-list.component.ts @@ -18,7 +18,7 @@ import { ApiService } from '../../services/api.service'; export class TransactionsListComponent implements OnInit, OnChanges { network = ''; nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId; - showMoreIncrement = 40; + showMoreIncrement = 1000; @Input() transactions: Transaction[]; @Input() showConfirmations = false; From 6cd1f9e8703d78d9ada0b6d2905d4eae02b2d0d4 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 22 Nov 2022 14:48:34 +0900 Subject: [PATCH 12/17] Fix load more inputs for non-esplora backends --- .../transactions-list.component.html | 2 +- .../transactions-list.component.ts | 30 ++++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) 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 cd2d58f2f..139da368b 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.html +++ b/frontend/src/app/components/transactions-list/transactions-list.component.html @@ -283,7 +283,7 @@
{{ tx.fee / (tx.weight / 4) | feeRounding }} sat/vB  – {{ tx.fee | number }} sat
-
Show all inputs to reveal fee data
+
Show more inputs to reveal fee data
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 0d9d60c95..be5bd5343 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.ts +++ b/frontend/src/app/components/transactions-list/transactions-list.component.ts @@ -209,17 +209,19 @@ export class TransactionsListComponent implements OnInit, OnChanges { } loadMoreInputs(tx: Transaction): void { - tx['@vinLimit'] = false; - - this.electrsApiService.getTransaction$(tx.txid) - .subscribe((newTx) => { - tx.vin = newTx.vin; - tx.fee = newTx.fee; - this.ref.markForCheck(); - }); + if (!tx['@vinLoaded']) { + this.electrsApiService.getTransaction$(tx.txid) + .subscribe((newTx) => { + tx['@vinLoaded'] = true; + tx.vin = newTx.vin; + tx.fee = newTx.fee; + this.ref.markForCheck(); + }); + } } showMoreInputs(tx: Transaction): void { + this.loadMoreInputs(tx); tx['@vinLimit'] = this.getVinLimit(tx, true); } @@ -228,11 +230,19 @@ export class TransactionsListComponent implements OnInit, OnChanges { } getVinLimit(tx: Transaction, next = false): number { - return Math.min(Math.max(tx['@vinLimit'] || 0, this.inputRowLimit) + (next ? this.showMoreIncrement : 0), tx.vin.length); + if ((tx['@vinLimit'] || 0) > this.inputRowLimit) { + return Math.min(tx['@vinLimit'] + (next ? this.showMoreIncrement : 0), tx.vin.length); + } else { + return Math.min((next ? this.showMoreIncrement : this.inputRowLimit), tx.vin.length); + } } getVoutLimit(tx: Transaction, next = false): number { - return Math.min(Math.max(tx['@voutLimit'] || 0, this.outputRowLimit) + (next ? this.showMoreIncrement : 0), tx.vout.length); + if ((tx['@voutLimit'] || 0) > this.outputRowLimit) { + return Math.min(tx['@voutLimit'] + (next ? this.showMoreIncrement : 0), tx.vout.length); + } else { + return Math.min((next ? this.showMoreIncrement : this.outputRowLimit), tx.vout.length); + } } ngOnDestroy(): void { From 01a727a344d961527a8156a03244e944d28270ca Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 22 Nov 2022 18:05:20 +0900 Subject: [PATCH 13/17] fix stray space, automatically show more outputs if <5 remaining --- .../transactions-list.component.html | 4 ++-- .../transactions-list.component.ts | 18 ++++++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) 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 139da368b..8368a62c4 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.html +++ b/frontend/src/app/components/transactions-list/transactions-list.component.html @@ -153,7 +153,7 @@ Show more - ({{ tx.vin.length - getVinLimit(tx) }} remaining ) + ({{ tx.vin.length - getVinLimit(tx) }} remaining) @@ -270,7 +270,7 @@ Show more - ({{ tx.vout.length - getVoutLimit(tx) }} remaining ) + ({{ tx.vout.length - getVoutLimit(tx) }} remaining) 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 be5bd5343..b09226f33 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.ts +++ b/frontend/src/app/components/transactions-list/transactions-list.component.ts @@ -230,19 +230,29 @@ export class TransactionsListComponent implements OnInit, OnChanges { } getVinLimit(tx: Transaction, next = false): number { + let limit; if ((tx['@vinLimit'] || 0) > this.inputRowLimit) { - return Math.min(tx['@vinLimit'] + (next ? this.showMoreIncrement : 0), tx.vin.length); + limit = Math.min(tx['@vinLimit'] + (next ? this.showMoreIncrement : 0), tx.vin.length); } else { - return Math.min((next ? this.showMoreIncrement : this.inputRowLimit), tx.vin.length); + limit = Math.min((next ? this.showMoreIncrement : this.inputRowLimit), tx.vin.length); } + if (tx.vin.length - limit <= 5) { + limit = tx.vin.length; + } + return limit; } getVoutLimit(tx: Transaction, next = false): number { + let limit; if ((tx['@voutLimit'] || 0) > this.outputRowLimit) { - return Math.min(tx['@voutLimit'] + (next ? this.showMoreIncrement : 0), tx.vout.length); + limit = Math.min(tx['@voutLimit'] + (next ? this.showMoreIncrement : 0), tx.vout.length); } else { - return Math.min((next ? this.showMoreIncrement : this.outputRowLimit), tx.vout.length); + limit = Math.min((next ? this.showMoreIncrement : this.outputRowLimit), tx.vout.length); } + if (tx.vout.length - limit <= 5) { + limit = tx.vout.length; + } + return limit; } ngOnDestroy(): void { From 4f3296566af0801e0243df64433fa53269a45478 Mon Sep 17 00:00:00 2001 From: softsimon Date: Tue, 22 Nov 2022 19:08:09 +0900 Subject: [PATCH 14/17] Make api available on all backends --- backend/src/api/bitcoin/bitcoin.routes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/api/bitcoin/bitcoin.routes.ts b/backend/src/api/bitcoin/bitcoin.routes.ts index e774a0ded..433f4bdb7 100644 --- a/backend/src/api/bitcoin/bitcoin.routes.ts +++ b/backend/src/api/bitcoin/bitcoin.routes.ts @@ -88,7 +88,8 @@ class BitcoinRoutes { .get(config.MEMPOOL.API_URL_PREFIX + 'blocks', this.getBlocks.bind(this)) .get(config.MEMPOOL.API_URL_PREFIX + 'blocks/:height', this.getBlocks.bind(this)) .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash', this.getBlock) - .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/summary', this.getStrippedBlockTransactions); + .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/summary', this.getStrippedBlockTransactions) + .post(config.MEMPOOL.API_URL_PREFIX + 'psbt/addparents', this.postPsbtCompletion) ; if (config.MEMPOOL.BACKEND !== 'esplora') { @@ -96,7 +97,6 @@ class BitcoinRoutes { .get(config.MEMPOOL.API_URL_PREFIX + 'mempool', this.getMempool) .get(config.MEMPOOL.API_URL_PREFIX + 'mempool/txids', this.getMempoolTxIds) .get(config.MEMPOOL.API_URL_PREFIX + 'mempool/recent', this.getRecentMempoolTransactions) - .post(config.MEMPOOL.API_URL_PREFIX + 'psbt/addparents', this.postPsbtCompletion) .get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId', this.getTransaction) .post(config.MEMPOOL.API_URL_PREFIX + 'tx', this.$postTransaction) .get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/hex', this.getRawTransaction) From 584f443f56cad1f7297bd5d4b646100b5d384c19 Mon Sep 17 00:00:00 2001 From: softsimon Date: Tue, 22 Nov 2022 21:45:05 +0900 Subject: [PATCH 15/17] Adding new getTransactionHex api --- .../src/api/bitcoin/bitcoin-api-abstract-factory.ts | 1 + backend/src/api/bitcoin/bitcoin-api.ts | 5 +++++ backend/src/api/bitcoin/bitcoin.routes.ts | 10 +++++----- backend/src/api/bitcoin/esplora-api.ts | 5 +++++ 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts index 358bd29e4..aa9fe5d15 100644 --- a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts +++ b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts @@ -3,6 +3,7 @@ import { IEsploraApi } from './esplora-api.interface'; export interface AbstractBitcoinApi { $getRawMempool(): Promise; $getRawTransaction(txId: string, skipConversion?: boolean, addPrevout?: boolean, lazyPrevouts?: boolean): Promise; + $getTransactionHex(txId: string): Promise; $getBlockHeightTip(): Promise; $getBlockHashTip(): Promise; $getTxIdsForBlock(hash: string): Promise; diff --git a/backend/src/api/bitcoin/bitcoin-api.ts b/backend/src/api/bitcoin/bitcoin-api.ts index ebde5cc07..0a3d674ec 100644 --- a/backend/src/api/bitcoin/bitcoin-api.ts +++ b/backend/src/api/bitcoin/bitcoin-api.ts @@ -57,6 +57,11 @@ class BitcoinApi implements AbstractBitcoinApi { }); } + $getTransactionHex(txId: string): Promise { + return this.$getRawTransaction(txId, true) + .then((tx) => tx.hex || ''); + } + $getBlockHeightTip(): Promise { return this.bitcoindClient.getChainTips() .then((result: IBitcoinApi.ChainTips[]) => { diff --git a/backend/src/api/bitcoin/bitcoin.routes.ts b/backend/src/api/bitcoin/bitcoin.routes.ts index 433f4bdb7..3740cccd4 100644 --- a/backend/src/api/bitcoin/bitcoin.routes.ts +++ b/backend/src/api/bitcoin/bitcoin.routes.ts @@ -250,7 +250,7 @@ class BitcoinRoutes { * the full parent transaction even with segwit inputs. * It will respond with a text/plain PSBT in the same format (hex|base64). */ - private async postPsbtCompletion(req: Request, res: Response) { + private async postPsbtCompletion(req: Request, res: Response): Promise { res.setHeader('content-type', 'text/plain'); const notFoundError = `Couldn't get transaction hex for parent of input`; try { @@ -275,11 +275,11 @@ class BitcoinRoutes { .reverse() .toString('hex'); - let transaction: IEsploraApi.Transaction; + let transactionHex: string; // If missing transaction, return 404 status error try { - transaction = await bitcoinApi.$getRawTransaction(txid, true); - if (!transaction.hex) { + transactionHex = await bitcoinApi.$getTransactionHex(txid); + if (!transactionHex) { throw new Error(''); } } catch (err) { @@ -287,7 +287,7 @@ class BitcoinRoutes { } psbt.updateInput(index, { - nonWitnessUtxo: Buffer.from(transaction.hex, 'hex'), + nonWitnessUtxo: Buffer.from(transactionHex, 'hex'), }); if (!isModified) { isModified = true; diff --git a/backend/src/api/bitcoin/esplora-api.ts b/backend/src/api/bitcoin/esplora-api.ts index ebaf2f6a0..3662347d6 100644 --- a/backend/src/api/bitcoin/esplora-api.ts +++ b/backend/src/api/bitcoin/esplora-api.ts @@ -20,6 +20,11 @@ class ElectrsApi implements AbstractBitcoinApi { .then((response) => response.data); } + $getTransactionHex(txId: string): Promise { + return axios.get(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/hex', this.axiosConfig) + .then((response) => response.data); + } + $getBlockHeightTip(): Promise { return axios.get(config.ESPLORA.REST_API_URL + '/blocks/tip/height', this.axiosConfig) .then((response) => response.data); From e08902b85b2d605d9fa2f0b7889789b8813d0e4f Mon Sep 17 00:00:00 2001 From: wiz Date: Wed, 23 Nov 2022 14:09:54 +0900 Subject: [PATCH 16/17] Fix nginx redirects for /liquid etc. --- production/nginx/location-redirects.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/production/nginx/location-redirects.conf b/production/nginx/location-redirects.conf index c6f20e7ad..d330b9deb 100644 --- a/production/nginx/location-redirects.conf +++ b/production/nginx/location-redirects.conf @@ -1,12 +1,12 @@ # redirect mempool.space/liquid to liquid.network -location /liquid { +location = /liquid { rewrite /liquid/(.*) https://liquid.network/$1; rewrite /liquid https://liquid.network/; return 308; } # redirect mempool.space/liquidtestnet to liquid.network/testnet -location /liquidtestnet { +location = /liquidtestnet { rewrite /liquidtestnet/(.*) https://liquid.network/testnet/$1; rewrite /liquidtestnet/ https://liquid.network/testnet/; rewrite /liquidtestnet https://liquid.network/testnet; @@ -14,7 +14,7 @@ location /liquidtestnet { } # redirect mempool.space/bisq to bisq.markets -location /bisq { +location = /bisq { rewrite /bisq/(.*) https://bisq.markets/$1; rewrite /bisq https://bisq.markets/; return 308; From 74dbd6cee12be3ac7096b2b19da1066fa18dd01c Mon Sep 17 00:00:00 2001 From: softsimon Date: Wed, 23 Nov 2022 18:43:37 +0900 Subject: [PATCH 17/17] Add support for application/base64 content type --- backend/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 09a12e200..cd81e4994 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -83,7 +83,7 @@ class Server { next(); }) .use(express.urlencoded({ extended: true })) - .use(express.text()) + .use(express.text({ type: ['text/plain', 'application/base64'] })) ; this.server = http.createServer(this.app);
- +
- +
- +