Skip to content

Commit 2f3ceba

Browse files
committed
merge bitcoin#22686: Use GetSelectionAmount in ApproximateBestSubset
We aren't adding the `assert` check as it's superceded by a "Fee needed > fee paid" error that was already backported from c1a84f1 (bitcoin#26643).
1 parent f94993c commit 2f3ceba

File tree

2 files changed

+57
-2
lines changed

2 files changed

+57
-2
lines changed

src/wallet/coinselection.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ static void ApproximateBestSubset(const std::vector<OutputGroup>& groups, const
199199
//the selection random.
200200
if (nPass == 0 ? insecure_rand.randbool() : !vfIncluded[i])
201201
{
202-
nTotal += groups[i].m_value;
202+
nTotal += groups[i].GetSelectionAmount();
203203
++nTotalInputCount;
204204
vfIncluded[i] = true;
205205
if (nTotal >= nTargetValue)
@@ -211,7 +211,7 @@ static void ApproximateBestSubset(const std::vector<OutputGroup>& groups, const
211211
nBestInputCount = nTotalInputCount;
212212
vfBest = vfIncluded;
213213
}
214-
nTotal -= groups[i].m_value;
214+
nTotal -= groups[i].GetSelectionAmount();
215215
--nTotalInputCount;
216216
vfIncluded[i] = false;
217217
}

test/functional/rpc_fundrawtransaction.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ def run_test(self):
9999
self.test_option_subtract_fee_from_outputs()
100100
self.test_subtract_fee_with_presets()
101101
self.test_include_unsafe()
102+
self.test_22670()
102103

103104
def test_change_position(self):
104105
"""Ensure setting changePosition in fundraw with an exact match is handled properly."""
@@ -932,6 +933,60 @@ def test_include_unsafe(self):
932933
signedtx = wallet.signrawtransactionwithwallet(fundedtx['hex'])
933934
wallet.sendrawtransaction(signedtx['hex'])
934935

936+
def test_22670(self):
937+
# In issue #22670, it was observed that ApproximateBestSubset may
938+
# choose enough value to cover the target amount but not enough to cover the transaction fees.
939+
# This leads to a transaction whose actual transaction feerate is lower than expected.
940+
# However at normal feerates, the difference between the effective value and the real value
941+
# that this bug is not detected because the transaction fee must be at least 0.01 BTC (the minimum change value).
942+
# Otherwise the targeted minimum change value will be enough to cover the transaction fees that were not
943+
# being accounted for. So the minimum relay fee is set to 0.1 BTC/kvB in this test.
944+
self.log.info("Test issue 22670 ApproximateBestSubset bug")
945+
# Make sure the default wallet will not be loaded when restarted with a high minrelaytxfee
946+
self.nodes[0].unloadwallet(self.default_wallet_name, False)
947+
feerate = Decimal("0.1")
948+
self.restart_node(0, [f"-minrelaytxfee={feerate}", "-discardfee=0"]) # Set high minrelayfee, set discardfee to 0 for easier calculation
949+
950+
self.nodes[0].loadwallet(self.default_wallet_name, True)
951+
funds = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
952+
self.nodes[0].createwallet(wallet_name="tester")
953+
tester = self.nodes[0].get_wallet_rpc("tester")
954+
955+
# Because this test is specifically for ApproximateBestSubset, the target value must be greater
956+
# than any single input available, and require more than 1 input. So we make 3 outputs
957+
for i in range(0, 3):
958+
funds.sendtoaddress(tester.getnewaddress(), 1)
959+
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
960+
961+
# Create transactions in order to calculate fees for the target bounds that can trigger this bug
962+
change_tx = tester.fundrawtransaction(tester.createrawtransaction([], [{funds.getnewaddress(): 1.5}]))
963+
tx = tester.createrawtransaction([], [{funds.getnewaddress(): 2}])
964+
no_change_tx = tester.fundrawtransaction(tx, {"subtractFeeFromOutputs": [0]})
965+
966+
overhead_fees = feerate * len(tx) / 2 / 1000
967+
cost_of_change = change_tx["fee"] - no_change_tx["fee"]
968+
fees = no_change_tx["fee"]
969+
assert_greater_than(fees, 0.01)
970+
971+
def do_fund_send(target):
972+
create_tx = tester.createrawtransaction([], [{funds.getnewaddress(): lower_bound}])
973+
funded_tx = tester.fundrawtransaction(create_tx)
974+
signed_tx = tester.signrawtransactionwithwallet(funded_tx["hex"])
975+
assert signed_tx["complete"]
976+
decoded_tx = tester.decoderawtransaction(signed_tx["hex"])
977+
assert_equal(len(decoded_tx["vin"]), 3)
978+
assert tester.testmempoolaccept([signed_tx["hex"]])[0]["allowed"]
979+
980+
# We want to choose more value than is available in 2 inputs when considering the fee,
981+
# but not enough to need 3 inputs when not considering the fee.
982+
# So the target value must be at least 2.00000001 - fee.
983+
lower_bound = Decimal("2.00000001") - fees
984+
# The target value must be at most 2 - cost_of_change - not_input_fees - min_change (these are all
985+
# included in the target before ApproximateBestSubset).
986+
upper_bound = Decimal("2.0") - cost_of_change - overhead_fees - Decimal("0.01")
987+
assert_greater_than_or_equal(upper_bound, lower_bound)
988+
do_fund_send(lower_bound)
989+
do_fund_send(upper_bound)
935990

936991
if __name__ == '__main__':
937992
RawTransactionsTest().main()

0 commit comments

Comments
 (0)