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

Code before:
        # Send to legacy address type so that we will have an ecdsa signature with a measurable effect on the feerate
        default_wallet.sendtoaddress(wallet.getnewaddress(address_type="legacy"), 10)
        self.generate(self.nodes[0], 1)

        assert_equal(wallet.listunspent(), watchonly.listunspent())

        ret_addr = default_wallet.getnewaddress()
        tx = wallet.createrawtransaction([], [{ret_addr: 5}])
        funded = wallet.fundrawtransaction(hexstring=tx, fee_rate=10)

        watchonly_funded = watchonly.fundrawtransaction(hexstring=tx, fee_rate=10)
        assert_greater_than(watchonly_funded["fee"], funded["fee"])

    def test_cannot_cover_fees(self):
        self.log.info("Test error message when transaction amount exceeds available balance when fees are included")
        default_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)

        self.nodes[1].createwallet("cannot_cover_fees")
        wallet = self.nodes[1].get_wallet_rpc("cannot_cover_fees")

        # Set up wallet with 2 utxos: 0.3 BTC and 0.15 BTC
        default_wallet.sendtoaddress(wallet.getnewaddress(), 0.3)
        txid2 = default_wallet.sendtoaddress(wallet.getnewaddress(), 0.15)
        self.generate(self.nodes[0], 1)
        utxos = wallet.listunspent(1)
        vout2 = next((u["vout"] for u in utxos if u["txid"] == txid2), 0)

        amount_with_fee_err_msg = "The total exceeds your balance when the {} transaction fee is included."

        self.log.info("Test without preselected inputs")
        self.log.info("Attempt to send 0.45 BTC without SFFO")
        rawtx = wallet.createrawtransaction(inputs=[], outputs=[{default_wallet.getnewaddress(): 0.45}])
        assert_raises_rpc_error(-4, amount_with_fee_err_msg.format("0.00000042"), wallet.fundrawtransaction, rawtx, options={"fee_rate":1})

        self.log.info("Send 0.45 BTC with SFFO")
        wallet.fundrawtransaction(rawtx, options={"subtractFeeFromOutputs":[0]})

        self.log.info("Attempt to send 0.45 BTC by restricting coin selection with minconf=6")
        assert_raises_rpc_error(-4, "Insufficient funds", wallet.fundrawtransaction, rawtx, options={"minconf":6})

        self.log.info("Test with preselected inputs")
        self.log.info("Attempt to send 0.45 BTC preselecting 0.15 BTC utxo")
        rawtx = wallet.createrawtransaction(inputs=[{"txid": txid2, "vout": vout2}], outputs=[{default_wallet.getnewaddress(): 0.45}])
        assert_raises_rpc_error(-4, amount_with_fee_err_msg.format("0.00000042"), wallet.fundrawtransaction, rawtx, options={"fee_rate":1})

        self.log.info("Send 0.45 BTC preselecting 0.15 BTC utxo with SFFO")
        wallet.fundrawtransaction(hexstring=rawtx, options={"subtractFeeFromOutputs":[0]})


if __name__ == '__main__':
    RawTransactionsTest(__file__).main()

Review comment (suggestion):
In f66d5bc0990cc4f82c40aea3e13232fb6b883d31 "wallet: introduce "tx amount exceeds balance when fees are included" error"

Providing the default for `next()` shouldn't be necessary.

This could probably be replaced by using `utxo = wallet.listunspent()[0]` instead of looking for the 0.15's info. It doesn't seem like that amount specifically is really relevant to the test, just that there is something being preselected.

Code after:
        # Send to legacy address type so that we will have an ecdsa signature with a measurable effect on the feerate
        default_wallet.sendtoaddress(wallet.getnewaddress(address_type="legacy"), 10)
        self.generate(self.nodes[0], 1)

        assert_equal(wallet.listunspent(), watchonly.listunspent())

        ret_addr = default_wallet.getnewaddress()
        tx = wallet.createrawtransaction([], [{ret_addr: 5}])
        funded = wallet.fundrawtransaction(hexstring=tx, fee_rate=10)

        watchonly_funded = watchonly.fundrawtransaction(hexstring=tx, fee_rate=10)
        assert_greater_than(watchonly_funded["fee"], funded["fee"])

    def test_cannot_cover_fees(self):
        self.log.info("Test error message when transaction amount exceeds available balance when fees are included")
        default_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)

        self.nodes[1].createwallet("cannot_cover_fees")
        wallet = self.nodes[1].get_wallet_rpc("cannot_cover_fees")

        # Set up wallet with 2 utxos: 0.3 BTC and 0.15 BTC
        default_wallet.sendtoaddress(wallet.getnewaddress(), 0.3)
        txid2 = default_wallet.sendtoaddress(wallet.getnewaddress(), 0.15)
        self.generate(self.nodes[0], 1)
        vout2 = next(utxo["vout"] for utxo in wallet.listunspent() if utxo["txid"] == txid2)
        amount_with_fee_err_msg = "The total exceeds your balance when the {} transaction fee is included."

        self.log.info("Test without preselected inputs")
        self.log.info("Attempt to send 0.45 BTC without SFFO")
        rawtx = wallet.createrawtransaction(inputs=[], outputs=[{default_wallet.getnewaddress(): 0.45}])
        assert_raises_rpc_error(-4, amount_with_fee_err_msg.format("0.00000042"), wallet.fundrawtransaction, rawtx, options={"fee_rate":1})

        self.log.info("Send 0.45 BTC with SFFO")
        wallet.fundrawtransaction(rawtx, options={"subtractFeeFromOutputs":[0]})

        self.log.info("Attempt to send 0.45 BTC by restricting coin selection with minconf=6")
        assert_raises_rpc_error(-4, "Insufficient funds", wallet.fundrawtransaction, rawtx, options={"minconf":6})

        self.log.info("Test with preselected inputs")
        self.log.info("Attempt to send 0.45 BTC preselecting 0.15 BTC utxo")
        rawtx = wallet.createrawtransaction(inputs=[{"txid": txid2, "vout": vout2}], outputs=[{default_wallet.getnewaddress(): 0.45}])
        assert_raises_rpc_error(-4, amount_with_fee_err_msg.format("0.00000042"), wallet.fundrawtransaction, rawtx, options={"fee_rate":1})

        self.log.info("Send 0.45 BTC preselecting 0.15 BTC utxo with SFFO")
        wallet.fundrawtransaction(hexstring=rawtx, options={"subtractFeeFromOutputs":[0]})

        self.log.info("Attempt to send 0.15 BTC using only the 0.15 BTC preselected utxo")
        rawtx = wallet.createrawtransaction(inputs=[{"txid": txid2, "vout": vout2}], outputs=[{default_wallet.getnewaddress(): 0.15}])
        assert_raises_rpc_error(-4, ERR_NOT_ENOUGH_PRESET_INPUTS, wallet.fundrawtransaction, rawtx, options={"fee_rate":1, "add_inputs":False})
        self.log.info("Send 0.15 BTC using only the 0.15 BTC preselected utxo with SFFO")
        wallet.fundrawtransaction(hexstring=rawtx, options={"subtractFeeFromOutputs":[0], "add_inputs":False})