`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>