wallet: Unrelated conflicted parent txs do not cause child txs to be marked as conflicted #29435

issue achow101 opened this issue on February 14, 2024
  1. achow101 commented at 5:45 PM on February 14, 2024: member

    Is there an existing issue for this?

    • I have searched the existing issues

    Current behaviour

    If we receive a transaction which spends from an unconfirmed parent, and that parent is replaced, the child does not get marked as conflicted but rather only as inactive. If the child contains an input from our wallet, that input cannot be reused until the child is abandoned.

    See also https://github.com/ACINQ/eclair/pull/2818

    Expected behaviour

    We should try to detect if a such a child transaction becomes conflicted due to a parent becoming conflicted and mark it as such.

    Steps to reproduce

    A (failing) test for this behavior can be found at https://github.com/achow101/bitcoin/commit/213fa91d5c289204e3f1e01194b1fccd08a69c6c

    Relevant log output

    No response

    How did you obtain Bitcoin Core

    Compiled from source

    What version of Bitcoin Core are you using?

    master@128b4a80387d322a7810d8716eccdac95ff9b8cd

    Operating system and version

    Arch

    Machine specifications

    No response

  2. Eunovo commented at 10:33 PM on March 2, 2024: contributor

    @achow101 What about situations where the parent goes from conflicted to unconfirmed?

  3. ryanofsky commented at 5:35 PM on March 12, 2024: contributor

    We should try to detect if a such a child transaction becomes conflicted due to a parent becoming conflicted and mark it as such.

    How could we do this? Would the idea be to add non-wallet transactions to mapTxSpends? This doesn't seem like a straightforward problem to solve.

  4. achow101 commented at 6:19 PM on March 12, 2024: member

    How could we do this? Would the idea be to add non-wallet transactions to mapTxSpends? This doesn't seem like a straightforward problem to solve.

    My initial thought was that we could just look at every tx that we get a TransactionRemovedFromMempool signal for and see if there are any descendants that are in the wallet.

    But I agree that this is nontrivial to solve.

  5. ryanofsky commented at 6:54 PM on March 12, 2024: contributor

    My initial thought was that we could just look at every tx that we get a TransactionRemovedFromMempool signal for and see if there are any descendants that are in the wallet.

    I think that makes sense if it's enough to detect conflicts where the parent transaction conflicts with a new block and is already in the mempool. The issue description doesn't seem to say whether or not the parent transaction is in the mempool, but maybe that's implied.

    Maybe one complication with using the TransactionRemovedFromMempool notification to detect block conflicts is that the notification doesn't include the hash of the conflicting block. But maybe this information could be added, or we could add back the vtxConflicted array argument to BlockConnected that was removed in #17477. I think recording the hash of the conflicting block is just mostly just important for reverting the conflicted state if there is a reorg.

  6. Eunovo commented at 7:46 PM on March 12, 2024: contributor

    Maybe one complication with using the TransactionRemovedFromMempool notification to detect block conflicts is that the notification doesn't include the hash of the conflicting block. But maybe this information could be added, or we could add back the vtxConflicted array argument to BlockConnected that was removed in #17477. I think recording the hash of the conflicting block is just mostly just important for reverting the conflicted state if there is a reorg. @ryanofsky I'm currently testing a rough PoC, https://github.com/Eunovo/bitcoin/commit/517e39cf1e88af18d82dfbb49292ddd89cad2761, that I think follows a similar idea but without using vtxConflicted array. I created a new map to keep track of non-wallet transactions, then I modified the REPLACED signal for TransactionRemovedFromMempool to include the txid of the double-spending transaction. By tracking this double-spend transactions, the wallet can detect conflicts in the BlockConnected signal and mark the wallet tx as conflicted.

  7. Eunovo commented at 7:49 PM on March 12, 2024: contributor

    My initial thought was that we could just look at every tx that we get a TransactionRemovedFromMempool signal for and see if there are any descendants that are in the wallet.

    @achow101 Not sure how we can access a mempool transaction's ancestry in the wallet. The mempool doesn't seem to be directly connected to the wallet.

  8. willcl-ark added the label Wallet on Jan 15, 2026
  9. molnard commented at 11:24 AM on September 4, 2026: none

    Building on @Eunovo 's suggestion), how about keeping the input dependencies of relevant non-wallet parents?

    Simple example:

    coin_external ---> tx_parent ---> coin_parent_output ---> tx_child
                   \                                           ^
                    ---> tx_replacement                        |
    coin_wallet -----------------------------------------------+
    

    Here, tx_replacement conflicts directly with tx_parent by spending coin_external. Our tx_child depends on coin_parent_output and also spends coin_wallet.

    The wallet knows the inputs of tx_child, including the reference to coin_parent_output, but it does not know the inputs tx_parent. The missing coin_external → tx_parent relationship prevents it from connecting the conflict to tx_child, so coin_wallet remains spent.

    I think we could keep a separate dependency index. For each relevant non-wallet parents/ancestor, keep:

    • Its transaction ID (txid).
    • All its input outpoints.
    • A reverse index from each parent transaction's txid, to its direct children: the transactions that spend its outputs. These children may be wallet or non-wallet transactions. For example, in tx_parent -> tx_middle -> tx_child, where only tx_child belongs to the wallet, following these links lets us propagate a conflict from tx_parent through tx_middle to tx_child. This index can be built from the stored input outpoints. We do not need to store every parent–children pair separately, we can walk through the chain. These records would need to survive mempool removal, since the conflicting transaction might confirm later.

    When a tx enters the mempool or appears in a block, we could look up its inputs, find the conflicting transactions, and follow their child links to collect all affected wallet descendants. We would then update these using the existing mempool and block conflict handling. In the example, this would mark tx_child as conflicted, allowing coin_wallet to become available.

    When collecting dependencies, we would follow each unconfirmed parent branch back to an output confirmed in the active chain. Already known parents could reuse the same records. Edge-cases to handle somehow:

    • A missing parent would mean incomplete information.
    • If a reorg removes a confirmation used as a stopping point (extend the dependency point beyond confirmation?).

    The same links would also be needed when a conflict disappears. Removing a conflicting tx from the mempool would remove its mempool conflict contribution, while removing a block(reorg) would require reevaluating the corresponding block conflict. Other remaining conflicts must be preserved. Ordinary mempool eviction without a known conflict would keep the current wallet behavior, coin_wallet is spent.

    This is also why I think we should follow actual input dependencies. If tx_replacement spends coin_external and another coin, a later replacement spending only that other coin, might not conflict with tx_parent. We should not automatically transfer the old conflict to the next replacement tx!

    The dependency records would need to survive mempool removal and wallet restarts, since the conflicting transaction might confirm later. The lookup indexes could be rebuilt from the stored records. We would still need to work out when those records can safely be deleted.

    If this approach looks reasonable, I think the first step would be to determine whether we can reliably build and restore the complete dependency graph, including delayed callbacks, reorgs, and periods when the wallet is unloaded. Would the existing callbacks and mempool queries, combined with temporary buffering and stored dependency records, provide enough information? This should also identify any cases where the required depgraf data cannot be recovered.


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-09-08 11:52 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me