Skip to content

Commit 92885c4

Browse files
committed
test: Test for ApproximateBestSubset edge case with too little fees
ApproximateBestSubset had an edge case (due to not using GetSelectionAmount) where it was possible for it to return success but fail to select enough to cover transaction fees. A test is added that could trigger this failure prior to the fix being implemented.
1 parent d926232 commit 92885c4

File tree

1 file changed

+57
-0
lines changed

1 file changed

+57
-0
lines changed

test/functional/rpc_fundrawtransaction.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ def run_test(self):
9999
self.test_subtract_fee_with_presets()
100100
self.test_transaction_too_large()
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."""
@@ -969,6 +970,62 @@ def test_include_unsafe(self):
969970
signedtx = wallet.signrawtransactionwithwallet(fundedtx['hex'])
970971
wallet.sendrawtransaction(signedtx['hex'])
971972

973+
def test_22670(self):
974+
# In issue #22670, it was observed that ApproximateBestSubset may
975+
# choose enough value to cover the target amount but not enough to cover the transaction fees.
976+
# This leads to a transaction whose actual transaction feerate is lower than expected.
977+
# However at normal feerates, the difference between the effective value and the real value
978+
# that this bug is not detected because the transaction fee must be at least 0.01 BTC (the minimum change value).
979+
# Otherwise the targeted minimum change value will be enough to cover the transaction fees that were not
980+
# being accounted for. So the minimum relay fee is set to 0.1 BTC/kvB in this test.
981+
self.log.info("Test issue 22670 ApproximateBestSubset bug")
982+
# Make sure the default wallet will not be loaded when restarted with a high minrelaytxfee
983+
self.nodes[0].unloadwallet(self.default_wallet_name, False)
984+
feerate = Decimal("0.1")
985+
self.restart_node(0, [f"-minrelaytxfee={feerate}", "-discardfee=0"]) # Set high minrelayfee, set discardfee to 0 for easier calculation
986+
987+
self.nodes[0].loadwallet(self.default_wallet_name, True)
988+
funds = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
989+
self.nodes[0].createwallet(wallet_name="tester")
990+
tester = self.nodes[0].get_wallet_rpc("tester")
991+
992+
# Because this test is specifically for ApproximateBestSubset, the target value must be greater
993+
# than any single input available, and require more than 1 input. So we make 3 outputs
994+
for i in range(0, 3):
995+
funds.sendtoaddress(tester.getnewaddress(address_type="bech32"), 1)
996+
self.nodes[0].generate(1)
997+
998+
# Create transactions in order to calculate fees for the target bounds that can trigger this bug
999+
change_tx = tester.fundrawtransaction(tester.createrawtransaction([], [{funds.getnewaddress(): 1.5}]))
1000+
tx = tester.createrawtransaction([], [{funds.getnewaddress(): 2}])
1001+
no_change_tx = tester.fundrawtransaction(tx, {"subtractFeeFromOutputs": [0]})
1002+
1003+
overhead_fees = feerate * len(tx) / 2 / 1000
1004+
cost_of_change = change_tx["fee"] - no_change_tx["fee"]
1005+
fees = no_change_tx["fee"]
1006+
assert_greater_than(fees, 0.01)
1007+
1008+
def do_fund_send(target):
1009+
create_tx = tester.createrawtransaction([], [{funds.getnewaddress(): lower_bound}])
1010+
funded_tx = tester.fundrawtransaction(create_tx)
1011+
signed_tx = tester.signrawtransactionwithwallet(funded_tx["hex"])
1012+
assert signed_tx["complete"]
1013+
decoded_tx = tester.decoderawtransaction(signed_tx["hex"])
1014+
assert_equal(len(decoded_tx["vin"]), 3)
1015+
assert tester.testmempoolaccept([signed_tx["hex"]])[0]["allowed"]
1016+
1017+
# We want to choose more value than is available in 2 inputs when considering the fee,
1018+
# but not enough to need 3 inputs when not considering the fee.
1019+
# So the target value must be at least 2.00000001 - fee.
1020+
lower_bound = Decimal("2.00000001") - fees
1021+
# The target value must be at most 2 - cost_of_change - not_input_fees - min_change (these are all
1022+
# included in the target before ApproximateBestSubset).
1023+
upper_bound = Decimal("2.0") - cost_of_change - overhead_fees - Decimal("0.01")
1024+
assert_greater_than_or_equal(upper_bound, lower_bound)
1025+
do_fund_send(lower_bound)
1026+
do_fund_send(upper_bound)
1027+
1028+
self.restart_node(0)
9721029

9731030
if __name__ == '__main__':
9741031
RawTransactionsTest().main()

0 commit comments

Comments
 (0)