Files
ObsidianDragon/third_party
DanSandClaude Opus 5.5 6f1ae874fb feat(lite-backend): select spend notes deterministically, max 250 inputs
send_to_address walked the wallet's notes in HashMap order and took them
until the target was covered, cloning the witness and spending key of
each note it visited, zero-value notes included. So which notes were
spent, how many and which address got the change were random: a send
could prove zero-value notes (each spend is a Groth16 proof of about
0.5-2 s plus 384 bytes) and far more notes than needed, and a send
needing 251-489 notes ground through minutes of proving to build a
transaction whose flat fee is below DragonX's default minimum relay
rate.

Selection is now a pure function (note_selection.rs) over the value and
nullifier of the eligible non-zero notes:
- the smallest single note that covers the target; otherwise the fewest
  notes, largest first, with the last pick swapped for the smallest note
  that still covers. Ties break on the nullifier, so map order no longer
  matters;
- at most 250 spends, and fewer when the estimated size would pass
  190,000 bytes (the consensus limit is 200,000);
- the notes cover the whole target whenever they can; only when they
  cannot do the keyed UTXOs, which are still spent in full, reduce what
  the notes must cover;
- only the chosen notes get their witness and key cloned.

Behaviour changes:
- which notes are spent, and so which address receives the change, is
  deterministic: the smallest single covering note, otherwise the fewest
  notes, largest first;
- zero-value notes are never spent;
- new errors before any proving: "Too many inputs: ..." (more than 250
  spends needed; it states the maximum sendable amount in DRGX) and
  "Transaction too large: ..." (it suggests shielding transparent funds
  first). Neither contains "Insufficient", so the
  GUI does not retry them. Sends that need 251-489 notes, which succeed
  today after minutes of proving, are refused;
- UTXOs alone fund a send only when no eligible non-zero note exists, or
  when the size budget leaves no room for a shielded input; then, as for
  a wallet without notes before, the change goes to the first z-address
  and is listed as a receive. Otherwise the smallest non-zero note is
  still spent, so the change is recorded as change;
- the "Insufficient verified funds" text is unchanged, but its "have"
  amount no longer counts UTXOs whose key the wallet lacks;
- duplicate nullifiers (corrupt state only) are de-duplicated;
- new info log: "Selected N of M spendable notes".

A note left with a stale witness by the pre-existing pending-send bug is
now picked every time it is the best fit, rather than by chance.

Pure tests check that the selection never uses more notes than the old
walk (3000 random cases, shuffled), that the count is minimal (brute
force) and independent of order, and the cap, size budget and fallbacks;
wallet tests cover best fit, a chat-fee send, the cap refused before
proving and UTXO funding. In the 1000-seed oracle every send verifies
offline at anchor offsets 0 and 4 and the largest send dropped from 7
inputs to 2; a differential run against the unmodified backend has
identical anchors and byte-identical non-selection state apart from the
cleared lists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 09:50:03 -05:00
..