URI: 
       twallet: use lock when modifying frozen_{addresses,coins} - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit da777caa0bec0388e62f429dacf3e4a375670c35
   DIR parent c4e9afa019e74a46585ca9af3eef433f19f338b9
  HTML Author: SomberNight <somber.night@protonmail.com>
       Date:   Fri, 22 Jan 2021 16:25:45 +0100
       
       wallet: use lock when modifying frozen_{addresses,coins}
       
       Diffstat:
         M electrum/wallet.py                  |      58 ++++++++++++++++++-------------
       
       1 file changed, 34 insertions(+), 24 deletions(-)
       ---
   DIR diff --git a/electrum/wallet.py b/electrum/wallet.py
       t@@ -43,6 +43,7 @@ from decimal import Decimal
        from typing import TYPE_CHECKING, List, Optional, Tuple, Union, NamedTuple, Sequence, Dict, Any, Set
        from abc import ABC, abstractmethod
        import itertools
       +import threading
        
        from aiorpcx import TaskGroup
        
       t@@ -285,13 +286,15 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
                self.use_change            = db.get('use_change', True)
                self.multiple_change       = db.get('multiple_change', False)
                self._labels                = db.get_dict('labels')
       -        self.frozen_addresses      = set(db.get('frozen_addresses', []))
       -        self.frozen_coins          = set(db.get('frozen_coins', []))  # set of txid:vout strings
       +        self._frozen_addresses      = set(db.get('frozen_addresses', []))
       +        self._frozen_coins          = set(db.get('frozen_coins', []))  # set of txid:vout strings
                self.fiat_value            = db.get_dict('fiat_value')
                self.receive_requests      = db.get_dict('payment_requests')  # type: Dict[str, Invoice]
                self.invoices              = db.get_dict('invoices')  # type: Dict[str, Invoice]
                self._reserved_addresses   = set(db.get('reserved_addresses', []))
        
       +        self._freeze_lock = threading.Lock()  # for mutating/iterating frozen_{addresses,coins}
       +
                self._prepare_onchain_invoice_paid_detection()
                self.calc_unused_change_addresses()
                # save wallet type the first time
       t@@ -657,8 +660,10 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
        
            def get_spendable_coins(self, domain, *, nonlocal_only=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(domain,
       -                               excluded_addresses=self.frozen_addresses,
       +                               excluded_addresses=frozen_addresses,
                                       mature_only=True,
                                       confirmed_only=confirmed_only,
                                       nonlocal_only=nonlocal_only)
       t@@ -678,11 +683,16 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
                return self.get_receiving_addresses(slice_start=0, slice_stop=1)[0]
        
            def get_frozen_balance(self):
       -        if not self.frozen_coins:  # shortcut
       -            return self.get_balance(self.frozen_addresses)
       +        with self._freeze_lock:
       +            frozen_addresses = self._frozen_addresses.copy()
       +            frozen_coins = self._frozen_coins.copy()
       +        if not frozen_coins:  # shortcut
       +            return self.get_balance(frozen_addresses)
                c1, u1, x1 = self.get_balance()
       -        c2, u2, x2 = self.get_balance(excluded_addresses=self.frozen_addresses,
       -                                      excluded_coins=self.frozen_coins)
       +        c2, u2, x2 = self.get_balance(
       +            excluded_addresses=frozen_addresses,
       +            excluded_coins=frozen_coins,
       +        )
                return c1-c2, u1-u2, x1-x2
        
            def balance_at_timestamp(self, domain, target_timestamp):
       t@@ -1309,33 +1319,33 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
                return tx
        
            def is_frozen_address(self, addr: str) -> bool:
       -        return addr in self.frozen_addresses
       +        return addr in self._frozen_addresses
        
            def is_frozen_coin(self, utxo: PartialTxInput) -> bool:
                prevout_str = utxo.prevout.to_str()
       -        return prevout_str in self.frozen_coins
       +        return prevout_str in self._frozen_coins
        
       -    def set_frozen_state_of_addresses(self, addrs, freeze: bool):
       +    def set_frozen_state_of_addresses(self, addrs: Sequence[str], freeze: bool) -> bool:
                """Set frozen state of the addresses to FREEZE, True or False"""
                if all(self.is_mine(addr) for addr in addrs):
       -            # FIXME take lock?
       -            if freeze:
       -                self.frozen_addresses |= set(addrs)
       -            else:
       -                self.frozen_addresses -= set(addrs)
       -            self.db.put('frozen_addresses', list(self.frozen_addresses))
       -            return True
       +            with self._freeze_lock:
       +                if freeze:
       +                    self._frozen_addresses |= set(addrs)
       +                else:
       +                    self._frozen_addresses -= set(addrs)
       +                self.db.put('frozen_addresses', list(self._frozen_addresses))
       +                return True
                return False
        
       -    def set_frozen_state_of_coins(self, utxos: Sequence[PartialTxInput], freeze: bool):
       +    def set_frozen_state_of_coins(self, utxos: Sequence[PartialTxInput], freeze: bool) -> None:
                """Set frozen state of the utxos to FREEZE, True or False"""
                utxos = {utxo.prevout.to_str() for utxo in utxos}
       -        # FIXME take lock?
       -        if freeze:
       -            self.frozen_coins |= set(utxos)
       -        else:
       -            self.frozen_coins -= set(utxos)
       -        self.db.put('frozen_coins', list(self.frozen_coins))
       +        with self._freeze_lock:
       +            if freeze:
       +                self._frozen_coins |= set(utxos)
       +            else:
       +                self._frozen_coins -= set(utxos)
       +            self.db.put('frozen_coins', list(self._frozen_coins))
        
            def is_address_reserved(self, addr: str) -> bool:
                # note: atm 'reserved' status is only taken into consideration for 'change addresses'