URI: 
       tturn lightning_payments_completed into dict. Show status of lightning payments in GUI. Make 'listchannels' available offline - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit b0d60007719ede7d4bc213a9393667e9b9328161
   DIR parent 26ced1b34375ae2ae73ed7e55c2c80b47b6f4930
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Mon, 28 Jan 2019 11:14:30 +0100
       
       tturn lightning_payments_completed into dict. Show status of lightning payments in GUI. Make 'listchannels' available offline
       
       Diffstat:
         M electrum/commands.py                |      35 +++++++++++++++++++++++++++++--
         M electrum/gui/kivy/uix/screens.py    |       2 +-
         M electrum/gui/qt/request_list.py     |      12 ++++++++----
         M electrum/gui/qt/util.py             |      11 ++++++-----
         M electrum/lnworker.py                |      52 ++++++++++---------------------
         M electrum/paymentrequest.py          |       7 +------
         M electrum/util.py                    |       7 +++++++
       
       7 files changed, 73 insertions(+), 53 deletions(-)
       ---
   DIR diff --git a/electrum/commands.py b/electrum/commands.py
       t@@ -786,7 +786,7 @@ class Commands:
            def nodeid(self):
                return bh2u(self.wallet.lnworker.node_keypair.pubkey)
        
       -    @command('wn')
       +    @command('w')
            def listchannels(self):
                return list(self.wallet.lnworker.list_channels())
        
       t@@ -806,7 +806,38 @@ class Commands:
        
            @command('w')
            def listinvoices(self):
       -        return "\n".join(self.wallet.lnworker.list_invoices())
       +        report = self.wallet.lnworker._list_invoices()
       +        return '\n'.join(self._format_ln_invoices(report))
       +
       +    def _format_ln_invoices(self, report):
       +        from .lnutil import SENT
       +        if report['settled']:
       +            yield 'Settled invoices:'
       +            yield '-----------------'
       +            for date, direction, htlc, preimage in sorted(report['settled']):
       +                # astimezone converts to local time
       +                # replace removes the tz info since we don't need to display it
       +                yield 'Paid at: ' + date.astimezone().replace(tzinfo=None).isoformat(sep=' ', timespec='minutes')
       +                yield 'We paid' if direction == SENT else 'They paid'
       +                yield str(htlc)
       +                yield 'Preimage: ' + (bh2u(preimage) if preimage else 'Not available') # if delete_invoice was called
       +                yield ''
       +        if report['unsettled']:
       +            yield 'Your unsettled invoices:'
       +            yield '------------------------'
       +            for addr, preimage, pay_req in report['unsettled']:
       +                yield pay_req
       +                yield str(addr)
       +                yield 'Preimage: ' + bh2u(preimage)
       +                yield ''
       +        if report['inflight']:
       +            yield 'Outgoing payments in progress:'
       +            yield '------------------------------'
       +            for addr, htlc, direction in report['inflight']:
       +                yield str(addr)
       +                yield str(htlc)
       +                yield ''
       +
        
            @command('wn')
            def closechannel(self, channel_point, force=False):
   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.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
       +from electrum.util import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
        from electrum.plugin import run_hook
        from electrum.wallet import InternalAddressCorruption
        from electrum import simple_config
   DIR diff --git a/electrum/gui/qt/request_list.py b/electrum/gui/qt/request_list.py
       t@@ -31,8 +31,8 @@ from PyQt5.QtCore import Qt, QItemSelectionModel
        
        from electrum.i18n import _
        from electrum.util import format_time, age
       +from electrum.util import PR_UNPAID, PR_EXPIRED, PR_PAID, PR_UNKNOWN, PR_INFLIGHT
        from electrum.plugin import run_hook
       -from electrum.paymentrequest import PR_UNKNOWN
        from electrum.wallet import InternalAddressCorruption
        from electrum.bitcoin import COIN
        from electrum.lnaddr import lndecode
       t@@ -144,7 +144,9 @@ class RequestList(MyTreeView):
                    items[0].setData(address, ROLE_RHASH_OR_ADDR)
                self.filter()
                # lightning
       -        for payreq_key, (preimage_hex, invoice) in self.wallet.lnworker.invoices.items():
       +        lnworker = self.wallet.lnworker
       +        for key, (preimage_hex, invoice) in lnworker.invoices.items():
       +            status = lnworker.get_invoice_status(key)
                    lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
                    amount_sat = lnaddr.amount*COIN if lnaddr.amount else None
                    amount_str = self.parent.format_amount(amount_sat) if amount_sat else ''
       t@@ -154,11 +156,13 @@ class RequestList(MyTreeView):
                            description = v
                            break
                    date = format_time(lnaddr.date)
       -            labels = [date, 'lightning', description, amount_str, '']
       +            labels = [date, 'lightning', description, amount_str, pr_tooltips.get(status,'')]
                    items = [QStandardItem(e) for e in labels]
                    items[1].setIcon(self.icon_cache.get(":icons/lightning.png"))
                    items[0].setData(REQUEST_TYPE_LN, ROLE_REQUEST_TYPE)
       -            items[0].setData(payreq_key, ROLE_RHASH_OR_ADDR)
       +            items[0].setData(key, ROLE_RHASH_OR_ADDR)
       +            if status is not PR_UNKNOWN:
       +                items[4].setIcon(self.icon_cache.get(pr_icons.get(status)))
                    self.model().insertRow(self.model().rowCount(), items)
                # sort requests by date
                self.model().sort(0)
   DIR diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py
       t@@ -23,9 +23,8 @@ from PyQt5.QtWidgets import (QPushButton, QLabel, QMessageBox, QHBoxLayout,
                                     QHeaderView, QApplication, QToolTip, QTreeWidget, QStyledItemDelegate)
        
        from electrum.i18n import _, languages
       -from electrum.util import (FileImportFailed, FileExportFailed,
       -                           resource_path)
       -from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_EXPIRED
       +from electrum.util import FileImportFailed, FileExportFailed, make_aiohttp_session, PrintError, resource_path
       +from electrum.util import PR_UNPAID, PR_PAID, PR_EXPIRED, PR_INFLIGHT
        
        if TYPE_CHECKING:
            from .main_window import ElectrumWindow
       t@@ -44,13 +43,15 @@ dialogs = []
        pr_icons = {
            PR_UNPAID:"unpaid.png",
            PR_PAID:"confirmed.png",
       -    PR_EXPIRED:"expired.png"
       +    PR_EXPIRED:"expired.png",
       +    PR_INFLIGHT:"lightning.png",
        }
        
        pr_tooltips = {
            PR_UNPAID:_('Pending'),
            PR_PAID:_('Paid'),
       -    PR_EXPIRED:_('Expired')
       +    PR_EXPIRED:_('Expired'),
       +    PR_INFLIGHT:_('Inflight')
        }
        
        expiration_values = [
   DIR diff --git a/electrum/lnworker.py b/electrum/lnworker.py
       t@@ -67,13 +67,14 @@ class LNWorker(PrintError):
            def __init__(self, wallet: 'Abstract_Wallet'):
                self.wallet = wallet
                # invoices we are currently trying to pay (might be pending HTLCs on a commitment transaction)
       +        self.invoices = self.wallet.storage.get('lightning_invoices', {})  # type: Dict[str, Tuple[str,str]]  # RHASH -> (preimage, invoice)
                self.paying = self.wallet.storage.get('lightning_payments_inflight', {}) # type: Dict[bytes, Tuple[str, Optional[int], str]]
       +        self.completed = self.wallet.storage.get('lightning_payments_completed', {})
                self.sweep_address = wallet.get_receiving_address()
                self.lock = threading.RLock()
                self.ln_keystore = self._read_ln_keystore()
                self.node_keypair = generate_keypair(self.ln_keystore, LnKeyFamily.NODE_KEY, 0)
                self.peers = {}  # type: Dict[bytes, Peer]  # pubkey -> Peer
       -        self.invoices = wallet.storage.get('lightning_invoices', {})  # type: Dict[str, Tuple[str,str]]  # RHASH -> (preimage, invoice)
                self.channels = {}  # type: Dict[bytes, Channel]
                for x in wallet.storage.get("channels", []):
                    c = Channel(x, sweep_address=self.sweep_address, payment_completed=self.payment_completed)
       t@@ -123,56 +124,37 @@ class LNWorker(PrintError):
        
            def payment_completed(self, chan, direction, htlc, preimage):
                assert type(direction) is Direction
       +        key = bh2u(htlc.payment_hash)
                chan_id = chan.channel_id
                if direction == SENT:
                    assert htlc.payment_hash not in self.invoices
       -            self.paying.pop(bh2u(htlc.payment_hash))
       +            self.paying.pop(key)
                    self.wallet.storage.put('lightning_payments_inflight', self.paying)
       -        l = self.wallet.storage.get('lightning_payments_completed', [])
                if not preimage:
                    preimage, _addr = self.get_invoice(htlc.payment_hash)
                tupl = (time.time(), direction, json.loads(encoder.encode(htlc)), bh2u(preimage), bh2u(chan_id))
       -        l.append(tupl)
       -        self.wallet.storage.put('lightning_payments_completed', l)
       +        self.completed[key] = tupl
       +        self.wallet.storage.put('lightning_payments_completed', self.completed)
                self.wallet.storage.write()
                self.network.trigger_callback('ln_payment_completed', tupl[0], direction, htlc, preimage, chan_id)
        
       -    def list_invoices(self):
       -        report = self._list_invoices()
       -        if report['settled']:
       -            yield 'Settled invoices:'
       -            yield '-----------------'
       -            for date, direction, htlc, preimage in sorted(report['settled']):
       -                # astimezone converts to local time
       -                # replace removes the tz info since we don't need to display it
       -                yield 'Paid at: ' + date.astimezone().replace(tzinfo=None).isoformat(sep=' ', timespec='minutes')
       -                yield 'We paid' if direction == SENT else 'They paid'
       -                yield str(htlc)
       -                yield 'Preimage: ' + (bh2u(preimage) if preimage else 'Not available') # if delete_invoice was called
       -                yield ''
       -        if report['unsettled']:
       -            yield 'Your unsettled invoices:'
       -            yield '------------------------'
       -            for addr, preimage, pay_req in report['unsettled']:
       -                yield pay_req
       -                yield str(addr)
       -                yield 'Preimage: ' + bh2u(preimage)
       -                yield ''
       -        if report['inflight']:
       -            yield 'Outgoing payments in progress:'
       -            yield '------------------------------'
       -            for addr, htlc, direction in report['inflight']:
       -                yield str(addr)
       -                yield str(htlc)
       -                yield ''
       +    def get_invoice_status(self, key):
       +        from electrum.util import PR_UNPAID, PR_EXPIRED, PR_PAID, PR_UNKNOWN, PR_INFLIGHT
       +        if key in self.completed:
       +            return PR_PAID
       +        elif key in self.paying:
       +            return PR_INFLIGHT
       +        elif key in self.invoices:
       +            return PR_UNPAID
       +        else:
       +            return PR_UNKNOWN
        
            def _list_invoices(self, chan_id=None):
                invoices  = dict(self.invoices)
       -        completed = self.wallet.storage.get('lightning_payments_completed', [])
                settled = []
                unsettled = []
                inflight = []
       -        for date, direction, htlc, hex_preimage, hex_chan_id in completed:
       +        for date, direction, htlc, hex_preimage, hex_chan_id in self.completed.values():
                    direction = Direction(direction)
                    if chan_id is not None:
                        if bfh(hex_chan_id) != chan_id:
   DIR diff --git a/electrum/paymentrequest.py b/electrum/paymentrequest.py
       t@@ -41,6 +41,7 @@ except ImportError:
        
        from . import bitcoin, ecc, util, transaction, x509, rsakey
        from .util import bh2u, bfh, export_meta, import_meta, make_aiohttp_session
       +from .util import PR_UNPAID, PR_EXPIRED, PR_PAID, PR_UNKNOWN, PR_INFLIGHT
        from .crypto import sha256
        from .bitcoin import TYPE_ADDRESS
        from .transaction import TxOutput
       t@@ -65,12 +66,6 @@ def load_ca_list():
        
        
        
       -# status of payment requests
       -PR_UNPAID  = 0
       -PR_EXPIRED = 1
       -PR_UNKNOWN = 2     # sent but not propagated
       -PR_PAID    = 3     # send and propagated
       -
        
        async def get_payment_request(url: str) -> 'PaymentRequest':
            u = urllib.parse.urlparse(url)
   DIR diff --git a/electrum/util.py b/electrum/util.py
       t@@ -73,6 +73,13 @@ base_units_list = ['BTC', 'mBTC', 'bits', 'sat']  # list(dict) does not guarante
        
        DECIMAL_POINT_DEFAULT = 5  # mBTC
        
       +# status of payment requests
       +PR_UNPAID  = 0
       +PR_EXPIRED = 1
       +PR_UNKNOWN = 2     # sent but not propagated
       +PR_PAID    = 3     # send and propagated
       +PR_INFLIGHT = 4    # lightning
       +
        
        class UnknownBaseUnit(Exception): pass