wallet: fix mixed-input transaction accounting in history RPCs #34872

pull w0xlt wants to merge 3 commits into bitcoin:master from w0xlt:wallet-mixed-input-history-only changing 7 files +353 −44
  1. w0xlt commented at 10:47 PM on March 19, 2026: contributor

    When a wallet owns only some inputs of a transaction, such as in CoinJoins, payjoins, or other collaborative transactions, wallet history RPCs currently apply normal send accounting.

    That works when all inputs are wallet-owned, but breaks for mixed-input transactions.

    Example:

    Inputs:

    1.00 BTC wallet-owned
    2.00 BTC foreign
    

    Outputs:

    0.80 BTC wallet-owned
    2.19 BTC non-wallet
    

    The wallet can safely know:

    wallet_debit  = 1.00
    wallet_credit = 0.80
    wallet_net    = -0.20
    

    But it cannot reliably know, from wallet history alone, the foreign input value, the total fee, the wallet's fee share, or which non-wallet output should be attributed as the user's payment. This is especially important after restore, rescan, or descriptor import, where any local transaction-intent metadata may be missing.

    This PR adds a conservative fallback for that no-metadata case:

    • classify wallet transactions as having no wallet inputs, partial wallet inputs, or all wallet inputs
    • keep existing send/fee accounting when all inputs are wallet-owned
    • report partial-input transactions as category: "mixed"
    • expose wallet_debit, wallet_credit, and the wallet net amount
    • avoid fabricating send entries or fees when attribution is unknown

    This does not try to fully solve collaborative transaction attribution. A richer layer with foreign-output / fee-share / intent metadata would still be useful for wallet-created transactions. This PR only makes the fallback honest when that metadata is unavailable.

    Addresses #14136

  2. DrahtBot added the label Wallet on Mar 19, 2026
  3. DrahtBot commented at 10:47 PM on March 19, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/34872.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    Concept NACK achow101
    Concept ACK rkrux

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #35009 (wallet, rpc: add include_change parameter to listtransactions by alfonsoromanz)
    • #34931 (validation: abort on DB unreadable coins instead of treating them as missing by furszy)
    • #34764 (rpc: replace ELISION references with explicit result fields by satsfy)
    • #34683 (rpc: support a formal description of our JSON-RPC interface by willcl-ark)
    • #27865 (wallet: Track no-longer-spendable TXOs separately by achow101)

    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.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

    LLM Linter (✨ experimental)

    Possible places where named args for integral literals may be used (e.g. func(x, /*named_arg=*/0) in C++, and func(x, named_arg=0) in Python):

    • ListTransactions(*pwallet, wtx, 0, false, details, /*filter_label=*/std::nullopt, /*include_change=*/false, /*include_mixed_received=*/true) in src/wallet/rpc/transactions.cpp

    <sup>2026-05-20 13:35:28</sup>

  4. luke-jr commented at 2:56 PM on March 20, 2026: member

    This won't show even the true send anymore... not sure that's a good idea.

    See #25991 for what is needed to fix this properly.

  5. w0xlt commented at 5:40 AM on March 22, 2026: contributor

    The goal here, in this PR, is a smaller immediate fix: for mixed-input transactions, the history RPCs should stop reporting send entries and full fees when the wallet cannot attribute them objectively.

    #25991 is an interesting direction, but as written it depends on manually providing additional wallet-local per-output metadata. Without that metadata, we still need a safe fallback, and that is what this PR provides.

  6. rkrux commented at 12:24 PM on March 23, 2026: contributor

    As I read the related issue #14136, it's unfortunate that the RPC shows incorrect information in the result - glad that this PR tries to address it.

    However, I'm leaning towards a NACK because this PR removes the send category details of such transactions altogether, which is not correct. The wallet does participate (though partially) in sending funds within these transactions and not showing those can be problematic as well.

    From #34872#issue-4104631274

    Fixes this by introducing CachedTxIsFromMeForAccounting,

    I am also not in favour of having two different views of CachedTxIsFromMe - one without accounting and one with. Having two views is also a sign that there is mismatch between what the wallet shows to the user and what the wallet does for the user.

    From #34872 (comment):

    Without that metadata, we still need a safe fallback, and that is what this PR provides.

    Due to the above reasons, I don't believe this is a safe fallback, and it only replaces one issue by another.

    From #34872#issue-4104631274

    This is because the accounting code treats any nDebit > 0 (i.e. any wallet-owned input) as a full wallet spend.

    It appears that this assumption made by the wallet doesn't hold true anymore and maybe it needs to be reworked for this issue to be completely fixed.

  7. w0xlt commented at 8:15 PM on March 23, 2026: contributor

    @rkrux Thanks for the review. I agree this isn’t a complete solution for mixed-input accounting.

    The goal here is narrower: to prevent history RPCs from reporting send entries and full fees that the wallet cannot objectively attribute from the transaction alone.

    So I see this as an improvement over the current behavior.

    A complete solution likely requires additional attribution metadata. However, that’s tricky - even if we add something like #25991, or a more automated way to set it, the metadata would not survive an importdescriptors restore, since there’s currently no way to import it.

    In those cases, we would still need a conservative fallback for history RPCs when attribution is unavailable.

    Do you see another way to address this without relying on additional metadata?

  8. achow101 commented at 8:55 PM on May 19, 2026: member

    NACK

    I think it's even more misleading to not show any send entries at all for transactions where we participate in the inputs.

  9. murchandamus commented at 9:56 PM on May 19, 2026: member

    At first glance, I would expect our wallet to be able to recognize which inputs are ours, and I’m astonished that it would just assume that all of the inputs must be ours when it recognizes one. Would it perhaps be possible to recognize such transactions as “multi user transactions” and only apply the inputs that came from our wallet as debits? If our wallet’s balance is decreased by the transaction, we might recognize any outputs from the transaction to our wallet as change output, whereas if the wallet’s balance is increased by the transaction (e.g., a Payjoin that paid us), we would treat the output as an incoming payment. The transaction should then be labeled as having us paid the amount that our wallet balance increased.

  10. achow101 commented at 10:23 PM on May 19, 2026: member

    it would just assume that all of the inputs must be ours when it recognizes one

    That's not really what it is doing. It is correctly treating transactions that have any inputs belonging to the wallet as that transaction belonging to the wallet. The "assumption" comes from CachedTxGetDebit only retrieving the total amount of our inputs, which only works when all of the inputs of the transaction belong to the wallet.

    But without storing more information, there is nothing that we can do to fix that. It shouldn't be astonishing that we are unable to calculate fees when the wallet does not know how much value the inputs that it doesn't know about are providing to the transaction.

    The transaction should then be labeled as having us paid the amount that our wallet balance increased.

    This isn't about annotating Bitcoin transactions, it's about annotating logical financial transactions and trying to determine what they are from a single Bitcoin transaction. Because a Bitcoin transaction can have multiple outputs, it can have multiple logical transactions, and the wallet tries to display these as such. For example, a multi party transaction could conceivably contain an output created by someone else in that transaction which pays us, while the same transaction simultaneously has an output that we create paying someone else. These should show as two entries in listtransactions - one showing a receive and one showing the send. The issue is with annotating the rest of the outputs (we don't store the logical transactions, so how do we know that the remaining outputs are not sends initiated by the user), and showing the correct amount for the fee (we don't know about the inputs that aren't ours, thus we cannot accurately calculate the fees).

    The solution presented in this PR is to omit all of that logical transaction information entirely for multi party transactions, which I disagree with.

  11. wallet: add mixed-input history accounting helpers bca744efa5
  12. wallet,rpc: report mixed-input history fallback 29fcb54dac
  13. test: cover mixed-input wallet history a302846565
  14. w0xlt force-pushed on May 20, 2026
  15. w0xlt commented at 1:41 PM on May 20, 2026: contributor

    @achow101 @murchandamus Thanks for the feedback.

    I pushed a new proposal that addresses it.

    As I understand it, a complete solution has two layers:

    1. A conservative fallback for mixed-input transactions when the wallet has no extra attribution metadata.

    
2. A richer metadata layer, e.g. foreign-output / ownership / fee-share marks, for wallet-created collaborative transactions.

    The idea here to solve the layer 1 only. Layer 2 is still useful, but it cannot replace the fallback. If a wallet is restored from descriptors, rescanned, or imported without the original local metadata, the wallet still needs a reliable way to report history without inventing send outputs or fees.

  16. w0xlt commented at 1:47 PM on May 20, 2026: contributor

    The new proposal:



    In master, wallet history accounting effectively does this when any input is ours:

    wallet_debit = sum(wallet-owned inputs)
    fee = wallet_debit - total_outputs
    send entries = all non-change outputs
    

    That works only when all inputs are wallet-owned.

    Example:

    Inputs:

        1.00 BTC mine
        2.00 BTC foreign
    

    Outputs:

        0.80 BTC mine
        2.19 BTC foreign
    

    Master can calculate wallet_debit = 1.00, but it does not know/store the foreign input value reliably in wallet history. So subtracting all outputs from only wallet-owned inputs gives wrong accounting:

    fee = 1.00 - 2.99 = -1.99

    It can also report foreign outputs as wallet send entries, even though the wallet cannot know from the transaction alone whether those outputs are wallet's payment, another participant’s change, or something else.

    This new push changes the previous fallback model to:

    wallet_debit  = sum(wallet-owned inputs)
    wallet_credit = sum(wallet-owned outputs)
    amount        = wallet_credit - wallet_debit
    

    For the same example:

    wallet_debit  = 1.00
    wallet_credit = 0.80
    amount        = -0.20
    fee_known     = false
    category      = mixed
    

    So the wallet reports the part it can prove: “the wallet balance went down by 0.20 BTC.” It does not guess the fee share or recipient attribution.


  17. w0xlt commented at 1:49 PM on May 20, 2026: contributor

    Moving this to draft for further discussion.

  18. w0xlt marked this as a draft on May 20, 2026
  19. w0xlt commented at 2:02 PM on May 20, 2026: contributor

    PR description updated.

  20. rkrux commented at 2:50 PM on May 20, 2026: contributor

    Do you see another way to address this without relying on additional metadata?

    Sorry, I had forgotten about this PR. I would prefer a basic conservative solution first without relying on additional metadata by letting the wallet return correct information (as much as it can) while not showing incorrect responses based on any missing transaction data.

    Based on a cursory glance, the new proposal seems to be in this^ direction, will review.

  21. DrahtBot added the label CI failed on May 20, 2026
  22. w0xlt commented at 5:13 PM on May 20, 2026: contributor

    CI error unrelated

  23. DrahtBot removed the label CI failed on May 21, 2026
  24. rkrux commented at 2:16 PM on May 21, 2026: contributor

    Concept ACK 29fcb54dac4a8f4b45573ba20b97874eec81be9d

    The new approach lgtm, will review when taken out of draft.

  25. w0xlt marked this as ready for review on May 21, 2026
  26. w0xlt commented at 5:07 PM on May 21, 2026: contributor

    Done.


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-05-31 18:51 UTC

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