Repository: bitcoin/bitcoin
File: test/functional/wallet_fundrawtransaction.py (Python)

Code before:
        self.nodes[0].createwallet("unsafe")
        wallet = self.nodes[0].get_wallet_rpc("unsafe")

        # We receive unconfirmed funds from external keys (unsafe outputs).
        addr = wallet.getnewaddress()
        inputs = []
        for i in range(0, 2):
            utxo = self.create_outpoints(self.nodes[2], outputs=[{addr: 5}])[0]
            inputs.append((utxo['txid'], utxo['vout']))
        self.sync_mempools()

        # Unsafe inputs are ignored by default.
        rawtx = wallet.createrawtransaction([], [{self.nodes[2].getnewaddress(): 7.5}])
        assert_raises_rpc_error(-4, "Insufficient funds", wallet.fundrawtransaction, rawtx)

        # But we can opt-in to use them for funding.
        fundedtx = wallet.fundrawtransaction(rawtx, include_unsafe=True)
        tx_dec = wallet.decoderawtransaction(fundedtx['hex'])
        assert all((txin["txid"], txin["vout"]) in inputs for txin in tx_dec["vin"])
        signedtx = wallet.signrawtransactionwithwallet(fundedtx['hex'])
        assert wallet.testmempoolaccept([signedtx['hex']])[0]["allowed"]

        # And we can also use them once they're confirmed.
        self.generate(self.nodes[0], 1)
        fundedtx = wallet.fundrawtransaction(rawtx, include_unsafe=False)
        tx_dec = wallet.decoderawtransaction(fundedtx['hex'])
        assert all((txin["txid"], txin["vout"]) in inputs for txin in tx_dec["vin"])
        signedtx = wallet.signrawtransactionwithwallet(fundedtx['hex'])
        assert wallet.testmempoolaccept([signedtx['hex']])[0]["allowed"]
        self.nodes[0].unloadwallet("unsafe")

    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

Review comment (bug):
Tested that this would fail if the `return util::Error...` line was commented out in source/wallet/spend.cpp.  It did (as expected).

Code after:

        self.nodes[2].createwallet("test_weight_limits")
        wallet = self.nodes[2].get_wallet_rpc("test_weight_limits")

        outputs = []
        for _ in range(1472):
            outputs.append({wallet.getnewaddress(address_type="legacy"): 0.1})
        txid = self.nodes[0].send(outputs=outputs)["txid"]
        self.generate(self.nodes[0], 1)

        # 272 WU per input (273 when high-s); picking 1471 inputs will exceed the max standard tx weight.
        rawtx = wallet.createrawtransaction([], [{wallet.getnewaddress(): 0.1 * 1471}])

        # 1) Try to fund transaction only using the preset inputs
        input_weights = []
        for i in range(1471):
            input_weights.append({"txid": txid, "vout": i, "weight": 273})
        assert_raises_rpc_error(-4, "Transaction too large", wallet.fundrawtransaction, hexstring=rawtx, input_weights=input_weights)

        # 2) Let the wallet fund the transaction
        assert_raises_rpc_error(-4, "The inputs size exceeds the maximum weight. Please try sending a smaller amount or manually consolidating your wallet's UTXOs",
                                wallet.fundrawtransaction, hexstring=rawtx)

        # 3) Pre-select some inputs and let the wallet fill-up the remaining amount
        inputs = input_weights[0:1000]
        assert_raises_rpc_error(-4, "The combination of the pre-selected inputs and the wallet automatic inputs selection exceeds the transaction maximum weight. Please try sending a smaller amount or manually consolidating your wallet's UTXOs",
                                wallet.fundrawtransaction, hexstring=rawtx, input_weights=inputs)

        self.nodes[2].unloadwallet("test_weight_limits")

    def test_include_unsafe(self):
        self.log.info("Test fundrawtxn with unsafe inputs")

        self.nodes[0].createwallet("unsafe")
        wallet = self.nodes[0].get_wallet_rpc("unsafe")

        # We receive unconfirmed funds from external keys (unsafe outputs).
        addr = wallet.getnewaddress()
        inputs = []
        for i in range(0, 2):
            utxo = self.create_outpoints(self.nodes[2], outputs=[{addr: 5}])[0]
            inputs.append((utxo['txid'], utxo['vout']))
        self.sync_mempools()

        # Unsafe inputs are ignored by default.
        rawtx = wallet.createrawtransaction([], [{self.nodes[2].getnewaddress(): 7.5}])
        assert_raises_rpc_error(-4, "Insufficient funds", wallet.fundrawtransaction, rawtx)

        # But we can opt-in to use them for funding.
        fundedtx = wallet.fundrawtransaction(rawtx, include_unsafe=True)
        tx_dec = wallet.decoderawtransaction(fundedtx['hex'])