diff options
| author | Bitflash <btf01@proton.me> | 2026-07-31 16:37:13 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-07-31 16:37:13 -0300 |
| commit | 2d10bf257ffb177306b9cd10f7fc57f92d34b7e1 (patch) | |
| tree | 70577a961c626035d9d37af47af7f4c4b5fc1cc8 /src/main.cpp | |
| parent | 417bfed08ded5c6afaed25f4ef724b5629b13fae (diff) | |
wallet: stop paying the recipient the transaction fee on a fee retry (#74)
CreateTransaction kept the payee amount in nValue and then did
int64 nValueOut = nValue;
nValue += nFee;
with both lines inside the retry loop. The first pass is fine. When the fee
turns out to be too low the loop starts over, and on the second pass
nValueOut is read from an nValue that already carries the fee -- so the
recipient is sent the amount plus the fee, out of the sender's pocket, and
it compounds on every further retry.
Reachable, but it takes two things at once, which is why it has sat here:
the user must have set a transaction fee in Options (with the default of
zero, nValue += nFee adds nothing on the first pass and nothing is
corrupted), and the transaction must be big enough for GetMinFee to demand
more than that -- over 10 KB, so a wallet paying from many small inputs.
Keep nValue meaning what the payee asked for and carry nValue + nFee in a
separate nTotalValue, the way Bitcoin fixed it in 107d9e288.
Verified by building the same transaction on both versions, with a
synthetic wallet of 200 coins of 50, a configured fee of 0.05, and a
request of 5,000 that needs 101 inputs and 28 KB -- enough for GetMinFee to
demand 0.12 and force the retry:
before payee gets 5,000.05 change 49.83
after payee gets 5,000.00 change 49.88
Two other fixes from the same era were considered and left out. Satoshi's
CENT floor on the change output (496823249) exists to avoid a dust output
that his fee rules then penalised; this fee schedule has no such rule, so
adopting it would only donate sub-cent change to the miner. The
1-confirmation rule (a790fa46f) is a spending-policy change, not a bug fix,
and deserves its own decision.
Diffstat (limited to 'src/main.cpp')
| -rw-r--r-- | src/main.cpp | 26 |
1 files changed, 20 insertions, 6 deletions
diff --git a/src/main.cpp b/src/main.cpp index c3fb33b..5610de9 100644 --- a/src/main.cpp +++ b/src/main.cpp | |||
| @@ -3437,22 +3437,36 @@ bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, in | |||
| 3437 | wtxNew.vout.clear(); | 3437 | wtxNew.vout.clear(); |
| 3438 | if (nValue < 0) | 3438 | if (nValue < 0) |
| 3439 | return false; | 3439 | return false; |
| 3440 | int64 nValueOut = nValue; | 3440 | |
| 3441 | nValue += nFee; | 3441 | // nValue is the amount the payee asked for and must stay that. |
| 3442 | // | ||
| 3443 | // This used to read "nValueOut = nValue; nValue += nFee;", and | ||
| 3444 | // both lines are inside the retry loop. The first pass was | ||
| 3445 | // fine. But when the fee turned out to be too low the loop | ||
| 3446 | // starts over, and on the second pass nValueOut is read from | ||
| 3447 | // an nValue that already had the fee added -- so the payee is | ||
| 3448 | // sent the amount plus the fee, and it compounds on every | ||
| 3449 | // further retry, with the sender covering it. | ||
| 3450 | // | ||
| 3451 | // The retry fires whenever GetMinFee exceeds the fee we | ||
| 3452 | // guessed, which for this fee schedule means any transaction | ||
| 3453 | // over 10 KB: a wallet paying from many small inputs. Bitcoin | ||
| 3454 | // fixed this in 107d9e288. | ||
| 3455 | int64 nTotalValue = nValue + nFee; | ||
| 3442 | 3456 | ||
| 3443 | // Choose coins to use | 3457 | // Choose coins to use |
| 3444 | set<CWalletTx*> setCoins; | 3458 | set<CWalletTx*> setCoins; |
| 3445 | if (!SelectCoins(nValue, setCoins)) | 3459 | if (!SelectCoins(nTotalValue, setCoins)) |
| 3446 | return false; | 3460 | return false; |
| 3447 | int64 nValueIn = 0; | 3461 | int64 nValueIn = 0; |
| 3448 | foreach(CWalletTx* pcoin, setCoins) | 3462 | foreach(CWalletTx* pcoin, setCoins) |
| 3449 | nValueIn += pcoin->GetCredit(); | 3463 | nValueIn += pcoin->GetCredit(); |
| 3450 | 3464 | ||
| 3451 | // Fill vout[0] to the payee | 3465 | // Fill vout[0] to the payee |
| 3452 | wtxNew.vout.push_back(CTxOut(nValueOut, scriptPubKey)); | 3466 | wtxNew.vout.push_back(CTxOut(nValue, scriptPubKey)); |
| 3453 | 3467 | ||
| 3454 | // Fill vout[1] back to self with any change | 3468 | // Fill vout[1] back to self with any change |
| 3455 | if (nValueIn > nValue) | 3469 | if (nValueIn > nTotalValue) |
| 3456 | { | 3470 | { |
| 3457 | // Use the same key as one of the coins | 3471 | // Use the same key as one of the coins |
| 3458 | vector<unsigned char> vchPubKey; | 3472 | vector<unsigned char> vchPubKey; |
| @@ -3467,7 +3481,7 @@ bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, in | |||
| 3467 | // Fill vout[1] to ourself | 3481 | // Fill vout[1] to ourself |
| 3468 | CScript scriptPubKey; | 3482 | CScript scriptPubKey; |
| 3469 | scriptPubKey << vchPubKey << OP_CHECKSIG; | 3483 | scriptPubKey << vchPubKey << OP_CHECKSIG; |
| 3470 | wtxNew.vout.push_back(CTxOut(nValueIn - nValue, scriptPubKey)); | 3484 | wtxNew.vout.push_back(CTxOut(nValueIn - nTotalValue, scriptPubKey)); |
| 3471 | } | 3485 | } |
| 3472 | 3486 | ||
| 3473 | // Fill vin | 3487 | // Fill vin |