twallet: txi/txo small clean-up - electrum - Electrum Bitcoin wallet
 (HTM) git clone https://git.parazyd.org/electrum
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
       ---
 (DIR) commit b138fff9a53dc7f8ad1e15c834143ace165f399e
 (DIR) parent 241873f0a4bcf907f5a4ae3bba087355a5f14c34
 (HTM) 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