URI: 
       tRework wallet history methods: - wallet.get_full_history returns onchain and lightning - capital gains are returned by get_detailed_history - display lightning history in kivy - command line: separate lightning and onchain history - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit af7d7e883c760caedbe8ba738e450fae535b1886
   DIR parent 7e8be3d2e7e178cc6534520c07d0af413896f2bf
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Fri, 14 Jun 2019 13:01:23 +0200
       
       Rework wallet history methods:
        - wallet.get_full_history returns onchain and lightning
        - capital gains are returned by get_detailed_history
        - display lightning history in kivy
        - command line: separate lightning and onchain history
       
       Diffstat:
         M electrum/commands.py                |      15 +++++++++------
         M electrum/gui/kivy/uix/screens.py    |      47 ++++++++++++++++++++-----------
         M electrum/gui/qt/history_list.py     |      39 +++++++------------------------
         M electrum/wallet.py                  |      98 ++++++++++++++++++++-----------
       
       4 files changed, 112 insertions(+), 87 deletions(-)
       ---
   DIR diff --git a/electrum/commands.py b/electrum/commands.py
       t@@ -497,14 +497,11 @@ class Commands:
                return tx.as_dict()
        
            @command('w')
       -    def history(self, year=None, show_addresses=False, show_fiat=False, show_fees=False,
       -                from_height=None, to_height=None):
       -        """Wallet history. Returns the transaction history of your wallet."""
       +    def onchain_history(self, year=None, show_addresses=False, show_fiat=False, show_fees=False):
       +        """Wallet onchain history. Returns the transaction history of your wallet."""
                kwargs = {
                    'show_addresses': show_addresses,
                    'show_fees': show_fees,
       -            'from_height': from_height,
       -            'to_height': to_height,
                }
                if year:
                    import time
       t@@ -516,7 +513,13 @@ class Commands:
                    from .exchange_rate import FxThread
                    fx = FxThread(self.config, None)
                    kwargs['fx'] = fx
       -        return json_encode(self.wallet.get_full_history(**kwargs))
       +        return json_encode(self.wallet.get_detailed_history(**kwargs))
       +
       +    @command('w')
       +    def lightning_history(self, show_fiat=False):
       +        """ lightning history """
       +        lightning_history = self.wallet.lnworker.get_history() if self.wallet.lnworker else []
       +        return json_encode(lightning_history)
        
            @command('w')
            def setlabel(self, key, label):
   DIR diff --git a/electrum/gui/kivy/uix/screens.py b/electrum/gui/kivy/uix/screens.py
       t@@ -26,7 +26,7 @@ from electrum.util import profiler, parse_URI, format_time, InvalidPassword, Not
        from electrum import bitcoin, constants
        from electrum.transaction import TxOutput, Transaction, tx_from_str
        from electrum.util import send_exception_to_crash_reporter, parse_URI, InvalidBitcoinURI
       -from electrum.util import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
       +from electrum.util import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED, TxMinedInfo
        from electrum.plugin import run_hook
        from electrum.wallet import InternalAddressCorruption
        from electrum import simple_config
       t@@ -145,34 +145,49 @@ class HistoryScreen(CScreen):
                d = LabelDialog(_('Enter Transaction Label'), text, callback)
                d.open()
        
       -    def get_card(self, tx_hash, tx_mined_status, value, balance):
       -        status, status_str = self.app.wallet.get_tx_status(tx_hash, tx_mined_status)
       -        icon = "atlas://electrum/gui/kivy/theming/light/" + TX_ICONS[status]
       -        label = self.app.wallet.get_label(tx_hash) if tx_hash else _('Pruned transaction outputs')
       +    def get_card(self, tx_item): #tx_hash, tx_mined_status, value, balance):
       +        is_lightning = tx_item.get('lightning', False)
       +        timestamp = tx_item['timestamp']
       +        if is_lightning:
       +            status = 0
       +            txpos = tx_item['txpos']
       +            if timestamp is None:
       +                status_str = 'unconfirmed'
       +            else:
       +                status_str = format_time(int(timestamp))
       +            icon = "atlas://electrum/gui/kivy/theming/light/lightning"
       +        else:
       +            tx_hash = tx_item['txid']
       +            conf = tx_item['confirmations']
       +            txpos = tx_item['txpos_in_block'] or 0
       +            height = tx_item['height']
       +            tx_mined_info = TxMinedInfo(height=tx_item['height'],
       +                                        conf=tx_item['confirmations'],
       +                                        timestamp=tx_item['timestamp'])
       +            status, status_str = self.app.wallet.get_tx_status(tx_hash, tx_mined_info)
       +            icon = "atlas://electrum/gui/kivy/theming/light/" + TX_ICONS[status]
                ri = {}
                ri['screen'] = self
       -        ri['tx_hash'] = tx_hash
                ri['icon'] = icon
                ri['date'] = status_str
       -        ri['message'] = label
       -        ri['confirmations'] = tx_mined_status.conf
       +        ri['message'] = tx_item['label']
       +        value = tx_item['value'].value
                if value is not None:
                    ri['is_mine'] = value < 0
                    if value < 0: value = - value
                    ri['amount'] = self.app.format_amount_and_units(value)
       -            if self.app.fiat_unit:
       -                fx = self.app.fx
       -                fiat_value = value / Decimal(bitcoin.COIN) * self.app.wallet.price_at_timestamp(tx_hash, fx.timestamp_rate)
       -                fiat_value = Fiat(fiat_value, fx.ccy)
       -                ri['quote_text'] = fiat_value.to_ui_string()
       +            if 'fiat_value' in tx_item:
       +                ri['quote_text'] = tx_item['fiat_value'].to_ui_string()
                return ri
        
            def update(self, see_all=False):
       -        if self.app.wallet is None:
       +        import operator
       +        wallet = self.app.wallet
       +        if wallet is None:
                    return
       -        history = reversed(self.app.wallet.get_history())
       +        history = sorted(wallet.get_full_history(self.app.fx).values(), key=lambda x: x.get('timestamp') or float('inf'), reverse=True)
                history_card = self.screen.ids.history_container
       -        history_card.data = [self.get_card(*item) for item in history]
       +        history_card.data = [self.get_card(item) for item in history]
        
        
        class SendScreen(CScreen):
   DIR diff --git a/electrum/gui/qt/history_list.py b/electrum/gui/qt/history_list.py
       t@@ -116,7 +116,6 @@ class HistoryModel(QAbstractItemModel, Logger):
                self.view = None  # type: HistoryList
                self.transactions = OrderedDictWithIndex()
                self.tx_status_cache = {}  # type: Dict[str, Tuple[int, str]]
       -        self.summary = None
        
            def set_view(self, history_list: 'HistoryList'):
                # FIXME HistoryModel and HistoryList mutually depend on each other.
       t@@ -173,7 +172,7 @@ class HistoryModel(QAbstractItemModel, Logger):
                        HistoryColumns.DESCRIPTION:
                            tx_item['label'] if 'label' in tx_item else None,
                        HistoryColumns.AMOUNT:
       -                    tx_item['value'].value if 'value' in tx_item else None,
       +                    tx_item['bc_value'].value if 'bc_value' in tx_item else None,
                        HistoryColumns.LN_AMOUNT:
                            tx_item['ln_value'].value if 'ln_value' in tx_item else None,
                        HistoryColumns.BALANCE:
       t@@ -217,8 +216,8 @@ class HistoryModel(QAbstractItemModel, Logger):
                    return QVariant(status_str)
                elif col == HistoryColumns.DESCRIPTION and 'label' in tx_item:
                    return QVariant(tx_item['label'])
       -        elif col == HistoryColumns.AMOUNT and 'value' in tx_item:
       -            value = tx_item['value'].value
       +        elif col == HistoryColumns.AMOUNT and 'bc_value' in tx_item:
       +            value = tx_item['bc_value'].value
                    v_str = self.parent.format_amount(value, is_diff=True, whitespaces=True)
                    return QVariant(v_str)
                elif col == HistoryColumns.LN_AMOUNT and 'ln_value' in tx_item:
       t@@ -276,44 +275,22 @@ class HistoryModel(QAbstractItemModel, Logger):
                fx = self.parent.fx
                if fx: fx.history_used_spot = False
                wallet = self.parent.wallet
       -        r = wallet.get_full_history(domain=self.get_domain(), from_timestamp=None, to_timestamp=None, fx=fx)
       -        lightning_history = wallet.lnworker.get_history() if wallet.lnworker else []
                self.set_visibility_of_columns()
       -        #if r['transactions'] == list(self.transactions.values()):
       -        #    return
       +        transactions = wallet.get_full_history(self.parent.fx)
       +        if transactions == list(self.transactions.values()):
       +            return
                old_length = len(self.transactions)
                if old_length != 0:
                    self.beginRemoveRows(QModelIndex(), 0, old_length)
                    self.transactions.clear()
                    self.endRemoveRows()
       -
       -        transactions = OrderedDictWithIndex()
       -        for tx_item in r['transactions']:
       -            txid = tx_item['txid']
       -            transactions[txid] = tx_item
       -        for i, tx_item in enumerate(lightning_history):
       -            txid = tx_item.get('txid')
       -            ln_value = tx_item['amount_msat']/1000.
       -            if txid and txid in transactions:
       -                item = transactions[txid]
       -                item['label'] = tx_item['label']
       -                item['ln_value'] = Satoshis(ln_value)
       -                item['balance_msat'] = tx_item['balance_msat']
       -            else:
       -                tx_item['lightning'] = True
       -                tx_item['ln_value'] = Satoshis(ln_value)
       -                tx_item['txpos'] = i # for sorting
       -                key = tx_item['payment_hash'] if 'payment_hash' in tx_item else tx_item['type'] + tx_item['channel_id']
       -                transactions[key] = tx_item
       -
                self.beginInsertRows(QModelIndex(), 0, len(transactions)-1)
                self.transactions = transactions
                self.endInsertRows()
                if selected_row:
                    self.view.selectionModel().select(self.createIndex(selected_row, 0), QItemSelectionModel.Rows | QItemSelectionModel.SelectCurrent)
                self.view.filter()
       -        # update summary
       -        self.summary = r['summary']
       +        # update time filter
                if not self.view.years and self.transactions:
                    start_date = date.today()
                    end_date = date.today()
       t@@ -535,7 +512,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop):
                    return datetime.datetime(date.year, date.month, date.day)
        
            def show_summary(self):
       -        h = self.model().sourceModel().summary
       +        h = self.parent.wallet.get_detailed_history()['summary']
                if not h:
                    self.parent.show_message(_("Nothing to summarize."))
                    return
   DIR diff --git a/electrum/wallet.py b/electrum/wallet.py
       t@@ -45,7 +45,7 @@ from .util import (NotEnoughFunds, UserCancelled, profiler,
                           format_satoshis, format_fee_satoshis, NoDynamicFeeEstimates,
                           WalletFileException, BitcoinException,
                           InvalidPassword, format_time, timestamp_to_datetime, Satoshis,
       -                   Fiat, bfh, bh2u, TxMinedInfo, quantize_feerate, create_bip21_uri)
       +                   Fiat, bfh, bh2u, TxMinedInfo, quantize_feerate, create_bip21_uri, OrderedDictWithIndex)
        from .simple_config import get_config
        from .bitcoin import (COIN, TYPE_ADDRESS, is_address, address_to_script,
                              is_minikey, relayfee, dust_threshold)
       t@@ -482,45 +482,79 @@ class Abstract_Wallet(AddressSynchronizer):
                # return last balance
                return balance
        
       +    def get_onchain_history(self):
       +        for tx_hash, tx_mined_status, value, balance in self.get_history():
       +            yield {
       +                'txid': tx_hash,
       +                'height': tx_mined_status.height,
       +                'confirmations': tx_mined_status.conf,
       +                'timestamp': tx_mined_status.timestamp,
       +                'incoming': True if value>0 else False,
       +                'bc_value': Satoshis(value),
       +                'balance': Satoshis(balance),
       +                'date': timestamp_to_datetime(tx_mined_status.timestamp),
       +                'label': self.get_label(tx_hash),
       +                'txpos_in_block': tx_mined_status.txpos,
       +            }
       +
       +    @profiler
       +    def get_full_history(self, fx=None):
       +        transactions = OrderedDictWithIndex()
       +        onchain_history = self.get_onchain_history()
       +        for tx_item in onchain_history:
       +            txid = tx_item['txid']
       +            transactions[txid] = tx_item
       +        lightning_history = self.lnworker.get_history() if self.lnworker else []
       +        for i, tx_item in enumerate(lightning_history):
       +            txid = tx_item.get('txid')
       +            ln_value = Decimal(tx_item['amount_msat']) / 1000
       +            if txid and txid in transactions:
       +                item = transactions[txid]
       +                item['label'] = tx_item['label']
       +                item['ln_value'] = Satoshis(ln_value)
       +                item['balance_msat'] = tx_item['balance_msat']
       +            else:
       +                tx_item['lightning'] = True
       +                tx_item['ln_value'] = Satoshis(ln_value)
       +                tx_item['txpos'] = i # for sorting
       +                key = tx_item['payment_hash'] if 'payment_hash' in tx_item else tx_item['type'] + tx_item['channel_id']
       +                transactions[key] = tx_item
       +        now = time.time()
       +        for item in transactions.values():
       +            # add on-chain and lightning values
       +            value = Decimal(0)
       +            if item.get('bc_value'):
       +                value += item['bc_value'].value
       +            if item.get('ln_value'):
       +                value += item.get('ln_value').value
       +            item['value'] = Satoshis(value)
       +            if fx:
       +                timestamp = item['timestamp'] or now
       +                fiat_value = value / Decimal(bitcoin.COIN) * fx.timestamp_rate(timestamp)
       +                item['fiat_value'] = Fiat(fiat_value, fx.ccy)
       +                item['fiat_default'] = True
       +        return transactions
       +
            @profiler
       -    def get_full_history(self, domain=None, from_timestamp=None, to_timestamp=None,
       -                         fx=None, show_addresses=False, show_fees=False,
       -                         from_height=None, to_height=None):
       -        if (from_timestamp is not None or to_timestamp is not None) \
       -                and (from_height is not None or to_height is not None):
       -            raise Exception('timestamp and block height based filtering cannot be used together')
       +    def get_detailed_history(self, from_timestamp=None, to_timestamp=None,
       +                             fx=None, show_addresses=False, show_fees=False):
       +        # History with capital gains, using utxo pricing
       +        # FIXME: Lightning capital gains would requires FIFO
                out = []
                income = 0
                expenditures = 0
                capital_gains = Decimal(0)
                fiat_income = Decimal(0)
                fiat_expenditures = Decimal(0)
       -        h = self.get_history(domain)
                now = time.time()
       -        for tx_hash, tx_mined_status, value, balance in h:
       -            timestamp = tx_mined_status.timestamp
       +        for item in self.get_onchain_history():
       +            timestamp = item['timestamp']
                    if from_timestamp and (timestamp or now) < from_timestamp:
                        continue
                    if to_timestamp and (timestamp or now) >= to_timestamp:
                        continue
       -            height = tx_mined_status.height
       -            if from_height is not None and height < from_height:
       -                continue
       -            if to_height is not None and height >= to_height:
       -                continue
       +            tx_hash = item['txid']
                    tx = self.db.get_transaction(tx_hash)
       -            item = {
       -                'txid': tx_hash,
       -                'height': height,
       -                'confirmations': tx_mined_status.conf,
       -                'timestamp': timestamp,
       -                'incoming': True if value>0 else False,
       -                'value': Satoshis(value),
       -                'balance': Satoshis(balance),
       -                'date': timestamp_to_datetime(timestamp),
       -                'label': self.get_label(tx_hash),
       -                'txpos_in_block': tx_mined_status.txpos,
       -            }
                    tx_fee = None
                    if show_fees:
                        tx_fee = self.get_tx_fee(tx)
       t@@ -529,10 +563,8 @@ class Abstract_Wallet(AddressSynchronizer):
                        item['inputs'] = list(map(lambda x: dict((k, x[k]) for k in ('prevout_hash', 'prevout_n')), tx.inputs()))
                        item['outputs'] = list(map(lambda x:{'address':x.address, 'value':Satoshis(x.value)},
                                                   tx.get_outputs_for_UI()))
       -            # value may be None if wallet is not fully synchronized
       -            if value is None:
       -                continue
                    # fixme: use in and out values
       +            value = item['bc_value'].value
                    if value < 0:
                        expenditures += -value
                    else:
       t@@ -550,7 +582,7 @@ class Abstract_Wallet(AddressSynchronizer):
                    out.append(item)
                # add summary
                if out:
       -            b, v = out[0]['balance'].value, out[0]['value'].value
       +            b, v = out[0]['balance'].value, out[0]['bc_value'].value
                    start_balance = None if b is None or v is None else b - v
                    end_balance = out[-1]['balance'].value
                    if from_timestamp is not None and to_timestamp is not None:
       t@@ -562,15 +594,13 @@ class Abstract_Wallet(AddressSynchronizer):
                    summary = {
                        'start_date': start_date,
                        'end_date': end_date,
       -                'from_height': from_height,
       -                'to_height': to_height,
                        'start_balance': Satoshis(start_balance),
                        'end_balance': Satoshis(end_balance),
                        'incoming': Satoshis(income),
                        'outgoing': Satoshis(expenditures)
                    }
                    if fx and fx.is_enabled() and fx.get_history_config():
       -                unrealized = self.unrealized_gains(domain, fx.timestamp_rate, fx.ccy)
       +                unrealized = self.unrealized_gains(None, fx.timestamp_rate, fx.ccy)
                        summary['fiat_currency'] = fx.ccy
                        summary['fiat_capital_gains'] = Fiat(capital_gains, fx.ccy)
                        summary['fiat_incoming'] = Fiat(fiat_income, fx.ccy)