mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-25 10:43:19 -03:00
Merge bitcoin/bitcoin#26643: wallet: Move fee underpayment check to after all fee has been set
798430d127
wallet: Sanity check fee paid cannot be negative (Andrew Chow)c1a84f108e
wallet: Move fee underpayment check to after fee setting (Andrew Chow)e5daf976d5
wallet: Rename nFeeRet in CreateTransactionInternal to current_fee (Andrew Chow) Pull request description: Currently the fee underpayment check occurs right after we calculate what the transaction's fee should be. However the fee paid by the transaction at that time does not always match. Notably, when doing SFFO, the fee paid at that time will almost always be less than the fee required, which then required having a bypass of the underpayment check that results in SFFO payments going through when they should not. This PR moves the underpayment check to after fees have been finalized so that we always check whether the fee is being underpaid. This removes the exception for SFFO and unifies this behavior for both SFFO and non-SFFO txs. ACKs for top commit: S3RK: Code review ACK798430d127
furszy: Code review ACK798430d
glozow: utACK798430d127
, code looks correct to me Tree-SHA512: 720e8a3dbdc9937b12ee7881eb2ad58332c9584520da87ef3080e6f9d6220ce8d3bd8b9317b4877e56a229113437340852976db8f64df0d5cc50723fa04b02f0
This commit is contained in:
commit
8f3021155e
2 changed files with 34 additions and 14 deletions
|
@ -17,6 +17,7 @@
|
|||
#include <ios>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
@ -280,6 +281,12 @@ inline void SerializeTransaction(const TxType& tx, Stream& s) {
|
|||
s << tx.nLockTime;
|
||||
}
|
||||
|
||||
template<typename TxType>
|
||||
inline CAmount CalculateOutputValue(const TxType& tx)
|
||||
{
|
||||
return std::accumulate(tx.vout.cbegin(), tx.vout.cend(), CAmount{0}, [](CAmount sum, const auto& txout) { return sum + txout.nValue; });
|
||||
}
|
||||
|
||||
|
||||
/** The basic transaction that is broadcasted on the network and contained in
|
||||
* blocks. A transaction can contain multiple inputs and outputs.
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <interfaces/chain.h>
|
||||
#include <numeric>
|
||||
#include <policy/policy.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <script/signingprovider.h>
|
||||
#include <util/check.h>
|
||||
#include <util/fees.h>
|
||||
|
@ -778,7 +779,6 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
|
|||
AssertLockHeld(wallet.cs_wallet);
|
||||
|
||||
// out variables, to be packed into returned result structure
|
||||
CAmount nFeeRet;
|
||||
int nChangePosInOut = change_pos;
|
||||
|
||||
FastRandomContext rng_fast;
|
||||
|
@ -960,24 +960,28 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
|
|||
return util::Error{_("Missing solving data for estimating transaction size")};
|
||||
}
|
||||
CAmount fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes);
|
||||
nFeeRet = result->GetSelectedValue() - recipients_sum - change_amount;
|
||||
const CAmount output_value = CalculateOutputValue(txNew);
|
||||
Assume(recipients_sum + change_amount == output_value);
|
||||
CAmount current_fee = result->GetSelectedValue() - output_value;
|
||||
|
||||
// The only time that fee_needed should be less than the amount available for fees is when
|
||||
// we are subtracting the fee from the outputs. If this occurs at any other time, it is a bug.
|
||||
if (!coin_selection_params.m_subtract_fee_outputs && fee_needed > nFeeRet) {
|
||||
return util::Error{Untranslated(STR_INTERNAL_BUG("Fee needed > fee paid"))};
|
||||
// Sanity check that the fee cannot be negative as that means we have more output value than input value
|
||||
if (current_fee < 0) {
|
||||
return util::Error{Untranslated(STR_INTERNAL_BUG("Fee paid < 0"))};
|
||||
}
|
||||
|
||||
// If there is a change output and we overpay the fees then increase the change to match the fee needed
|
||||
if (nChangePosInOut != -1 && fee_needed < nFeeRet) {
|
||||
if (nChangePosInOut != -1 && fee_needed < current_fee) {
|
||||
auto& change = txNew.vout.at(nChangePosInOut);
|
||||
change.nValue += nFeeRet - fee_needed;
|
||||
nFeeRet = fee_needed;
|
||||
change.nValue += current_fee - fee_needed;
|
||||
current_fee = result->GetSelectedValue() - CalculateOutputValue(txNew);
|
||||
if (fee_needed != current_fee) {
|
||||
return util::Error{Untranslated(STR_INTERNAL_BUG("Change adjustment: Fee needed != fee paid"))};
|
||||
}
|
||||
}
|
||||
|
||||
// Reduce output values for subtractFeeFromAmount
|
||||
if (coin_selection_params.m_subtract_fee_outputs) {
|
||||
CAmount to_reduce = fee_needed - nFeeRet;
|
||||
CAmount to_reduce = fee_needed - current_fee;
|
||||
int i = 0;
|
||||
bool fFirst = true;
|
||||
for (const auto& recipient : vecSend)
|
||||
|
@ -1008,7 +1012,16 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
|
|||
}
|
||||
++i;
|
||||
}
|
||||
nFeeRet = fee_needed;
|
||||
current_fee = result->GetSelectedValue() - CalculateOutputValue(txNew);
|
||||
if (fee_needed != current_fee) {
|
||||
return util::Error{Untranslated(STR_INTERNAL_BUG("SFFO: Fee needed != fee paid"))};
|
||||
}
|
||||
}
|
||||
|
||||
// fee_needed should now always be less than or equal to the current fees that we pay.
|
||||
// If it is not, it is a bug.
|
||||
if (fee_needed > current_fee) {
|
||||
return util::Error{Untranslated(STR_INTERNAL_BUG("Fee needed > fee paid"))};
|
||||
}
|
||||
|
||||
// Give up if change keypool ran out and change is required
|
||||
|
@ -1030,7 +1043,7 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
|
|||
return util::Error{_("Transaction too large")};
|
||||
}
|
||||
|
||||
if (nFeeRet > wallet.m_default_max_tx_fee) {
|
||||
if (current_fee > wallet.m_default_max_tx_fee) {
|
||||
return util::Error{TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED)};
|
||||
}
|
||||
|
||||
|
@ -1046,14 +1059,14 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
|
|||
reservedest.KeepDestination();
|
||||
|
||||
wallet.WalletLogPrintf("Fee Calculation: Fee:%d Bytes:%u Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n",
|
||||
nFeeRet, nBytes, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay,
|
||||
current_fee, nBytes, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay,
|
||||
feeCalc.est.pass.start, feeCalc.est.pass.end,
|
||||
(feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool) > 0.0 ? 100 * feeCalc.est.pass.withinTarget / (feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool) : 0.0,
|
||||
feeCalc.est.pass.withinTarget, feeCalc.est.pass.totalConfirmed, feeCalc.est.pass.inMempool, feeCalc.est.pass.leftMempool,
|
||||
feeCalc.est.fail.start, feeCalc.est.fail.end,
|
||||
(feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) > 0.0 ? 100 * feeCalc.est.fail.withinTarget / (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) : 0.0,
|
||||
feeCalc.est.fail.withinTarget, feeCalc.est.fail.totalConfirmed, feeCalc.est.fail.inMempool, feeCalc.est.fail.leftMempool);
|
||||
return CreatedTransactionResult(tx, nFeeRet, nChangePosInOut, feeCalc);
|
||||
return CreatedTransactionResult(tx, current_fee, nChangePosInOut, feeCalc);
|
||||
}
|
||||
|
||||
util::Result<CreatedTransactionResult> CreateTransaction(
|
||||
|
|
Loading…
Add table
Reference in a new issue