From 2c73e7f85471d2710b22e84a1d9bc2ca75eb1465 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 11 Jul 2022 16:18:38 +0200 Subject: [PATCH] wallet: (regression) fix get_spendable_coins when "domain" arg is given `get_spendable_coins` was ignoring the "domain" param, and returning utxos for the whole wallet --- electrum/tests/test_wallet_vertical.py | 35 ++++++++++++++++++++++++++ electrum/wallet.py | 28 +++++++++++++++------ 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/electrum/tests/test_wallet_vertical.py b/electrum/tests/test_wallet_vertical.py index 9f3c93806..253903051 100644 --- a/electrum/tests/test_wallet_vertical.py +++ b/electrum/tests/test_wallet_vertical.py @@ -2412,6 +2412,41 @@ class TestWalletSending(TestCaseForTestnet): self.assertEqual('02000000000101ce010c0cab95cde544f713771916613a1a84c8787bbc95321854410b212aed9b0100000000fdffffff02cac00000000000001600147a65e09bb1da80abfc65d545388a2e61aab7c721eec100000000000016001405424089c64d39d9a498b6e1c8e646327431b240024730440220526eac6c56cba19842b67f6c9e45af113b1a2d44fb229335bdeaf08cb2cc164e0220087fba65619016fd3f62f6c8717070e48f94b45743b86d8e0517698d2b9c3afc012102d67eaa10463f5c786271feb9ae3456c27d35c3cf6c7d881617e915d1f32cb875c4951e00', str(tx_copy)) + @mock.patch.object(wallet.Abstract_Wallet, 'save_db') + def test_get_spendable_coins(self, mock_save_db): + wallet = self.create_standard_wallet_from_seed('frost repair depend effort salon ring foam oak cancel receive save usage', + config=self.config) + + # bootstrap wallet (incoming funding_tx1) + funding_tx1 = Transaction('01000000000102acd6459dec7c3c51048eb112630da756f5d4cb4752b8d39aa325407ae0885cba020000001716001455c7f5e0631d8e6f5f05dddb9f676cec48845532fdffffffd146691ef6a207b682b13da5f2388b1f0d2a2022c8cfb8dc27b65434ec9ec8f701000000171600147b3be8a7ceaf15f57d7df2a3d216bc3c259e3225fdffffff02a9875b000000000017a914ea5a99f83e71d1c1dfc5d0370e9755567fe4a141878096980000000000160014d4ca56fcbad98fb4dcafdc573a75d6a6fffb09b702483045022100dde1ba0c9a2862a65791b8d91295a6603207fb79635935a67890506c214dd96d022046c6616642ef5971103c1db07ac014e63fa3b0e15c5729eacdd3e77fcb7d2086012103a72410f185401bb5b10aaa30989c272b554dc6d53bda6da85a76f662723421af024730440220033d0be8f74e782fbcec2b396647c7715d2356076b442423f23552b617062312022063c95cafdc6d52ccf55c8ee0f9ceb0f57afb41ea9076eb74fe633f59c50c6377012103b96a4954d834fbcfb2bbf8cf7de7dc2b28bc3d661c1557d1fd1db1bfc123a94abb391400') + funding_txid1 = funding_tx1.txid() + self.assertEqual('52e669a20a26c8b3df5b41e5e6309b18bcde8e1ad7ea17a18f63b6dc6c8becc0', funding_txid1) + wallet.adb.receive_tx_callback(funding_txid1, funding_tx1, TX_HEIGHT_UNCONFIRMED) + + # another incoming transaction (funding_tx2) + funding_tx2 = Transaction('01000000000101c0ec8b6cdcb6638fa117ead71a8edebc189b30e6e5415bdfb3c8260aa269e6520000000017160014ba9ca815474a674ff1efb3fc82cf0f3460de8c57fdffffff0230390f000000000017a9148b59abaca8215c0d4b18cbbf715550aa2b50c85b87404b4c000000000016001483c3bc7234f17a209cc5dcce14903b54ee4dab9002473044022038a05f7d38bcf810dfebb39f1feda5cc187da4cf5d6e56986957ddcccedc75d302203ab67ccf15431b4e2aeeab1582b9a5a7821e7ac4be8ebf512505dbfdc7e094fd0121032168234e0ba465b8cedc10173ea9391725c0f6d9fa517641af87926626a5144abd391400') + funding_txid2 = funding_tx2.txid() + self.assertEqual('c36a6e1cd54df108e69574f70bc9b88dc13beddc70cfad9feb7f8f6593255d4a', funding_txid2) + wallet.adb.receive_tx_callback(funding_txid2, funding_tx2, TX_HEIGHT_UNCONFIRMED) + + self.assertEqual((0, 15_000_000, 0), wallet.get_balance()) + self.assertEqual( + {'c36a6e1cd54df108e69574f70bc9b88dc13beddc70cfad9feb7f8f6593255d4a:1', + '52e669a20a26c8b3df5b41e5e6309b18bcde8e1ad7ea17a18f63b6dc6c8becc0:1'}, + {txi.prevout.to_str() for txi in wallet.get_spendable_coins()}) + self.assertEqual( + {'52e669a20a26c8b3df5b41e5e6309b18bcde8e1ad7ea17a18f63b6dc6c8becc0:1'}, + {txi.prevout.to_str() for txi in wallet.get_spendable_coins(["tb1q6n99dl96mx8mfh90m3tn5awk5mllkzdh25dw7z"])}) + # test freezing an address + wallet.set_frozen_state_of_addresses(["tb1q6n99dl96mx8mfh90m3tn5awk5mllkzdh25dw7z"], freeze=True) + self.assertEqual( + {'c36a6e1cd54df108e69574f70bc9b88dc13beddc70cfad9feb7f8f6593255d4a:1'}, + {txi.prevout.to_str() for txi in wallet.get_spendable_coins()}) + wallet.set_frozen_state_of_addresses(["tb1q6n99dl96mx8mfh90m3tn5awk5mllkzdh25dw7z"], freeze=False) + self.assertEqual( + {'c36a6e1cd54df108e69574f70bc9b88dc13beddc70cfad9feb7f8f6593255d4a:1', + '52e669a20a26c8b3df5b41e5e6309b18bcde8e1ad7ea17a18f63b6dc6c8becc0:1'}, + {txi.prevout.to_str() for txi in wallet.get_spendable_coins()}) class TestWalletOfflineSigning(TestCaseForTestnet): diff --git a/electrum/wallet.py b/electrum/wallet.py index 4ae7419eb..0e391c8e9 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -40,7 +40,7 @@ from functools import partial from collections import defaultdict from numbers import Number from decimal import Decimal -from typing import TYPE_CHECKING, List, Optional, Tuple, Union, NamedTuple, Sequence, Dict, Any, Set +from typing import TYPE_CHECKING, List, Optional, Tuple, Union, NamedTuple, Sequence, Dict, Any, Set, Iterable from abc import ABC, abstractmethod import itertools import threading @@ -830,19 +830,31 @@ class Abstract_Wallet(ABC, Logger, EventListener): def get_addr_balance(self, address): return self.adb.get_balance([address]) - def get_utxos(self, **kwargs): - domain = self.get_addresses() + def get_utxos( + self, + domain: Optional[Iterable[str]] = None, + **kwargs, + ): + if domain is None: + domain = self.get_addresses() return self.adb.get_utxos(domain=domain, **kwargs) - def get_spendable_coins(self, domain, *, nonlocal_only=False) -> Sequence[PartialTxInput]: + def get_spendable_coins( + self, + domain: Optional[Iterable[str]] = None, + *, + nonlocal_only: bool = False, + ) -> Sequence[PartialTxInput]: confirmed_only = self.config.get('confirmed_only', False) with self._freeze_lock: frozen_addresses = self._frozen_addresses.copy() utxos = self.get_utxos( - excluded_addresses=frozen_addresses, - mature_only=True, - confirmed_funding_only=confirmed_only, - nonlocal_only=nonlocal_only) + domain=domain, + excluded_addresses=frozen_addresses, + mature_only=True, + confirmed_funding_only=confirmed_only, + nonlocal_only=nonlocal_only, + ) utxos = [utxo for utxo in utxos if not self.is_frozen_coin(utxo)] return utxos