URI: 
       twallet: don't put partial tx as UTXO into psbt - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit e25602ab3be4c43f883d427cdad1d3071b010437
   DIR parent 785fe6aeeacef8b88cb2b8adb19df6d1612b9a1a
  HTML Author: SomberNight <somber.night@protonmail.com>
       Date:   Thu,  4 Mar 2021 13:20:49 +0100
       
       wallet: don't put partial tx as UTXO into psbt
       
       if there is a chain of unsigned txs, we cannot populate NON_WITNESS_UTXO
       
       closes #7080
       closes #7009
       closes #6482
       
       Diffstat:
         M electrum/tests/test_wallet_vertica… |      45 ++++++++++++++++++++++++++++++-
         M electrum/transaction.py             |      12 ++++++++++--
         M electrum/wallet.py                  |      15 +++++++++++++--
       
       3 files changed, 67 insertions(+), 5 deletions(-)
       ---
   DIR diff --git a/electrum/tests/test_wallet_vertical.py b/electrum/tests/test_wallet_vertical.py
       t@@ -13,7 +13,8 @@ from electrum.address_synchronizer import TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCON
        from electrum.wallet import (sweep, Multisig_Wallet, Standard_Wallet, Imported_Wallet,
                                     restore_wallet_from_text, Abstract_Wallet, BumpFeeStrategy)
        from electrum.util import bfh, bh2u, create_and_start_event_loop
       -from electrum.transaction import TxOutput, Transaction, PartialTransaction, PartialTxOutput, PartialTxInput, tx_from_any
       +from electrum.transaction import (TxOutput, Transaction, PartialTransaction, PartialTxOutput,
       +                                  PartialTxInput, tx_from_any, TxOutpoint)
        from electrum.mnemonic import seed_type
        
        from electrum.plugins.trustedcoin import trustedcoin
       t@@ -2124,6 +2125,48 @@ class TestWalletSending(TestCaseForTestnet):
                                 str(tx_copy))
                self.assertEqual('3021a4fe24e33af9d0ccdf25c478387c97df671fe1fd8b4db0de4255b3a348c5', tx_copy.txid())
        
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_wallet_history_chain_of_unsigned_transactions(self, mock_save_db):
       +        wallet = self.create_standard_wallet_from_seed('cross end slow expose giraffe fuel track awake turtle capital ranch pulp',
       +                                                       config=self.config, gap_limit=3)
       +
       +        # bootstrap wallet
       +        funding_tx = Transaction('0200000000010132515e6aade1b79ec7dd3bac0896d8b32c56195d23d07d48e21659cef24301560100000000fdffffff0112841e000000000016001477fe6d2a27e8860c278d4d2cd90bad716bb9521a02473044022041ed68ef7ef122813ac6a5e996b8284f645c53fbe6823b8e430604a8915a867802203233f5f4d347a687eb19b2aa570829ab12aeeb29a24cc6d6d20b8b3d79e971ae012102bee0ee043817e50ac1bb31132770f7c41e35946ccdcb771750fb9696bdd1b307ad951d00')
       +        funding_txid = funding_tx.txid()
       +        self.assertEqual('db949963c3787c90a40fb689ffdc3146c27a9874a970d1fd20921afbe79a7aa9', funding_txid)
       +        wallet.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
       +
       +        # create tx1
       +        outputs = [PartialTxOutput.from_address_and_value('tb1qsfcddwf7yytl62e3catwv8hpl2hs9e36g2cqxl', 100000)]
       +        coins = wallet.get_spendable_coins(domain=None)
       +        tx = wallet.make_unsigned_transaction(coins=coins, outputs=outputs, fee=190)
       +        tx.set_rbf(True)
       +        tx.locktime = 1938861
       +        tx.version = 2
       +        self.assertEqual("70736274ff0100710200000001a97a9ae7fb1a9220fdd170a974987ac24631dcff89b60fa4907c78c3639994db0000000000fdffffff02a0860100000000001600148270d6b93e2117fd2b31c756e61ee1faaf02e63ab4fc1c0000000000160014b8e4fdc91593b67de2bf214694ef47e38dc2ee8ead951d00000100bf0200000000010132515e6aade1b79ec7dd3bac0896d8b32c56195d23d07d48e21659cef24301560100000000fdffffff0112841e000000000016001477fe6d2a27e8860c278d4d2cd90bad716bb9521a02473044022041ed68ef7ef122813ac6a5e996b8284f645c53fbe6823b8e430604a8915a867802203233f5f4d347a687eb19b2aa570829ab12aeeb29a24cc6d6d20b8b3d79e971ae012102bee0ee043817e50ac1bb31132770f7c41e35946ccdcb771750fb9696bdd1b307ad951d002206026cc6a74c2b0e38661d341ffae48fe7dde5196ca4afe95d28b496673fa4cf6467105f83afb40000008000000000000000000022020312ea49b9b1eea28e3330316a5b7e6673b43e01da38f802c99a777d30b903fa5e105f83afb40000008000000000010000000022020349321bee98c012887997f26c6400018b0711dd254b702c038b96a30ebe2af1d2105f83afb400000080010000000000000000",
       +                         tx.serialize_as_bytes().hex())
       +        self.assertFalse(tx.is_complete())
       +        self.assertTrue(tx.is_segwit())
       +        wallet.add_transaction(tx)
       +
       +        # create tx2, which spends from unsigned tx1
       +        outputs = [PartialTxOutput.from_address_and_value('tb1qq0lm9esmq6pfjc3jls7v6twy93lnqcs85wlth3', '!')]
       +        coins = wallet.get_spendable_coins(domain=None)
       +        tx = wallet.make_unsigned_transaction(coins=coins, outputs=outputs, fee=5000)
       +        tx.set_rbf(True)
       +        tx.locktime = 1938863
       +        tx.version = 2
       +        self.assertEqual("70736274ff01007b020000000288234495e0ff1d8ac06038f6cc5d5a92738d719f4c15afd581366da94754478f0000000000fdffffff88234495e0ff1d8ac06038f6cc5d5a92738d719f4c15afd581366da94754478f0100000000fdffffff01cc6f1e000000000016001403ffb2e61b0682996232fc3ccd2dc42c7f306207af951d000001011fa0860100000000001600148270d6b93e2117fd2b31c756e61ee1faaf02e63a22060312ea49b9b1eea28e3330316a5b7e6673b43e01da38f802c99a777d30b903fa5e105f83afb40000008000000000010000000001011fb4fc1c0000000000160014b8e4fdc91593b67de2bf214694ef47e38dc2ee8e22060349321bee98c012887997f26c6400018b0711dd254b702c038b96a30ebe2af1d2105f83afb4000000800100000000000000002202036f9a5913f1c22742dbc9e7f3ac3064be8b125a23563fcc8a519f387e16c7244c105f83afb400000080000000000200000000",
       +                         tx.serialize_as_bytes().hex())
       +        self.assertFalse(tx.is_complete())
       +        self.assertTrue(tx.is_segwit())
       +        wallet.add_transaction(tx)
       +
       +        coins = wallet.get_spendable_coins(domain=None)
       +        self.assertEqual(1, len(coins))
       +        self.assertEqual("bf08206effded4126a95fbed375cedc0452b5e16a5d2025ac645dfae81addbe4:0",
       +                         coins[0].prevout.to_str())
       +
        
        class TestWalletOfflineSigning(TestCaseForTestnet):
        
   DIR diff --git a/electrum/transaction.py b/electrum/transaction.py
       t@@ -1168,8 +1168,16 @@ class PartialTxInput(TxInput, PSBTSection):
                return self._utxo
        
            @utxo.setter
       -    def utxo(self, value: Optional[Transaction]):
       -        self._utxo = value
       +    def utxo(self, tx: Optional[Transaction]):
       +        if tx is None:
       +            return
       +        # note that tx might be a PartialTransaction
       +        # serialize and de-serialize tx now. this might e.g. convert a complete PartialTx to a Tx
       +        tx = tx_from_any(str(tx))
       +        # 'utxo' field in PSBT cannot be another PSBT:
       +        if not tx.is_complete():
       +            return
       +        self._utxo = tx
                self.validate_data()
                self.ensure_there_is_only_one_utxo()
        
   DIR diff --git a/electrum/wallet.py b/electrum/wallet.py
       t@@ -1781,8 +1781,19 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
                    self,
                    txin: PartialTxInput,
                    *,
       +            address: str = None,
                    ignore_network_issues: bool = True,
            ) -> None:
       +        # We prefer to include UTXO (full tx) for every input.
       +        # We cannot include UTXO if the prev tx is not signed yet though (chain of unsigned txs),
       +        # in which case we might include a WITNESS_UTXO.
       +        address = address or txin.address
       +        if txin.witness_utxo is None and txin.is_segwit() and address:
       +            received, spent = self.get_addr_io(address)
       +            item = received.get(txin.prevout.to_str())
       +            if item:
       +                txin_value = item[1]
       +                txin.witness_utxo = TxOutput.from_address_and_value(address, txin_value)
                if txin.utxo is None:
                    txin.utxo = self.get_input_tx(txin.prevout.txid.hex(), ignore_network_issues=ignore_network_issues)
                txin.ensure_there_is_only_one_utxo()
       t@@ -1802,9 +1813,9 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
                    only_der_suffix: bool = False,
                    ignore_network_issues: bool = True,
            ) -> None:
       -        # note: we add input utxos regardless of is_mine
       -        self._add_input_utxo_info(txin, ignore_network_issues=ignore_network_issues)
                address = self.get_txin_address(txin)
       +        # note: we add input utxos regardless of is_mine
       +        self._add_input_utxo_info(txin, ignore_network_issues=ignore_network_issues, address=address)
                if not self.is_mine(address):
                    is_mine = self._learn_derivation_path_for_address_from_txinout(txin, address)
                    if not is_mine: