aboutsummaryrefslogtreecommitdiff
path: root/test/functional/rpc_fundrawtransaction.py
diff options
context:
space:
mode:
authorAndrew Chow <achow101-github@achow101.com>2021-08-11 22:07:15 -0400
committerAndrew Chow <achow101-github@achow101.com>2021-08-13 00:34:47 -0400
commit92885c4f69a5e6fc4989677d6e5be8a666fbff0d (patch)
tree53d6ef2e91a0761df8fedf50261d851ee50f5d15 /test/functional/rpc_fundrawtransaction.py
parentd9262324e80da32725e21d4e0fbfee56f25490b1 (diff)
downloadbitcoin-92885c4f69a5e6fc4989677d6e5be8a666fbff0d.tar.xz
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.
Diffstat (limited to 'test/functional/rpc_fundrawtransaction.py')
-rwxr-xr-xtest/functional/rpc_fundrawtransaction.py57
1 files changed, 57 insertions, 0 deletions
diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py
index fa98c44152..2524eaa7a5 100755
--- a/test/functional/rpc_fundrawtransaction.py
+++ b/test/functional/rpc_fundrawtransaction.py
@@ -99,6 +99,7 @@ class RawTransactionsTest(BitcoinTestFramework):
self.test_subtract_fee_with_presets()
self.test_transaction_too_large()
self.test_include_unsafe()
+ self.test_22670()
def test_change_position(self):
"""Ensure setting changePosition in fundraw with an exact match is handled properly."""
@@ -969,6 +970,62 @@ class RawTransactionsTest(BitcoinTestFramework):
signedtx = wallet.signrawtransactionwithwallet(fundedtx['hex'])
wallet.sendrawtransaction(signedtx['hex'])
+ def test_22670(self):
+ # In issue #22670, it was observed that ApproximateBestSubset may
+ # choose enough value to cover the target amount but not enough to cover the transaction fees.
+ # This leads to a transaction whose actual transaction feerate is lower than expected.
+ # However at normal feerates, the difference between the effective value and the real value
+ # that this bug is not detected because the transaction fee must be at least 0.01 BTC (the minimum change value).
+ # Otherwise the targeted minimum change value will be enough to cover the transaction fees that were not
+ # being accounted for. So the minimum relay fee is set to 0.1 BTC/kvB in this test.
+ self.log.info("Test issue 22670 ApproximateBestSubset bug")
+ # Make sure the default wallet will not be loaded when restarted with a high minrelaytxfee
+ self.nodes[0].unloadwallet(self.default_wallet_name, False)
+ feerate = Decimal("0.1")
+ self.restart_node(0, [f"-minrelaytxfee={feerate}", "-discardfee=0"]) # Set high minrelayfee, set discardfee to 0 for easier calculation
+
+ self.nodes[0].loadwallet(self.default_wallet_name, True)
+ funds = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
+ self.nodes[0].createwallet(wallet_name="tester")
+ tester = self.nodes[0].get_wallet_rpc("tester")
+
+ # Because this test is specifically for ApproximateBestSubset, the target value must be greater
+ # than any single input available, and require more than 1 input. So we make 3 outputs
+ for i in range(0, 3):
+ funds.sendtoaddress(tester.getnewaddress(address_type="bech32"), 1)
+ self.nodes[0].generate(1)
+
+ # Create transactions in order to calculate fees for the target bounds that can trigger this bug
+ change_tx = tester.fundrawtransaction(tester.createrawtransaction([], [{funds.getnewaddress(): 1.5}]))
+ tx = tester.createrawtransaction([], [{funds.getnewaddress(): 2}])
+ no_change_tx = tester.fundrawtransaction(tx, {"subtractFeeFromOutputs": [0]})
+
+ overhead_fees = feerate * len(tx) / 2 / 1000
+ cost_of_change = change_tx["fee"] - no_change_tx["fee"]
+ fees = no_change_tx["fee"]
+ assert_greater_than(fees, 0.01)
+
+ def do_fund_send(target):
+ create_tx = tester.createrawtransaction([], [{funds.getnewaddress(): lower_bound}])
+ funded_tx = tester.fundrawtransaction(create_tx)
+ signed_tx = tester.signrawtransactionwithwallet(funded_tx["hex"])
+ assert signed_tx["complete"]
+ decoded_tx = tester.decoderawtransaction(signed_tx["hex"])
+ assert_equal(len(decoded_tx["vin"]), 3)
+ assert tester.testmempoolaccept([signed_tx["hex"]])[0]["allowed"]
+
+ # We want to choose more value than is available in 2 inputs when considering the fee,
+ # but not enough to need 3 inputs when not considering the fee.
+ # So the target value must be at least 2.00000001 - fee.
+ lower_bound = Decimal("2.00000001") - fees
+ # The target value must be at most 2 - cost_of_change - not_input_fees - min_change (these are all
+ # included in the target before ApproximateBestSubset).
+ upper_bound = Decimal("2.0") - cost_of_change - overhead_fees - Decimal("0.01")
+ assert_greater_than_or_equal(upper_bound, lower_bound)
+ do_fund_send(lower_bound)
+ do_fund_send(upper_bound)
+
+ self.restart_node(0)
if __name__ == '__main__':
RawTransactionsTest().main()