Skip to content

Conversation

t-bast
Copy link
Contributor

@t-bast t-bast commented Feb 21, 2022

We add an RPC to fetch mempool transactions spending any of the given outpoints.

Without this RPC, application developers need to first call getrawmempool which returns a long list of txid, then fetch each of these transactions individually (getrawtransaction) to check whether they spend the given outpoints, which wastes a lot of bandwidth (in the worst case we need to transfer the whole mempool).

For example in lightning, when we discover that one of our channel funding transactions has been spent, we need to find the spending transaction to claim our outputs from it. We are currently forced to fetch the whole mempool to do the analysis ourselves, which is quite costly.

I believe that this RPC is also generally useful when doing some introspection on your mempool after one of your transactions failed to broadcast, for example when you implement RBF at the application level. Fetching and analyzing the conflicting transaction gives you more information to successfully replace it.

@t-bast t-bast force-pushed the get-mempool-spending-tx branch from a7966e4 to 2a7db52 Compare February 21, 2022 17:25
@darosior
Copy link
Member

For this usecase, why not using getmempoolentry's spentby field?

@t-bast
Copy link
Contributor Author

t-bast commented Feb 21, 2022

For this usecase, why not using getmempoolentry's spentby field?

Because that wouldn't work for confirmed utxos!

My funding transaction (or any L2 protocol initial state) has been confirmed on-chain and I want to check who spends it in the mempool. I can't use getmempoolentry because I don't know yet the txid of the spending tx...

Maybe the RPC name is misleading, because its inputs (txid and vout) aren't necessarily for a mempool tx (and most of the time will not), but the RPC output will be a mempool tx (or null).

@darosior
Copy link
Member

Right, sorry, i shouldn't have inferred the behaviour from the name. I see it's in the context of txindex then, so i don't have an opinion.

@t-bast
Copy link
Contributor Author

t-bast commented Feb 21, 2022

It's much weaker than txindex, it's exposing a txindex for mempool contents, which is why I think it's acceptable (I'm only querying existing mempool functions).

@ghost
Copy link

ghost commented Feb 22, 2022

For this usecase, why not using getmempoolentry's spentby field?

Because that wouldn't work for confirmed utxos!

My funding transaction (or any L2 protocol initial state) has been confirmed on-chain and I want to check who spends it in the mempool. I can't use getmempoolentry because I don't know yet the txid of the spending tx...

Maybe the RPC name is misleading, because its inputs (txid and vout) aren't necessarily for a mempool tx (and most of the time will not), but the RPC output will be a mempool tx (or null).

Is it possible to add it in existing RPC getmempoolentry by adding more arguments (optional)?

@t-bast
Copy link
Contributor Author

t-bast commented Feb 22, 2022

Is it possible to add it in existing RPC getmempoolentry by adding more arguments (optional)?

I'm not sure it would be very ergonomic, because getmempoolentry requires the txid of the mempool transaction you're fetching. If we wanted to overload it, it would have the following two distinct sets of behaviors:

  • a txid is provided, so we return a mempool transaction that matches this txid
  • a txid (probably named differently to avoid confusion) and a vout are provided, so we return a mempool transaction spending this given txid:vout

Correctly defining the arguments lists (and which are required) to support both use-cases without creating confusion seems hard to be honest...

@nopara73
Copy link

Not being able to walk the chain forward is certainly the source of huge friction and hacky code while working with Bitcoin RPC. I'd even go that far to claim that this is the single largest missing feature that if we'd have, it would have saved the most time for me personally. Certainly cACK this approach, but I'd like to note a general version of it would be more useful, somehow we should be able to determine which transaction spends a UTXO if any, regardless if it's from mempool or not.

Copy link
Member

@glozow glozow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK, seems very reasonable to me especially since we already have a mempool function to fetch the transaction. Also in general, it seems useful to be able to fetch the transaction that conflicts with yours.

Comment on lines 792 to 804
"\nScans the mempool to find a transaction spending the given output\n",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "transaction id"},
{"n", RPCArg::Type::NUM, RPCArg::Optional::NO, "vout number"},
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if, in the future, you might want to pass in a list of prevouts? (e.g. if you have transaction(s) with multiple inputs and want to see if anything in the mempool conflicts)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good idea, it's worth adding this flexibility right now to avoid running in backwards-compatibility issues if we add it later, I'll try that. I'll take a list of prevouts and return a map of prevout -> spending tx (or null if not found).

Copy link
Contributor Author

@t-bast t-bast Feb 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 0015ceb
I chose to return only the spendingtxid instead of the mempool tx data, because otherwise if a transaction appears multiple times (because it spends many of the provided prevouts) it would be duplicated, which is a (small) waste.

Callers can chain this with getmempoolentry to get more data about the conflicting transaction.
If you think it makes more sense to directly include the mempool tx let me know, I don't have strong feelings about that.

@glozow
Copy link
Member

glozow commented Feb 22, 2022

I'd like to note a general version of it would be more useful, somehow we should be able to determine which transaction spends a UTXO if any, regardless if it's from mempool or not.

This might be tricky (the non-mempool case) since we would have deleted the UTXO from our chainstate. And not possible sometimes on a pruned node.

@michaelfolkson
Copy link

Concept ACK - rationale in PR description seems strong and other projects (e.g. Wasabi) have experienced same issue.

I'd like to note a general version of it would be more useful, somehow we should be able to determine which transaction spends a UTXO if any, regardless if it's from mempool or not

I think the use case from @nopara73's comment above would be you don't know whether the transaction spending the UTXO is in your mempool or confirmed onchain and you want to query your mempool first and then your transaction index? I think generally you'd want one RPC to query your mempool and one RPC to query your transaction index. Combining both into a single RPC seems to me against separation of concerns (and is easily resolved by just running two RPC commands).

@nopara73
Copy link

FTR there's gettxout. I think my suggestion would fit best there.

@t-bast
Copy link
Contributor Author

t-bast commented Feb 22, 2022

FTR there's gettxout. I think my suggestion would fit best there.

@nopara73 do you mean that instead of adding a new RPC like I'm doing, we could update gettxout to return the spending tx when the utxo is spent?

In the case where the spending tx is not in the mempool, I think the only solution would be to walk the chain backwards and analyze each block to see if the spending tx is inside it. However, as @glozow says, if you're running a pruned node, you will likely analyze every block you have and won't find anything, which is very inefficient. Even worse, if the provided utxo never existed, you will walk the whole chain without finding anything...

It looks like a much more debatable change which would involve serious trade-offs, that's why I think restricting this PR to mempool txs has a better chance of being accepted. I could still reuse gettxout instead of adding a new RPC, but that would make it harder (backwards-compatibility wise) to accept a list of prevouts as @glozow suggests here.

@t-bast t-bast force-pushed the get-mempool-spending-tx branch 2 times, most recently from 0b6cdaa to 4ef5a16 Compare February 24, 2022 16:28
@DrahtBot
Copy link
Contributor

DrahtBot commented Feb 25, 2022

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #25204 (rpc: remove deprecated top-level fee fields from mempool entries by theStack)
  • #22341 (rpc: add getxpub by Sjors)
  • #21422 (Add feerate histogram to getmempoolinfo by kiminuo)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@t-bast t-bast changed the title Add RPC to get mempool spending tx rpc: add rpc to get mempool spending tx Feb 25, 2022
@t-bast t-bast changed the title rpc: add rpc to get mempool spending tx rpc: add rpc to get mempool txs spending specific prevouts Feb 25, 2022
Copy link
Contributor

@w0xlt w0xlt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK.
Maybe the two commits can be merged. Apparently the first commit is in an invalid state (since the outputs parameter is not converted).

@t-bast t-bast force-pushed the get-mempool-spending-tx branch from 263b0a7 to 0015ceb Compare February 25, 2022 15:40
@t-bast
Copy link
Contributor Author

t-bast commented Feb 25, 2022

Maybe the two commits can be merged.

Definitely, done. I was waiting for the builds to complete to check that I hadn't missed something else that would need fixing.

@maflcko
Copy link
Member

maflcko commented Mar 3, 2022

Without this RPC, application developers need to first call getrawmempool which returns a long list of txid, then fetch each of these transactions individually (getrawtransaction) to check whether they spend the given outpoints, which wastes a lot of bandwidth (in the worst case we need to transfer the whole mempool).

I think without this RPC, you can create a tx that spends the input and only fetch the whole mempool if the input was spent.

Moreover, I wonder what happens if the tx is included in a block right away? So the input will be gone, but there isn't a tx in the mempool that spends it.

While the RPC makes sense on its own, it doesn't fix your problem completely?

@t-bast
Copy link
Contributor Author

t-bast commented Mar 3, 2022

Thanks for the review @MarcoFalke!

I think without this RPC, you can create a tx that spends the input and only fetch the whole mempool if the input was spent.
Moreover, I wonder what happens if the tx is included in a block right away? So the input will be gone, but there isn't a tx in the mempool that spends it.
While the RPC makes sense on its own, it doesn't fix your problem completely?

I probably tried to be a bit too general in my description, let me try to explain in more details my current scenario.

In the specific case where I want to apply this in the short term, I'm trying to spend lightning htlcs. I have a pre-signed transaction that spends the outpoint I'm interested in but doesn't provide enough fees, so I'll want to add more inputs to reach the right feerate.

The complete flow looks like this:

  • I verify that the output is unspent with gettxout (without including the mempool)
    • If it's spent, then I have nothing to do (actually it triggers another flow to spend the output of that spending tx, but that's orthogonal)
    • Otherwise, I check if there is already a mempool tx spending this output with getmempooltxspendingprevout
      • If nothing is in the mempool, I'll use fundrawtransaction to add inputs and broadcast the transaction
      • Otherwise, I'll modify the mempool transaction to pay more fees (by lowering/removing the existing change output and potentially adding new inputs)

Without this RPC, I would have to optimistically fund and broadcast, and if that fails fetch the whole mempool to find the conflicting transaction. I expect to regularly have to go through the RBF flow, so it feels inefficient to have to dump the mempool when that happens :(

You're right that if the current mempool transaction confirms while I'm funding its replacement, I'll need to correctly handle it, but that doesn't seem to be too much of a problem for my use-case.

Does that make sense with this more detailed scenario?

@t-bast
Copy link
Contributor Author

t-bast commented Apr 25, 2022

Thanks for the review @glozow!

I don't feel strongly, but if you change your mind, I think it's perfectly fine to include the whole spending tx. There's not much waste since we've already retrieved it from the mempool, and only a privileged user can be using the RPC interface anyway.

I am more and more convinced that it's better to only have the spendingtxid. There is a lot of useful information in the output of getrawtransaction that callers will want, so they will very likely follow up with a call to that RPC anyway (and we don't want to replicate all of its contents in the output of gettxspendingprevout).

We're regularly seeing big coinjoin transactions in our systems, so if we include the whole tx in the RPC response we can easily have responses that span several Mb, which is a waste for bitcoind nodes on remote machines accessed through a (sometimes flaky) VPN (which we've seen some lightning node operators do).

@glozow
Copy link
Member

glozow commented Apr 26, 2022

re ACK 8c9152c

@maflcko
Copy link
Member

maflcko commented Apr 27, 2022

cc Antoine for review (@darosior @ariard), if interested.

Copy link

@ariard ariard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review ACK 8c9152c


A caveat : the usage of this RPC you're describing sounds unsafe to me. If you're desirous to monitor the spend of a shared-utxo such as LN funding output, your counterparty might be able to broadcast multiple conflicting transactions. If this counterparty knows your full-node, your mempool can even be partitioned from the rest of the network, with a junk feerate, potentially wrecking your fee-bump. Of course, this assumes a bit of cross-layer mapping work on your malicious counterparty. So better to reconciliate from multiple mempools or opt for blind fee-bump after a while imho.

@t-bast
Copy link
Contributor Author

t-bast commented Apr 28, 2022

A caveat : the usage of this RPC you're describing sounds unsafe to me. If you're desirous to monitor the spend of a shared-utxo such as LN funding output, your counterparty might be able to broadcast multiple conflicting transactions.

Thanks for the comments! But that shouldn't be an issue in our case (at least not related to this RPC). This RPC is helpful to react instantly once a shared output is spent. It doesn't matter if in the end it's another transaction that gets confirmed, we will learn this when we receive blocks and will then adapt. It then becomes a different problem of ensuring you're receiving blocks (avoiding complete eclipse attacks), which we solve by a combination of blockchain watchdogs and a good bitcoind setup.

Protecting against commit tx pinning is an entirely separate issue as well, which exists regardless of whether this RPC exists or not.

@t-bast t-bast force-pushed the get-mempool-spending-tx branch from 8c9152c to 8f9335f Compare April 28, 2022 06:40
@luke-jr
Copy link
Member

luke-jr commented Apr 29, 2022

@luke-jr can you detail how that would work?

You would add the txout you need to watch to the wallet (maybe we need to add a way to do that), then the wallet would scan every new block/tx internally, letting you know via listtransactions.

it would be wasteful to need both a watch-only wallet and a regular wallet to make this use-case work?

At least legacy wallets support both normal and watch-only in the same wallet...

Copy link
Contributor

@w0xlt w0xlt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tACK 8f9335f

I confirmed that the new RPC correctly returns the ids of transactions that spend the outpoints passed as a parameter.

@t-bast
Copy link
Contributor Author

t-bast commented May 2, 2022

@luke-jr thanks, I understand what you mean, I didn't know a wallet could be both normal and watch-only. This is something that could make sense, we are currently doing this scan outside of bitcoind, but having bitcoind do it for us could be useful.

Does it completely replace this PR though? I can see value in having both options (a dedicated RPC and a watch-only feature), the watch-only mode could be added afterwards (especially if we see people starting to rely on this new RPC).

Copy link
Member

@darosior darosior left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conceptually, this feature needs a transaction indexer and that's what a wallet is. I think ideally this feature should be part of the wallet as a more general "get spending tx by prevout" feature, rather than a very usecase-specific feature leveraging the mempool index.
However, it would be a bit convoluted to use at the moment. It would require them to import a new descriptor for each output they would need to claim, without (currently) the possibility to delete it. The number of descriptors under watch would therefore grow continuously as the software would be used.
Accounting for the immediate benefit to users, and the low cost of this RPC (it only calls into a single mempool function), i agree it can be merged. I believe longer term we should rather improve the wallet instead of providing (what i think are) specialized wallet functionalities at the node level.

light code review ACK 8f9335f -- the code makes sense to me and i didn't spot any issue. But i only quickly glanced at the diff.

(For reference, we have a similar issue with revaultd: in order to fetch a spending transaction from the watchonly wallet whose txid is unknown we need to iterate on the list of the wallet transactions. https://github.com/revault/revaultd/blob/b310208a8b3b420f9b0cddcc03588e264a99538b/src/bitcoind/interface.rs#L777-L864 (fortunately listsinceblock allows us to reduce the cost by only iterating on transactions since the last pool).)

@t-bast
Copy link
Contributor Author

t-bast commented May 2, 2022

Conceptually, this feature needs a transaction indexer and that's what a wallet is. I think ideally this feature should be part of the wallet as a more general "get spending tx by prevout" feature, rather than a very usecase-specific feature leveraging the mempool index.

I agree, and you're one step ahead, see #24539 :)
Thanks for the comments!

@t-bast t-bast force-pushed the get-mempool-spending-tx branch from 8f9335f to ad14372 Compare May 2, 2022 11:20
Copy link
Contributor

@w0xlt w0xlt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reACK ad14372

We add an RPC to fetch the mempool transactions spending given outpoints.
Without this RPC, application developers would need to first call
`getrawmempool` which returns a long list of `txid`, then fetch each of
these txs individually to check whether they spend the given outpoint(s).

This RPC can later be enriched to also find confirmed transactions instead
of being restricted to mempool transactions.
@t-bast t-bast force-pushed the get-mempool-spending-tx branch from ad14372 to 4185570 Compare May 5, 2022 12:57
@t-bast
Copy link
Contributor Author

t-bast commented May 5, 2022

Simple rebase on master without any changes in 4185570

@t-bast
Copy link
Contributor Author

t-bast commented May 18, 2022

I don't have any pending issues on that PR, if @ariard @darosior @glozow @MarcoFalke you have time to give this another pass whenever possible it would be great 🙏

Copy link
Member

@darosior darosior left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re-utACK 4185570

Copy link

@vincenzopalazzo vincenzopalazzo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re-ACK 4185570

Copy link
Contributor

@danielabrozzoni danielabrozzoni left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re-tACK 4185570

@fanquake fanquake requested a review from glozow May 18, 2022 09:41
luke-jr pushed a commit to luke-jr/bitcoin that referenced this pull request May 21, 2022
We add an RPC to fetch the mempool transactions spending given outpoints.
Without this RPC, application developers would need to first call
`getrawmempool` which returns a long list of `txid`, then fetch each of
these txs individually to check whether they spend the given outpoint(s).

This RPC can later be enriched to also find confirmed transactions instead
of being restricted to mempool transactions.

Github-Pull: bitcoin#24408
Rebased-From: ad14372
@fanquake
Copy link
Member

cc @glozow

Copy link
Contributor

@w0xlt w0xlt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reACK 4185570

@maflcko maflcko merged commit 3ba6dd6 into bitcoin:master May 27, 2022
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request May 28, 2022
…c prevouts

4185570 Add RPC to get mempool txs spending outputs (t-bast)

Pull request description:

  We add an RPC to fetch mempool transactions spending any of the given outpoints.

  Without this RPC, application developers need to first call `getrawmempool` which returns a long list of `txid`, then fetch each of these transactions individually (`getrawtransaction`) to check whether they spend the given outpoints, which wastes a lot of bandwidth (in the worst case we need to transfer the whole mempool).

  For example in lightning, when we discover that one of our channel funding transactions has been spent, we need to find the spending transaction to claim our outputs from it. We are currently forced to fetch the whole mempool to do the analysis ourselves, which is quite costly.

  I believe that this RPC is also generally useful when doing some introspection on your mempool after one of your transactions failed to broadcast, for example when you implement RBF at the application level. Fetching and analyzing the conflicting transaction gives you more information to successfully replace it.

ACKs for top commit:
  darosior:
    re-utACK 4185570
  vincenzopalazzo:
    re-ACK bitcoin@4185570
  danielabrozzoni:
    re-tACK 4185570
  w0xlt:
    reACK bitcoin@4185570

Tree-SHA512: 206687efb720308b7e0b6cf16dd0a994006c0b5a290c8eb386917a80130973a6356d0d5cae1c63a01bb29e066dd721594969db106cba7249214fcac90d2c3dbc
@t-bast t-bast deleted the get-mempool-spending-tx branch May 29, 2022 22:58
@bitcoin bitcoin locked and limited conversation to collaborators May 29, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.