electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit da777caa0bec0388e62f429dacf3e4a375670c35
parent c4e9afa019e74a46585ca9af3eef433f19f338b9
Author: SomberNight <somber.night@protonmail.com>
Date:   Fri, 22 Jan 2021 16:25:45 +0100

wallet: use lock when modifying frozen_{addresses,coins}

Diffstat:
Melectrum/wallet.py | 58++++++++++++++++++++++++++++++++++------------------------
1 file changed, 34 insertions(+), 24 deletions(-)

diff --git a/electrum/wallet.py b/electrum/wallet.py @@ -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 @@ -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 @@ -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) @@ -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): @@ -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'