wallet: Throw an error in sendall if the tx size cannot be calculated
What changed, and why it matters
This commit changes the Bitcoin Core wallet's 'sendall' RPC command so that instead of relying on an internal assumption that every coin's size is known, it explicitly checks whether the final transaction size can be calculated and throws a clear error if it cannot. Previously, the code used a non-fatal assertion that could be skipped in release builds, potentially allowing the command to proceed with an invalid or unknown transaction size when the wallet contained descriptors it could not fully solve.
Treat as a hardening/defensive fix. Review whether other RPC spend paths similarly rely on CHECK_NONFATAL or unchecked CalculateMaximumSignedTxSize return values, and consider applying the same explicit vsize==-1 error handling consistently across the wallet RPC layer.
Security signals we found
Removal of CHECK_NONFatal assertion in transaction size handling
Addition of explicit error for unsolvable descriptors
Prevents potential use of -1 vsize in downstream fee calculation
Hardens fee estimation path in wallet RPC
Evidence from the diff
The patch removes a CHECK_NONFATAL(output.input_bytes > 0) loop guard in sendall() and adds an explicit check after CalculateMaximumSignedTxSize(): if tx_size.vsize == -1, it throws RPC_WALLET_ERROR with the message ‘Unable to determine the size of the transaction, the wallet contains unsolvable descriptors’. CalculateMaximumSignedTxSize returns vsize=-1 when signing size estimation fails, typically because a descriptor is watch-only or unsolvable. The change converts a defensive assertion into a user-facing error path, preventing sendall from silently using an invalid size for fee estimation.
Changed components
src/wallet/rpc/spend.cppsendall RPC commandwallet transaction size estimationfee calculation for sendallInspect captured patch +3 / −1
diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp
index c333cb54..d00547a8 100644
--- a/src/wallet/rpc/spend.cpp
+++ b/src/wallet/rpc/spend.cpp
@@ -1521,7 +1521,6 @@ RPCHelpMan sendall()
CoinFilterParams coins_params;
coins_params.min_amount = 0;
for (const COutput& output : AvailableCoins(*pwallet, &coin_control, fee_rate, coins_params).All()) {
- CHECK_NONFATAL(output.input_bytes > 0);
if (send_max && fee_rate.GetFee(output.input_bytes) > output.txout.nValue) {
continue;
}
@@ -1544,6 +1543,9 @@ RPCHelpMan sendall()
// estimate final size of tx
const TxSize tx_size{CalculateMaximumSignedTxSize(CTransaction(rawTx), pwallet.get())};
+ if (tx_size.vsize == -1) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Unable to determine the size of the transaction, the wallet contains unsolvable descriptors");
+ }
const CAmount fee_from_size{fee_rate.GetFee(tx_size.vsize)};
const std::optional<CAmount> total_bump_fees{pwallet->chain().calculateCombinedBumpFee(outpoints_spent, fee_rate)};
CAmount effective_value = total_input_value - fee_from_size - total_bump_fees.value_or(0);
Why this scored 32/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.