Skip to content

Commit b0c8306

Browse files
committed
Merge bitcoin/bitcoin#24649: wallet: do not count wallet utxos as external
7832e94 test: fundrawtransaction preset input weight calculation (S3RK) c3981e3 wallet: do not count wallet utxos as external (S3RK) Pull request description: Correctly differentiating between external vs non-external utxos in coin control produces more accurate weight and fee estimations. Weight for external utxos is estimated based on the maximum signature size, while for the wallet utxos we expect minimal signature due to signature grinding. ACKs for top commit: achow101: re-ACK 7832e94 Xekyo: re-ACK 7832e94 furszy: ACK 7832e94 Tree-SHA512: bb5635b0bd85fa9a76922a53ad3fa062286424c06a695a0e87407c665713e80a33555b644fbb13bcc1ab503dcd7f53aacbdc368d69ac0ecff8005603623ac94f
2 parents 0ea92ca + 7832e94 commit b0c8306

File tree

3 files changed

+43
-17
lines changed

3 files changed

+43
-17
lines changed

src/wallet/rpc/spend.cpp

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -699,19 +699,6 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out,
699699
setSubtractFeeFromOutputs.insert(pos);
700700
}
701701

702-
// Fetch specified UTXOs from the UTXO set to get the scriptPubKeys and values of the outputs being selected
703-
// and to match with the given solving_data. Only used for non-wallet outputs.
704-
std::map<COutPoint, Coin> coins;
705-
for (const CTxIn& txin : tx.vin) {
706-
coins[txin.prevout]; // Create empty map entry keyed by prevout.
707-
}
708-
wallet.chain().findCoins(coins);
709-
for (const auto& coin : coins) {
710-
if (!coin.second.out.IsNull()) {
711-
coinControl.SelectExternal(coin.first, coin.second.out);
712-
}
713-
}
714-
715702
bilingual_str error;
716703

717704
if (!FundTransaction(wallet, tx, fee_out, change_position, error, lockUnspents, setSubtractFeeFromOutputs, coinControl)) {

src/wallet/spend.cpp

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,14 +1023,28 @@ bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet,
10231023

10241024
coinControl.fAllowOtherInputs = true;
10251025

1026-
for (const CTxIn& txin : tx.vin) {
1027-
coinControl.Select(txin.prevout);
1028-
}
1029-
10301026
// Acquire the locks to prevent races to the new locked unspents between the
10311027
// CreateTransaction call and LockCoin calls (when lockUnspents is true).
10321028
LOCK(wallet.cs_wallet);
10331029

1030+
// Fetch specified UTXOs from the UTXO set to get the scriptPubKeys and values of the outputs being selected
1031+
// and to match with the given solving_data. Only used for non-wallet outputs.
1032+
std::map<COutPoint, Coin> coins;
1033+
for (const CTxIn& txin : tx.vin) {
1034+
coins[txin.prevout]; // Create empty map entry keyed by prevout.
1035+
}
1036+
wallet.chain().findCoins(coins);
1037+
1038+
for (const CTxIn& txin : tx.vin) {
1039+
// if it's not in the wallet and corresponding UTXO is found than select as external output
1040+
const auto& outPoint = txin.prevout;
1041+
if (wallet.mapWallet.find(outPoint.hash) == wallet.mapWallet.end() && !coins[outPoint].out.IsNull()) {
1042+
coinControl.SelectExternal(outPoint, coins[outPoint].out);
1043+
} else {
1044+
coinControl.Select(outPoint);
1045+
}
1046+
}
1047+
10341048
FeeCalculation fee_calc_out;
10351049
std::optional<CreatedTransactionResult> txr = CreateTransaction(wallet, vecSend, nChangePosInOut, error, coinControl, fee_calc_out, false);
10361050
if (!txr) return false;

test/functional/rpc_fundrawtransaction.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111

1212
from test_framework.descriptors import descsum_create
1313
from test_framework.key import ECKey
14+
from test_framework.messages import (
15+
COIN,
16+
)
1417
from test_framework.test_framework import BitcoinTestFramework
1518
from test_framework.util import (
1619
assert_approx,
@@ -103,6 +106,7 @@ def run_test(self):
103106
self.generate(self.nodes[2], 1)
104107
self.generate(self.nodes[0], 121)
105108

109+
self.test_weight_calculation()
106110
self.test_change_position()
107111
self.test_simple()
108112
self.test_simple_two_coins()
@@ -1069,6 +1073,27 @@ def test_external_inputs(self):
10691073

10701074
self.nodes[2].unloadwallet("extfund")
10711075

1076+
def test_weight_calculation(self):
1077+
self.log.info("Test weight calculation with external inputs")
1078+
1079+
self.nodes[2].createwallet("test_weight_calculation")
1080+
wallet = self.nodes[2].get_wallet_rpc("test_weight_calculation")
1081+
1082+
addr = wallet.getnewaddress()
1083+
txid = self.nodes[0].sendtoaddress(addr, 5)
1084+
vout = find_vout_for_address(self.nodes[0], txid, addr)
1085+
1086+
self.nodes[0].sendtoaddress(wallet.getnewaddress(), 5)
1087+
self.generate(self.nodes[0], 1)
1088+
1089+
rawtx = wallet.createrawtransaction([{'txid': txid, 'vout': vout}], [{self.nodes[0].getnewaddress(): 9.999}])
1090+
fundedtx = wallet.fundrawtransaction(rawtx, {'fee_rate': 10})
1091+
# with 71-byte signatures we should expect following tx size
1092+
tx_size = 10 + 41*2 + 31*2 + (2 + 107*2)/4
1093+
assert_equal(fundedtx['fee'] * COIN, tx_size * 10)
1094+
1095+
self.nodes[2].unloadwallet("test_weight_calculation")
1096+
10721097
def test_include_unsafe(self):
10731098
self.log.info("Test fundrawtxn with unsafe inputs")
10741099

0 commit comments

Comments
 (0)