|
|
@ -287,7 +287,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC): |
|
|
|
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_coins = db.get_dict('frozen_coins') # type: Dict[str, bool] |
|
|
|
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] |
|
|
@ -685,7 +685,10 @@ class Abstract_Wallet(AddressSynchronizer, ABC): |
|
|
|
def get_frozen_balance(self): |
|
|
|
with self._freeze_lock: |
|
|
|
frozen_addresses = self._frozen_addresses.copy() |
|
|
|
frozen_coins = self._frozen_coins.copy() |
|
|
|
# note: for coins, use is_frozen_coin instead of _frozen_coins, |
|
|
|
# as latter only contains *manually* frozen ones |
|
|
|
frozen_coins = {utxo.prevout.to_str() for utxo in self.get_utxos() |
|
|
|
if self.is_frozen_coin(utxo)} |
|
|
|
if not frozen_coins: # shortcut |
|
|
|
return self.get_balance(frozen_addresses) |
|
|
|
c1, u1, x1 = self.get_balance() |
|
|
@ -1323,7 +1326,46 @@ class Abstract_Wallet(AddressSynchronizer, ABC): |
|
|
|
|
|
|
|
def is_frozen_coin(self, utxo: PartialTxInput) -> bool: |
|
|
|
prevout_str = utxo.prevout.to_str() |
|
|
|
return prevout_str in self._frozen_coins |
|
|
|
frozen = self._frozen_coins.get(prevout_str, None) |
|
|
|
# note: there are three possible states for 'frozen': |
|
|
|
# True/False if the user explicitly set it, |
|
|
|
# None otherwise |
|
|
|
if frozen is None: |
|
|
|
return self._is_coin_small_and_unconfirmed(utxo) |
|
|
|
return bool(frozen) |
|
|
|
|
|
|
|
def _is_coin_small_and_unconfirmed(self, utxo: PartialTxInput) -> bool: |
|
|
|
"""If true, the coin should not be spent. |
|
|
|
The idea here is that an attacker might send us a UTXO in a |
|
|
|
large low-fee unconfirmed tx that will ~never confirm. If we |
|
|
|
spend it as part of a tx ourselves, that too will not confirm |
|
|
|
(unless we use a high fee but that might not be worth it for |
|
|
|
a small value UTXO). |
|
|
|
In particular, this test triggers for large "dusting transactions" |
|
|
|
that are used for advertising purposes by some entities. |
|
|
|
see #6960 |
|
|
|
""" |
|
|
|
# confirmed UTXOs are fine; check this first for performance: |
|
|
|
block_height = utxo.block_height |
|
|
|
assert block_height is not None |
|
|
|
if block_height > 0: |
|
|
|
return False |
|
|
|
# exempt large value UTXOs |
|
|
|
value_sats = utxo.value_sats() |
|
|
|
assert value_sats is not None |
|
|
|
threshold = self.config.get('unconf_utxo_freeze_threshold', 5_000) |
|
|
|
if value_sats >= threshold: |
|
|
|
return False |
|
|
|
# if funding tx has any is_mine input, then UTXO is fine |
|
|
|
funding_tx = self.db.get_transaction(utxo.prevout.txid.hex()) |
|
|
|
if funding_tx is None: |
|
|
|
# we should typically have the funding tx available; |
|
|
|
# might not have it e.g. while not up_to_date |
|
|
|
return True |
|
|
|
if any(self.is_mine(self.get_txin_address(txin)) |
|
|
|
for txin in funding_tx.inputs()): |
|
|
|
return False |
|
|
|
return True |
|
|
|
|
|
|
|
def set_frozen_state_of_addresses(self, addrs: Sequence[str], freeze: bool) -> bool: |
|
|
|
"""Set frozen state of the addresses to FREEZE, True or False""" |
|
|
@ -1342,11 +1384,8 @@ class Abstract_Wallet(AddressSynchronizer, ABC): |
|
|
|
# basic sanity check that input is not garbage: (see if raises) |
|
|
|
[TxOutpoint.from_str(utxo) for utxo in utxos] |
|
|
|
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)) |
|
|
|
for utxo in utxos: |
|
|
|
self._frozen_coins[utxo] = bool(freeze) |
|
|
|
|
|
|
|
def is_address_reserved(self, addr: str) -> bool: |
|
|
|
# note: atm 'reserved' status is only taken into consideration for 'change addresses' |
|
|
@ -1694,7 +1733,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC): |
|
|
|
return True |
|
|
|
return False |
|
|
|
|
|
|
|
def get_input_tx(self, tx_hash, *, ignore_network_issues=False) -> Optional[Transaction]: |
|
|
|
def get_input_tx(self, tx_hash: str, *, ignore_network_issues=False) -> Optional[Transaction]: |
|
|
|
# First look up an input transaction in the wallet where it |
|
|
|
# will likely be. If co-signing a transaction it may not have |
|
|
|
# all the input txs, in which case we ask the network. |
|
|
|