twallet: txi/txo small clean-up - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit b138fff9a53dc7f8ad1e15c834143ace165f399e DIR parent 241873f0a4bcf907f5a4ae3bba087355a5f14c34 HTML Author: SomberNight <somber.night@protonmail.com> Date: Wed, 11 Sep 2019 21:47:44 +0200 wallet: txi/txo small clean-up Diffstat: M electrum/address_synchronizer.py | 19 ++++++++++--------- M electrum/json_db.py | 27 +++++++++++++++++---------- M electrum/transaction.py | 7 +++++-- M electrum/wallet.py | 12 ++++++------ 4 files changed, 38 insertions(+), 27 deletions(-) --- DIR diff --git a/electrum/address_synchronizer.py b/electrum/address_synchronizer.py t@@ -125,13 +125,13 @@ class AddressSynchronizer(Logger): """Return number of transactions where address is involved.""" return len(self._history_local.get(addr, ())) - def get_txin_address(self, txi): + def get_txin_address(self, txi) -> Optional[str]: addr = txi.get('address') if addr and addr != "(pubkey)": return addr prevout_hash = txi.get('prevout_hash') prevout_n = txi.get('prevout_n') - for addr in self.db.get_txo(prevout_hash): + for addr in self.db.get_txo_addresses(prevout_hash): l = self.db.get_txo_addr(prevout_hash, addr) for n, v, is_cb in l: if n == prevout_n: t@@ -266,7 +266,7 @@ class AddressSynchronizer(Logger): # add inputs def add_value_from_prev_output(): # note: this nested loop takes linear time in num is_mine outputs of prev_tx - for addr in self.db.get_txo(prevout_hash): + for addr in self.db.get_txo_addresses(prevout_hash): outputs = self.db.get_txo_addr(prevout_hash, addr) # note: instead of [(n, v, is_cb), ...]; we could store: {n -> (v, is_cb)} for n, v, is_cb in outputs: t@@ -325,7 +325,7 @@ class AddressSynchronizer(Logger): tx = self.db.remove_transaction(tx_hash) remove_from_spent_outpoints() self._remove_tx_from_local_history(tx_hash) - for addr in itertools.chain(self.db.get_txi(tx_hash), self.db.get_txo(tx_hash)): + for addr in itertools.chain(self.db.get_txi_addresses(tx_hash), self.db.get_txo_addresses(tx_hash)): self._get_addr_balance_cache.pop(addr, None) # invalidate cache self.db.remove_txi(tx_hash) self.db.remove_txo(tx_hash) t@@ -384,7 +384,7 @@ class AddressSynchronizer(Logger): for addr in hist_addrs_mine: hist = self.db.get_addr_history(addr) for tx_hash, tx_height in hist: - if self.db.get_txi(tx_hash) or self.db.get_txo(tx_hash): + if self.db.get_txi_addresses(tx_hash) or self.db.get_txo_addresses(tx_hash): continue tx = self.db.get_transaction(tx_hash) if tx is not None: t@@ -475,7 +475,7 @@ class AddressSynchronizer(Logger): def _add_tx_to_local_history(self, txid): with self.transaction_lock: - for addr in itertools.chain(self.db.get_txi(txid), self.db.get_txo(txid)): + for addr in itertools.chain(self.db.get_txi_addresses(txid), self.db.get_txo_addresses(txid)): cur_hist = self._history_local.get(addr, set()) cur_hist.add(txid) self._history_local[addr] = cur_hist t@@ -483,7 +483,7 @@ class AddressSynchronizer(Logger): def _remove_tx_from_local_history(self, txid): with self.transaction_lock: - for addr in itertools.chain(self.db.get_txi(txid), self.db.get_txo(txid)): + for addr in itertools.chain(self.db.get_txi_addresses(txid), self.db.get_txo_addresses(txid)): cur_hist = self._history_local.get(addr, set()) try: cur_hist.remove(txid) t@@ -584,6 +584,7 @@ class AddressSynchronizer(Logger): height = self.unverified_tx[tx_hash] return TxMinedInfo(height=height, conf=0) elif tx_hash in self.future_tx: + # FIXME this is ugly conf = self.future_tx[tx_hash] return TxMinedInfo(height=TX_HEIGHT_FUTURE, conf=conf) else: t@@ -623,11 +624,11 @@ class AddressSynchronizer(Logger): def get_tx_value(self, txid): """effect of tx on the entire domain""" delta = 0 - for addr in self.db.get_txi(txid): + for addr in self.db.get_txi_addresses(txid): d = self.db.get_txi_addr(txid, addr) for n, v in d: delta -= v - for addr in self.db.get_txo(txid): + for addr in self.db.get_txo_addresses(txid): d = self.db.get_txo_addr(txid, addr) for n, v, cb in d: delta += v DIR diff --git a/electrum/json_db.py b/electrum/json_db.py t@@ -28,7 +28,7 @@ import json import copy import threading from collections import defaultdict -from typing import Dict, Optional +from typing import Dict, Optional, List, Tuple, Set, Iterable from . import util, bitcoin from .util import profiler, WalletFileException, multisig_type, TxMinedInfo t@@ -518,20 +518,24 @@ class JsonDB(Logger): raise WalletFileException(msg) @locked - def get_txi(self, tx_hash): + def get_txi_addresses(self, tx_hash) -> List[str]: + """Returns list of is_mine addresses that appear as inputs in tx.""" return list(self.txi.get(tx_hash, {}).keys()) @locked - def get_txo(self, tx_hash): + def get_txo_addresses(self, tx_hash) -> List[str]: + """Returns list of is_mine addresses that appear as outputs in tx.""" return list(self.txo.get(tx_hash, {}).keys()) @locked - def get_txi_addr(self, tx_hash, address): - return self.txi.get(tx_hash, {}).get(address, []) + def get_txi_addr(self, tx_hash, address) -> Iterable[Tuple[str, int]]: + """Returns an iterable of (prev_outpoint, value).""" + return self.txi.get(tx_hash, {}).get(address, []).copy() @locked - def get_txo_addr(self, tx_hash, address): - return self.txo.get(tx_hash, {}).get(address, []) + def get_txo_addr(self, tx_hash, address) -> Iterable[Tuple[int, int, bool]]: + """Returns an iterable of (output_index, value, is_coinbase).""" + return self.txo.get(tx_hash, {}).get(address, []).copy() @modifier def add_txi_addr(self, tx_hash, addr, ser, v): t@@ -754,8 +758,11 @@ class JsonDB(Logger): @profiler def _load_transactions(self): # references in self.data - self.txi = self.get_data_ref('txi') # txid -> address -> list of (prev_outpoint, value) - self.txo = self.get_data_ref('txo') # txid -> address -> list of (output_index, value, is_coinbase) + # TODO make all these private + # txid -> address -> set of (prev_outpoint, value) + self.txi = self.get_data_ref('txi') # type: Dict[str, Dict[str, Set[Tuple[str, int]]]] + # txid -> address -> set of (output_index, value, is_coinbase) + self.txo = self.get_data_ref('txo') # type: Dict[str, Dict[str, Set[Tuple[int, int, bool]]]] self.transactions = self.get_data_ref('transactions') # type: Dict[str, Transaction] self.spent_outpoints = self.get_data_ref('spent_outpoints') self.history = self.get_data_ref('addr_history') # address -> list of (txid, height) t@@ -771,7 +778,7 @@ class JsonDB(Logger): d[addr] = set([tuple(x) for x in lst]) # remove unreferenced tx for tx_hash in list(self.transactions.keys()): - if not self.get_txi(tx_hash) and not self.get_txo(tx_hash): + if not self.get_txi_addresses(tx_hash) and not self.get_txo_addresses(tx_hash): self.logger.info(f"removing unreferenced tx: {tx_hash}") self.transactions.pop(tx_hash) # remove unreferenced outpoints DIR diff --git a/electrum/transaction.py b/electrum/transaction.py t@@ -31,7 +31,7 @@ import struct import traceback import sys from typing import (Sequence, Union, NamedTuple, Tuple, Optional, Iterable, - Callable, List, Dict, Set) + Callable, List, Dict, Set, TYPE_CHECKING) from collections import defaultdict from . import ecc, bitcoin, constants, segwit_addr t@@ -45,6 +45,9 @@ from .crypto import sha256d from .keystore import xpubkey_to_address, xpubkey_to_pubkey from .logging import get_logger +if TYPE_CHECKING: + from .wallet import Abstract_Wallet + _logger = get_logger(__name__) t@@ -687,7 +690,7 @@ class Transaction: txin['witness'] = None # force re-serialization self.raw = None - def add_inputs_info(self, wallet): + def add_inputs_info(self, wallet: 'Abstract_Wallet') -> None: if self.is_complete(): return for txin in self.inputs(): DIR diff --git a/electrum/wallet.py b/electrum/wallet.py t@@ -699,9 +699,9 @@ class Abstract_Wallet(AddressSynchronizer): return self.labels.get(tx_hash, '') or self.get_default_label(tx_hash) def get_default_label(self, tx_hash): - if not self.db.get_txi(tx_hash): + if not self.db.get_txi_addresses(tx_hash): labels = [] - for addr in self.db.get_txo(tx_hash): + for addr in self.db.get_txo_addresses(tx_hash): label = self.labels.get(addr) if label: labels.append(label) t@@ -1150,7 +1150,7 @@ class Abstract_Wallet(AddressSynchronizer): txin['value'] = item[1] self.add_input_sig_info(txin, address) - def can_sign(self, tx): + def can_sign(self, tx: Transaction) -> bool: if tx.is_complete(): return False # add info to inputs if we can; otherwise we might return a false negative: t@@ -1505,7 +1505,7 @@ class Abstract_Wallet(AddressSynchronizer): def txin_value(self, txin): txid = txin['prevout_hash'] prev_n = txin['prevout_n'] - for addr in self.db.get_txo(txid): + for addr in self.db.get_txo_addresses(txid): d = self.db.get_txo_addr(txid, addr) for n, v, cb in d: if n == prev_n: t@@ -1530,7 +1530,7 @@ class Abstract_Wallet(AddressSynchronizer): """ Average acquisition price of the inputs of a transaction """ input_value = 0 total_price = 0 - for addr in self.db.get_txi(txid): + for addr in self.db.get_txi_addresses(txid): d = self.db.get_txi_addr(txid, addr) for ser, v in d: input_value += v t@@ -1551,7 +1551,7 @@ class Abstract_Wallet(AddressSynchronizer): result = self._coin_price_cache.get(cache_key, None) if result is not None: return result - if self.db.get_txi(txid): + if self.db.get_txi_addresses(txid): result = self.average_price(txid, price_func, ccy) * txin_value/Decimal(COIN) self._coin_price_cache[cache_key] = result return result