@@ -99,6 +99,7 @@ def run_test(self):
99
99
self .test_subtract_fee_with_presets ()
100
100
self .test_transaction_too_large ()
101
101
self .test_include_unsafe ()
102
+ self .test_22670 ()
102
103
103
104
def test_change_position (self ):
104
105
"""Ensure setting changePosition in fundraw with an exact match is handled properly."""
@@ -969,6 +970,62 @@ def test_include_unsafe(self):
969
970
signedtx = wallet .signrawtransactionwithwallet (fundedtx ['hex' ])
970
971
wallet .sendrawtransaction (signedtx ['hex' ])
971
972
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 )
972
1029
973
1030
if __name__ == '__main__' :
974
1031
RawTransactionsTest ().main ()
0 commit comments