Skip to content

dash-spv: a spend with no wallet-owned output is never recorded — coin stays unspent, balance overstated #992

Description

@HashEngineering

A watched coin can be spent by a transaction that pays nothing back to the wallet, so the transaction touches the wallet only through its input. We found this via CoinJoin collateral burns (sole output OP_RETURN, zero value), but it applies to any such transaction — a send-everything with no change, or an exact-amount payment that leaves no change. Burns made it easy to miss for a long time because the amounts are dust; a no-change send of a large coin fails the same way, silently.

On a restored testnet wallet, 4 of 5 burns were never found. The transactions are absent, the coins stay marked unspent, and the balance is overstated by 0.00048773 DASH with no error surfaced. Verified against a dashj wallet on the same seed, which holds all five.

burn tx height spends coin result
55f3bd9f7d58f58bde04d38251d9bc4c824e5a0a5221ff0c6f38f65410173935 1153140 d5dfbe889f8ec3c9a79aaacde1a4780119b08a049ddd06162f5d4f2deaf3cb03:0 found
f2fe8ad5aad30d6d0859a55db01b2c9240ee028eaff792516051f32c795377b7 1153176 6c3add0e8f9fee0bf36864e0839de335871c86e249b6f197d8aa89842270421e:0 missed
e821cec4c233d3e7a34f688727d04468ed136ebf1b8553f1ab046177ae813afb 1190152 ed7217e95dc1aab6fecd9da1f164249959f3f17540512770f37d87bfed46f1a7:0 missed
560b22d38c08b8a7be8d3732a8e05b822df7d47d5a1fda9e52f23d7cdc7c69e1 1190172 6b6b3c5cf0d3c73f498f391add2c44e219e8585da056cef7e78ffe53eca1846d:0 missed
da763356f267c57925400c44973e716d389c624f6c29fa69ed1d45e095cb8426 1194323 b29fb8c29ce5507be5bd9f4be5d58a52e50d80cb35bd3e9f0960f099d1486eef:0 missed

Not a gap-limit problem. Checking every unspent coin against dashj's spent set returns exactly these four; the other 1,062 agree. The wallet's CoinJoin chain runs past index 8,800 and ordinary spends there are found reliably. The only shared property of the misses is the transaction shape.

Likely mechanism. An ordinary spend can be recorded via an owned output or via a connected input; these transactions offer only the input. If the coin isn't in the UTXO set when that block is processed, the input can't be connected, the transaction is judged irrelevant, and nothing revisits it — a later filter match re-tests the height but doesn't re-attribute a spend an earlier pass discarded. So either the committed-range sweep never re-tests those heights for input-side matches, or it does and attribution still drops them. This makes restore and rescan the exposed paths: during live operation the wallet authored the send and already holds the coin.

Suggested regression test: fund a watched coin at H, spend it at H+n with a transaction that has no wallet-owned output, and process blocks so the coin isn't in the UTXO set when H+n is first scanned. Assert the coin ends spent.

Testnet; wallet synced to 1,542,559. Store snapshot and full audit available on request.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions