fix(key-wallet): return the fee the tx actually pays from build/build_signed#872
Conversation
…_signed TransactionBuilder::build_unsigned()/build_signed() returned a fee computed from the encoded size of the built transaction. When the builder drops a dust change remainder (0 < change <= 546 duffs), those duffs go to miners, so the real fee is the size fee plus the dust — the returned value under-reported by up to 546 duffs exactly when the fee got larger. Compute the returned fee as sum(selected input values) - sum(output values) instead, which is what the transaction actually pays in every case (normal change, exact change, dust drop, and drain). Remove the now-unused encoded_size helper and its error paths, and update the doc comments. Fixes #871 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 47 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthrough
ChangesActual transaction fee reporting
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #872 +/- ##
==========================================
+ Coverage 73.67% 73.74% +0.07%
==========================================
Files 325 325
Lines 73775 73799 +24
==========================================
+ Hits 54353 54424 +71
+ Misses 19422 19375 -47
|
…ot fee An asset lock burns the locked amount into an on-chain OP_RETURN output mirroring the payload's credit outputs, so it is part of the output sum and inputs - outputs yields the miner fee only. Add a regression test proving the returned fee excludes the locked credits. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Reviewed |
Fixes #871
Problem
TransactionBuilder::build_unsigned()/build_signed()returned a fee computed from the encoded size of the built transaction (fee_rate.calculate_fee(encoded_size(tx))). When the builder drops a dust change remainder (0 < change ≤ 546duffs), those duffs go to miners — the transaction's real fee is the size fee plus the dust, so the returned value under-reported by up to 546 duffs exactly in the case where the fee got larger. Callers surfacing this value as "the network fee" (e.g.send_paymentvia FFI/Swift, see dashpay/platform#4049) showed a wrong number.Fix
Return
Σ(selected input values) − Σ(output values)— the fee the transaction actually pays — from bothbuild_unsigned()andbuild_signed(). Both totals are already in scope, so this removes code rather than adding lookups:build_unsigned(): fee computed from the assembled tx's inputs/outputs.build_signed(): input total captured before signing consumes the UTXO list; output total read from the signed tx.encoded_sizehelper, its error paths (including the reservation-release-on-encode-failure branches), and theEncodableimport are removed.The semantics are unchanged in the normal cases: with emitted change or a drain (
SelectionStrategy::All), inputs − outputs equals the size-based estimate. Only the dust-drop case changes, where the new value is the correct one.Consumer audit
Internal consumers pass the fee through unchanged:
key-wallet-ffi(transaction.rsfee_out),AssetLockResult::fee, andtransaction_building.rswrappers. The dash-spv integration tests assert fees via balance deltas, which already match the corrected semantics.Testing
test_dropped_dust_change_counts_toward_returned_fee: 150 526-duff input, 150 000-duff output → 300-duff dust dropped, returned fee is 526 (not the 226 size fee).cargo test -p key-wallet— 526 passedcargo test -p key-wallet-ffi— all passedcargo test -p dash-spv --test dashd_sync transactionagainst a real dashd regtest node — 9 passedcargo clippy -p key-wallet --all-features --all-targetsandcargo fmt --check— cleanFollow-up
dashpay/platform can now delete the
send_paymentcaller-side recomputation block and takebuild_signed's fee directly (pinned by the regression tests added in dashpay/platform#4049).🤖 Generated with Claude Code
Summary by CodeRabbit