wallet: use outpoint when estimating input size
What changed, and why it matters
This Bitcoin Core wallet patch fixes a bug where the software underestimated the size (and therefore the required transaction fee) of certain externally provided transaction inputs. The fix passes the actual outpoint being spent into the size-estimation function, so the fee calculation can account for extra data that some inputs require. The included test demonstrates that without the fix, the estimated size is one byte too small.
Review whether underestimated fees could cause transactions to be delayed or rejected from mempools, and consider if any additional fee-bumping guidance is needed for users who created transactions with affected versions. No immediate emergency action is indicated.
Security signals we found
Fee underestimation bug in wallet transaction construction
Externally selected inputs (CCoinControl) affected
Maximum signed input size miscalculation
Test added demonstrating size difference
Evidence from the diff
CalculateMaximumSignedInputSize() in src/wallet/spend.cpp now passes the real COutPoint to MaxInputWeight() as part of a CTxIn, rather than an empty CTxIn{}. The outpoint affects the inferred descriptor’s maximum input weight when the input was selected externally via CCoinControl. Underestimating the input weight leads to underestimating the transaction’s virtual size and thus the fee needed for the chosen feerate. The new unit test confirms the corrected estimate is exactly one vbyte larger than the old one for a P2PKH input selected through coin_control.
Changed components
src/wallet/spend.cppwallet fee estimationexternally selected coin inputsCCoinControlInspect captured patch +18 / −1
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index 0a6d0056..9384299d 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -94,7 +94,7 @@ int CalculateMaximumSignedInputSize(const CTxOut& txout, const COutPoint outpoin
if (!provider) return -1;
if (const auto desc = InferDescriptor(txout.scriptPubKey, *provider)) {
- if (const auto weight = MaxInputWeight(*desc, {}, coin_control, true, can_grind_r)) {
+ if (const auto weight = MaxInputWeight(*desc, CTxIn{outpoint}, coin_control, true, can_grind_r)) {
return static_cast<int>(GetVirtualTransactionSize(*weight, 0, 0));
}
}
diff --git a/src/wallet/test/spend_tests.cpp b/src/wallet/test/spend_tests.cpp
index 866ffa48..218792ff 100644
--- a/src/wallet/test/spend_tests.cpp
+++ b/src/wallet/test/spend_tests.cpp
@@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <consensus/amount.h>
+#include <key.h>
#include <policy/fees/block_policy_estimator.h>
#include <script/solver.h>
#include <validation.h>
@@ -16,6 +17,22 @@
namespace wallet {
BOOST_FIXTURE_TEST_SUITE(spend_tests, WalletTestingSetup)
+BOOST_AUTO_TEST_CASE(max_signed_input_size_uses_external_outpoint)
+{
+ const CKey key{GenerateRandomKey()};
+ FillableSigningProvider provider;
+ BOOST_REQUIRE(provider.AddKey(key));
+
+ const CTxOut txout{COIN, GetScriptForDestination(PKHash{key.GetPubKey()})};
+ const COutPoint outpoint{Txid{}, 0};
+ CCoinControl coin_control;
+ coin_control.Select(outpoint).SetTxOut(txout);
+
+ const int low_r{CalculateMaximumSignedInputSize(txout, COutPoint{}, &provider, /*can_grind_r=*/true, &coin_control)};
+ const int high_r{CalculateMaximumSignedInputSize(txout, outpoint, &provider, /*can_grind_r=*/true, &coin_control)};
+ BOOST_CHECK_EQUAL(high_r, low_r + 1);
+}
+
BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup)
{
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
Why this scored 49/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.