twallet: fix bump_fee when there are only change outputs - electrum - Electrum Bitcoin wallet
 (HTM) git clone https://git.parazyd.org/electrum
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
       ---
 (DIR) commit d9b5ab30860bd2180e2b7b5592c0229bfc73bff0
 (DIR) parent aac0fe9ae6d36b0500f00ad0d5d3378c54a30a5a
 (HTM) Author: SomberNight <somber.night@protonmail.com>
       Date:   Tue, 22 Oct 2019 17:12:23 +0200
       
       wallet: fix bump_fee when there are only change outputs
       
       closes #5718
       closes #5603
       
       Diffstat:
         M electrum/coinchooser.py             |       1 +
         M electrum/tests/test_wallet_vertica… |      57 +++++++++++++++++++++++++++++++
         M electrum/wallet.py                  |       2 ++
       
       3 files changed, 60 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/electrum/coinchooser.py b/electrum/coinchooser.py
       t@@ -273,6 +273,7 @@ class CoinChooserBase(Logger):
        
                Note: fee_estimator_vb expects virtual bytes
                """
       +        assert outputs, 'tx outputs cannot be empty'
        
                # Deterministic randomness from coins
                utxos = [c['prevout_hash'] + str(c['prevout_n']) for c in coins]
 (DIR) diff --git a/electrum/tests/test_wallet_vertical.py b/electrum/tests/test_wallet_vertical.py
       t@@ -880,6 +880,8 @@ class TestWalletSending(TestCaseForTestnet):
                        self._bump_fee_when_user_sends_max(simulate_moving_txs=simulate_moving_txs)
                    with self.subTest(msg="_bump_fee_when_new_inputs_need_to_be_added", simulate_moving_txs=simulate_moving_txs):
                        self._bump_fee_when_new_inputs_need_to_be_added(simulate_moving_txs=simulate_moving_txs)
       +            with self.subTest(msg="_bump_fee_p2wpkh_when_there_is_only_a_single_output_and_that_is_a_change_address", simulate_moving_txs=simulate_moving_txs):
       +                self._bump_fee_p2wpkh_when_there_is_only_a_single_output_and_that_is_a_change_address(simulate_moving_txs=simulate_moving_txs)
                    with self.subTest(msg="_rbf_batching", simulate_moving_txs=simulate_moving_txs):
                        self._rbf_batching(simulate_moving_txs=simulate_moving_txs)
        
       t@@ -1031,6 +1033,61 @@ class TestWalletSending(TestCaseForTestnet):
                wallet.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
                self.assertEqual((0, 7490060, 0), wallet.get_balance())
        
       +    def _bump_fee_p2wpkh_when_there_is_only_a_single_output_and_that_is_a_change_address(self, *, simulate_moving_txs):
       +        wallet = self.create_standard_wallet_from_seed('frost repair depend effort salon ring foam oak cancel receive save usage')
       +
       +        # bootstrap wallet
       +        funding_tx = Transaction('01000000000102acd6459dec7c3c51048eb112630da756f5d4cb4752b8d39aa325407ae0885cba020000001716001455c7f5e0631d8e6f5f05dddb9f676cec48845532fdffffffd146691ef6a207b682b13da5f2388b1f0d2a2022c8cfb8dc27b65434ec9ec8f701000000171600147b3be8a7ceaf15f57d7df2a3d216bc3c259e3225fdffffff02a9875b000000000017a914ea5a99f83e71d1c1dfc5d0370e9755567fe4a141878096980000000000160014d4ca56fcbad98fb4dcafdc573a75d6a6fffb09b702483045022100dde1ba0c9a2862a65791b8d91295a6603207fb79635935a67890506c214dd96d022046c6616642ef5971103c1db07ac014e63fa3b0e15c5729eacdd3e77fcb7d2086012103a72410f185401bb5b10aaa30989c272b554dc6d53bda6da85a76f662723421af024730440220033d0be8f74e782fbcec2b396647c7715d2356076b442423f23552b617062312022063c95cafdc6d52ccf55c8ee0f9ceb0f57afb41ea9076eb74fe633f59c50c6377012103b96a4954d834fbcfb2bbf8cf7de7dc2b28bc3d661c1557d1fd1db1bfc123a94abb391400')
       +        funding_txid = funding_tx.txid()
       +        funding_output_value = 10000000
       +        self.assertEqual('52e669a20a26c8b3df5b41e5e6309b18bcde8e1ad7ea17a18f63b6dc6c8becc0', funding_txid)
       +        wallet.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
       +
       +        # create tx
       +        outputs = [TxOutput(bitcoin.TYPE_ADDRESS, 'tb1q7rl9cxr85962ztnsze089zs8ycv52hk43f3m9n', '!')]
       +        coins = wallet.get_spendable_coins(domain=None)
       +        tx = wallet.make_unsigned_transaction(coins, outputs, fixed_fee=5000)
       +        tx.set_rbf(True)
       +        tx.locktime = 1325499
       +        if simulate_moving_txs:
       +            tx = Transaction(str(tx))
       +        wallet.sign_transaction(tx, password=None)
       +
       +        self.assertTrue(tx.is_complete())
       +        self.assertTrue(tx.is_segwit())
       +        self.assertEqual(1, len(tx.inputs()))
       +        tx_copy = Transaction(tx.serialize())
       +        self.assertTrue(wallet.is_mine(wallet.get_txin_address(tx_copy.inputs()[0])))
       +
       +        self.assertEqual(tx.txid(), tx_copy.txid())
       +        self.assertEqual(tx.wtxid(), tx_copy.wtxid())
       +        self.assertEqual('02000000000101c0ec8b6cdcb6638fa117ead71a8edebc189b30e6e5415bdfb3c8260aa269e6520100000000fdffffff01f882980000000000160014f0fe5c1867a174a12e70165e728a072619455ed502483045022100ba1899d49de0ae70d407ccc9004a9ba7c0fddf822b6e0a36cc3606862802611402203fbd625a84b9605769e8a51e4ee302871820c84f257b32824a5ac3907be050310121028d4c44ca36d2c4bff3813df8d5d3c0278357521ecb892cd694c473c03970e4c5bb391400',
       +                         str(tx_copy))
       +        self.assertEqual('839b4d7ec2480975126ffa0c2a4552a85dd43435b23b375536391943e1f27074', tx_copy.txid())
       +        self.assertEqual('9c1b7c9b3f87b5ae7c53e3cf40a1cc844a16826268a2abfe043556930da904ac', tx_copy.wtxid())
       +
       +        wallet.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
       +        self.assertEqual((0, funding_output_value - 5000, 0), wallet.get_balance())
       +
       +        # bump tx
       +        tx = wallet.bump_fee(tx=Transaction(tx.serialize()), new_fee_rate=75)
       +        tx.locktime = 1325500
       +        if simulate_moving_txs:
       +            tx = Transaction(str(tx))
       +        self.assertFalse(tx.is_complete())
       +
       +        wallet.sign_transaction(tx, password=None)
       +        self.assertTrue(tx.is_complete())
       +        self.assertTrue(tx.is_segwit())
       +        tx_copy = Transaction(tx.serialize())
       +        self.assertEqual('02000000000101c0ec8b6cdcb6638fa117ead71a8edebc189b30e6e5415bdfb3c8260aa269e6520100000000fdffffff014676980000000000160014f0fe5c1867a174a12e70165e728a072619455ed502483045022100ffe74cde6fabd27cecca29e77b863cd901188cb7870e39c237b193e7532d6ef502203efdf069482ce5f89a4a273166ff319f1e122f2e9d56f37696dc15a1b933df420121028d4c44ca36d2c4bff3813df8d5d3c0278357521ecb892cd694c473c03970e4c5bc391400',
       +                         str(tx_copy))
       +        self.assertEqual('0787da6829907ede8a322273d19ba47943ac234ad7fd1cb1821f6a0e78fcc003', tx_copy.txid())
       +        self.assertEqual('776d3ff6e775f96c58f6bc5b6aab6f218f6cf24a4bb0d2846a95cedb2ee7e1a0', tx_copy.wtxid())
       +
       +        wallet.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
       +        self.assertEqual((0, 9991750, 0), wallet.get_balance())
       +
            def _bump_fee_when_user_sends_max(self, *, simulate_moving_txs):
                wallet = self.create_standard_wallet_from_seed('frost repair depend effort salon ring foam oak cancel receive save usage')
        
 (DIR) diff --git a/electrum/wallet.py b/electrum/wallet.py
       t@@ -1117,6 +1117,8 @@ class Abstract_Wallet(AddressSynchronizer):
                        fixed_outputs = old_not_is_mine
                    else:
                        fixed_outputs = old_outputs
       +        if not fixed_outputs:
       +            raise CannotBumpFee(_('Cannot bump fee') + ': could not figure out which outputs to keep')
        
                coins = self.get_spendable_coins(None)
                for item in coins: