Drop a dust-sized change into the fee on an RBF bump and audit every refused bump (B-87, TX-11, TX-24, AUD-40, AUD-09, ADR-19)

`bump_fee` only required `change > fee delta`. A change at the 294 sat minimum (a full-balance
withdrawal) shrank to ~153 sat after the very first minimum bump, relay policy refuses a
replacement with a sub-dust output, and the bumper tried again every 30 seconds for ever, with
nothing but a log line to show for it.

- When what would be left of the reducible output (the user's change, a payout's bump
  reserve) is under DUST_LIMIT_SATS the output is dropped and its whole value goes to the fee:
  that replacement is valid and the last one possible. If the output does not exist, or even
  its whole value is below BIP125's minimum increase (vsize x 1 sat/vB), RbfError. The
  recorded fee rate uses the replacement's own, smaller, size.
- RbfError now carries a `reason` (`change_too_small`, `max_fee_rate`, `replacement_rejected`);
  RbfBumper._record_failure writes one `rbf_bump_failed` row {pending_transaction_id, kind,
  txid, reason, message[:200]} with the row's user_id/round_id, in a transaction of its own,
  once per (current txid, reason) - the audit log is the dedupe clock (AUD-09) - and the pending
  row is untouched. A new txid or a new reason writes a new row.
- bump_fee's docstring no longer cites the "bet without change" case that B-62 removed.

No existing test changed (all bumps they make leave a change well above dust). New
tests/unit/test_rbf_dust.py: the 294 sat case end to end (recipient untouched, whole change to
the fee, last bump impossible), the exact boundary (leaves 294 kept / leaves 293 dropped), a
change under the minimum increase, a payout's reserve dropped with the winner and fee outputs
byte-identical, and the audit rows for each reason, once per pair, new row on a new txid or
reason, round/user attribution. Mutation-checked: the dust test of the output and the
once-per-pair check each fail tests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5.5 committed 2026-10-16 14:53:10 +02:00
1 parent 54dbffb552
commit ccd15807ed
11 files changed
+440 -108

No files matched your search

-10
View File
@@ -102,16 +102,6 @@ decisions, see `docs/limiti-noti.md`):
## High
### B-87 — The RBF bump can leave a dust change output
`bump_fee` only requires change > fee delta. A 294-sat change (full-balance
withdrawal) becomes ~153 sat after the minimum bump and every replacement is refused
by relay, every 30s.
Fix: when a bump would leave change below `DUST_LIMIT_SATS`, the change output is
dropped and its value goes to the fee (the replacement is then final); `RbfError`
and every refused bump write a `rbf_bump_failed` audit row.
### B-89 — Key problems surface only at first use
Startup checks only that the key file exists and `XPRV_ENCRYPTION_KEY` is non-empty;