URI: 
       tlightning: * store invoices for both directions * do not store lightning_payments_inflight, lightning_payments_completed in lnworker * payment history is returned by get_payments method of LNChannel * command line: lightning history, lightning_invoices * re-enable push_msat - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 0e8dba897eeabf9a8b19194233ad2a9a8246a2cc
   DIR parent d80b709aa4dbb8a07e04afeae171829245224de4
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Tue, 29 Jan 2019 19:01:04 +0100
       
       lightning:
       * store invoices for both directions
       * do not store lightning_payments_inflight, lightning_payments_completed in lnworker
       * payment history is returned by get_payments method of LNChannel
       * command line: lightning history, lightning_invoices
       * re-enable push_msat
       
       Diffstat:
         M electrum/commands.py                |      77 ++++++++++++++-----------------
         M electrum/gui/qt/channel_details.py  |      44 ++++++++++++++++----------------
         M electrum/gui/qt/main_window.py      |       4 +++-
         M electrum/gui/qt/request_list.py     |      11 +++++++----
         M electrum/gui/qt/util.py             |       6 ------
         M electrum/lnbase.py                  |       4 ++--
         M electrum/lnchan.py                  |      11 +++++++++++
         M electrum/lnworker.py                |     106 ++++++++++---------------------
         M electrum/util.py                    |       8 ++++++++
       
       9 files changed, 121 insertions(+), 150 deletions(-)
       ---
   DIR diff --git a/electrum/commands.py b/electrum/commands.py
       t@@ -47,6 +47,7 @@ from .wallet import Abstract_Wallet, create_new_wallet, restore_wallet_from_text
        from .address_synchronizer import TX_HEIGHT_LOCAL
        from .import lightning
        from .mnemonic import Mnemonic
       +from .lnutil import SENT, RECEIVED
        
        if TYPE_CHECKING:
            from .network import Network
       t@@ -108,6 +109,8 @@ class Commands:
                self.wallet = wallet
                self.network = network
                self._callback = callback
       +        if self.wallet:
       +            self.lnworker = self.wallet.lnworker
        
            def _run(self, method, args, password_getter, **kwargs):
                """This wrapper is called from the Qt python console."""
       t@@ -766,33 +769,33 @@ class Commands:
            # lightning network commands
            @command('wpn')
            def open_channel(self, connection_string, amount, channel_push=0, password=None):
       -        return self.wallet.lnworker.open_channel(connection_string, satoshis(amount), satoshis(channel_push), password)
       +        return self.lnworker.open_channel(connection_string, satoshis(amount), satoshis(channel_push), password)
        
            @command('wn')
            def reestablish_channel(self):
       -        self.wallet.lnworker.reestablish_channel()
       +        self.lnworker.reestablish_channel()
        
            @command('wn')
            def lnpay(self, invoice):
       -        addr, peer, f = self.wallet.lnworker.pay(invoice)
       +        addr, peer, f = self.lnworker.pay(invoice)
                return f.result()
        
            @command('wn')
            def addinvoice(self, requested_amount, message):
                # using requested_amount because it is documented in param_descriptions
       -        return self.wallet.lnworker.add_invoice(satoshis(requested_amount), message)
       +        return self.lnworker.add_invoice(satoshis(requested_amount), message)
        
            @command('wn')
            def nodeid(self):
       -        return bh2u(self.wallet.lnworker.node_keypair.pubkey)
       +        return bh2u(self.lnworker.node_keypair.pubkey)
        
            @command('w')
            def listchannels(self):
       -        return list(self.wallet.lnworker.list_channels())
       +        return list(self.lnworker.list_channels())
        
            @command('wn')
            def dumpgraph(self):
       -        return list(map(bh2u, self.wallet.lnworker.channel_db.nodes.keys()))
       +        return list(map(bh2u, self.lnworker.channel_db.nodes.keys()))
        
            @command('n')
            def inject_fees(self, fees):
       t@@ -805,47 +808,35 @@ class Commands:
                self.network.path_finder.blacklist.clear()
        
            @command('w')
       -    def listinvoices(self):
       -        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 ''
       +    def lightning_invoices(self):
       +        from .util import pr_tooltips
       +        out = []
       +        for payment_hash, (preimage, pay_req, direction, pay_timestamp) in self.lnworker.invoices.items():
       +            status = pr_tooltips[self.lnworker.get_invoice_status(payment_hash)]
       +            out.append({'payment_hash':payment_hash, 'invoice':pay_req, 'preimage':preimage, 'status':status, 'direction':direction})
       +        return out
        
       +    @command('w')
       +    def lightning_history(self):
       +        out = []
       +        for chan_id, htlc, direction, status in self.lnworker.get_payments().values():
       +            item = {
       +                'direction': 'sent' if direction == SENT else 'received',
       +                'status':status,
       +                'amout_msat':htlc.amount_msat,
       +                'payment_hash':bh2u(htlc.payment_hash),
       +                'chan_id':bh2u(chan_id),
       +                'htlc_id':htlc.htlc_id,
       +                'cltv_expiry':htlc.cltv_expiry
       +            }
       +            out.append(item)
       +        return out
        
            @command('wn')
            def closechannel(self, channel_point, force=False):
                chan_id = bytes(reversed(bfh(channel_point)))
       -        if force:
       -            return self.network.run_from_another_thread(self.wallet.lnworker.force_close_channel(chan_id))
       -        else:
       -            return self.network.run_from_another_thread(self.wallet.lnworker.close_channel(chan_id))
       +        coro = self.lnworker.force_close_channel(chan_id) if force else self.lnworker.force_close_channel(chan_id)
       +        return self.network.run_from_another_thread(coro)
        
        def eval_bool(x: str) -> bool:
            if x == 'false': return False
   DIR diff --git a/electrum/gui/qt/channel_details.py b/electrum/gui/qt/channel_details.py
       t@@ -56,11 +56,8 @@ class ChannelDetailsDialog(QtWidgets.QDialog):
                parentItem = model.invisibleRootItem()
                folder_types = {'settled': _('Fulfilled HTLCs'), 'inflight': _('HTLCs in current commitment transaction')}
                self.folders = {}
       -
                self.keyname_rows = {}
        
       -        invoices = dict(self.window.wallet.lnworker.invoices)
       -
                for keyname, i in folder_types.items():
                    myFont=QtGui.QFont()
                    myFont.setBold(True)
       t@@ -70,23 +67,26 @@ class ChannelDetailsDialog(QtWidgets.QDialog):
                    self.folders[keyname] = folder
                    mapping = {}
                    num = 0
       -            if keyname == 'inflight':
       -                for lnaddr, i, direction in htlcs[keyname]:
       -                    it = self.make_inflight(lnaddr, i, direction)
       -                    self.folders[keyname].appendRow(it)
       -                    mapping[i.payment_hash] = num
       -                    num += 1
       -            elif keyname == 'settled':
       -                for date, direction, i, preimage in htlcs[keyname]:
       -                    it = self.make_htlc_item(i, direction)
       -                    hex_pay_hash = bh2u(i.payment_hash)
       -                    if hex_pay_hash in invoices:
       -                        # if we made the invoice and still have it, we can show more info
       -                        invoice = invoices[hex_pay_hash][1]
       -                        self.append_lnaddr(it, lndecode(invoice))
       -                    self.folders[keyname].appendRow(it)
       -                    mapping[i.payment_hash] = num
       -                    num += 1
       +
       +        invoices = dict(self.window.wallet.lnworker.invoices)
       +        for pay_hash, item in htlcs.items():
       +            chan_id, i, direction, status = item
       +            if pay_hash in invoices:
       +                preimage, invoice, direction, timestamp = invoices[pay_hash]
       +                lnaddr = lndecode(invoice)
       +            if status == 'inflight':
       +                it = self.make_inflight(lnaddr, i, direction)
       +                self.folders['inflight'].appendRow(it)
       +                mapping[i.payment_hash] = num
       +                num += 1
       +            elif status == 'settled':
       +                it = self.make_htlc_item(i, direction)
       +                # if we made the invoice and still have it, we can show more info
       +                if pay_hash in invoices:
       +                    self.append_lnaddr(it, lndecode(invoice))
       +                self.folders['settled'].appendRow(it)
       +                mapping[i.payment_hash] = num
       +                num += 1
        
                    self.keyname_rows[keyname] = mapping
                return model
       t@@ -171,8 +171,8 @@ class ChannelDetailsDialog(QtWidgets.QDialog):
                # add htlc tree view to vbox (wouldn't scale correctly in QFormLayout)
                form_layout.addRow(_('Payments (HTLCs):'), None)
                w = QtWidgets.QTreeView(self)
       -        htlcs = window.wallet.lnworker._list_invoices(chan_id)
       -        w.setModel(self.make_model(htlcs))
       +        htlc_dict = chan.get_payments()
       +        w.setModel(self.make_model(htlc_dict))
                w.header().setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents)
                vbox.addWidget(w)
        
   DIR diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
       t@@ -62,7 +62,7 @@ from electrum.util import (format_time, format_satoshis, format_fee_satoshis,
                                   UnknownBaseUnit, DECIMAL_POINT_DEFAULT, UserFacingException,
                                   get_new_wallet_name, send_exception_to_crash_reporter,
                                   InvalidBitcoinURI, InvoiceError)
       -from electrum.lnutil import PaymentFailure
       +from electrum.lnutil import PaymentFailure, SENT, RECEIVED
        from electrum.transaction import Transaction, TxOutput
        from electrum.address_synchronizer import AddTransactionException
        from electrum.wallet import (Multisig_Wallet, CannotBumpFee, Abstract_Wallet,
       t@@ -1941,6 +1941,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                #self.amount_e.textEdited.emit("")
                self.payto_e.is_lightning = True
                self.show_send_tab_onchain_fees(False)
       +        # save
       +        self.wallet.lnworker.save_invoice(None, invoice, SENT)
        
            def show_send_tab_onchain_fees(self, b: bool):
                self.feecontrol_fields.setVisible(b)
   DIR diff --git a/electrum/gui/qt/request_list.py b/electrum/gui/qt/request_list.py
       t@@ -31,7 +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.util import PR_UNPAID, PR_EXPIRED, PR_PAID, PR_UNKNOWN, PR_INFLIGHT, pr_tooltips
       +from electrum.lnutil import SENT, RECEIVED
        from electrum.plugin import run_hook
        from electrum.wallet import InternalAddressCorruption
        from electrum.bitcoin import COIN
       t@@ -95,7 +96,7 @@ class RequestList(MyTreeView):
                        return
                    req = self.parent.get_request_URI(key)
                elif request_type == REQUEST_TYPE_LN:
       -            preimage, req = self.wallet.lnworker.invoices.get(key, (None, None))
       +            preimage, req, direction, pay_timestamp = self.wallet.lnworker.invoices.get(key, (None, None, None))
                    if req is None:
                        self.update()
                        return
       t@@ -145,7 +146,9 @@ class RequestList(MyTreeView):
                self.filter()
                # lightning
                lnworker = self.wallet.lnworker
       -        for key, (preimage_hex, invoice) in lnworker.invoices.items():
       +        for key, (preimage_hex, invoice, direction, pay_timestamp) in lnworker.invoices.items():
       +            if direction == SENT:
       +                continue
                    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
       t@@ -181,7 +184,7 @@ class RequestList(MyTreeView):
                if request_type == REQUEST_TYPE_BITCOIN:
                    req = self.wallet.receive_requests.get(addr)
                elif request_type == REQUEST_TYPE_LN:
       -            preimage, req = self.wallet.lnworker.invoices.get(addr)
       +            preimage, req, direction, pay_timestamp = self.wallet.lnworker.invoices.get(addr)
                if req is None:
                    self.update()
                    return
   DIR diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py
       t@@ -47,12 +47,6 @@ pr_icons = {
            PR_INFLIGHT:"lightning.png",
        }
        
       -pr_tooltips = {
       -    PR_UNPAID:_('Pending'),
       -    PR_PAID:_('Paid'),
       -    PR_EXPIRED:_('Expired'),
       -    PR_INFLIGHT:_('Inflight')
       -}
        
        expiration_values = [
            (_('1 hour'), 60*60),
   DIR diff --git a/electrum/lnbase.py b/electrum/lnbase.py
       t@@ -420,7 +420,7 @@ class Peer(PrintError):
            @log_exceptions
            async def channel_establishment_flow(self, password: Optional[str], funding_sat: int,
                                                 push_msat: int, temp_channel_id: bytes) -> Channel:
       -        assert push_msat == 0, "push_msat not supported currently"
       +        #assert push_msat == 0, "push_msat not supported currently"
                wallet = self.lnworker.wallet
                # dry run creating funding tx to see if we even have enough funds
                funding_tx_test = wallet.mktx([TxOutput(bitcoin.TYPE_ADDRESS, wallet.dummy_address(), funding_sat)],
       t@@ -549,7 +549,7 @@ class Peer(PrintError):
                    raise Exception('wrong chain_hash')
                funding_sat = int.from_bytes(payload['funding_satoshis'], 'big')
                push_msat = int.from_bytes(payload['push_msat'], 'big')
       -        assert push_msat == 0, "push_msat not supported currently"
       +        #assert push_msat == 0, "push_msat not supported currently"
                feerate = int.from_bytes(payload['feerate_per_kw'], 'big')
        
                temp_chan_id = payload['temporary_channel_id']
   DIR diff --git a/electrum/lnchan.py b/electrum/lnchan.py
       t@@ -171,6 +171,17 @@ class Channel(PrintError):
                self.local_commitment = None
                self.remote_commitment = None
        
       +    def get_payments(self):
       +        out = {}
       +        for subject in LOCAL, REMOTE:
       +            log = self.hm.log[subject]
       +            for htlc_id, htlc in log.get('adds', {}).items():
       +                rhash = bh2u(htlc.payment_hash)
       +                status = 'settled' if htlc_id in log.get('settles',{}) else 'inflight'
       +                direction = SENT if subject is LOCAL else RECEIVED
       +                out[rhash] = (self.channel_id, htlc, direction, status)
       +        return out
       +
            def set_local_commitment(self, ctx):
                ctn = extract_ctn_from_tx_and_chan(ctx, self)
                assert self.signature_fits(ctx), (self.log[LOCAL])
   DIR diff --git a/electrum/lnworker.py b/electrum/lnworker.py
       t@@ -19,6 +19,7 @@ import dns.exception
        
        from . import constants
        from . import keystore
       +from .util import PR_UNPAID, PR_EXPIRED, PR_PAID, PR_UNKNOWN, PR_INFLIGHT
        from .keystore import BIP32_KeyStore
        from .bitcoin import COIN
        from .transaction import Transaction
       t@@ -66,10 +67,7 @@ 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.inflight = 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.invoices = self.wallet.storage.get('lightning_invoices', {})  # type: Dict[str, Tuple[str,str]]  # RHASH -> (preimage, invoice, direction, pay_timestamp)
                self.sweep_address = wallet.get_receiving_address()
                self.lock = threading.RLock()
                self.ln_keystore = self._read_ln_keystore()
       t@@ -122,73 +120,34 @@ class LNWorker(PrintError):
                    self.wallet.storage.write()
                    self.print_error('saved lightning gossip timestamp')
        
       -    def payment_completed(self, chan, direction, htlc, preimage):
       -        assert type(direction) is Direction
       -        key = bh2u(htlc.payment_hash)
       +    def payment_completed(self, chan, direction, htlc, _preimage):
                chan_id = chan.channel_id
       +        key = bh2u(htlc.payment_hash)
       +        if key not in self.invoices:
       +            return
       +        preimage, invoice, direction, timestamp = self.invoices.get(key)
                if direction == SENT:
       -            assert htlc.payment_hash not in self.invoices
       -            self.inflight.pop(key)
       -            self.wallet.storage.put('lightning_payments_inflight', self.inflight)
       -        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))
       -        self.completed[key] = tupl
       -        self.wallet.storage.put('lightning_payments_completed', self.completed)
       +            preimage = _preimage
       +        now = time.time()
       +        self.invoices[key] = preimage, invoice, direction, now
       +        self.wallet.storage.put('lightning_invoices', self.invoices)
                self.wallet.storage.write()
       -        self.network.trigger_callback('ln_payment_completed', tupl[0], direction, htlc, preimage, chan_id)
       -
       -    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.inflight:
       -            return PR_INFLIGHT
       -        elif key in self.invoices:
       -            return PR_UNPAID
       -        else:
       +        self.network.trigger_callback('ln_payment_completed', now, direction, htlc, preimage, chan_id)
       +
       +    def get_invoice_status(self, payment_hash):
       +        if payment_hash not in self.invoices:
                    return PR_UNKNOWN
       +        preimage, _addr, direction, timestamp = self.invoices.get(payment_hash)
       +        if timestamp is None:
       +            return PR_UNPAID
       +        return PR_PAID
        
       -    def _list_invoices(self, chan_id=None):
       -        invoices  = dict(self.invoices)
       -        settled = []
       -        unsettled = []
       -        inflight = []
       -        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:
       -                    continue
       -            htlcobj = UpdateAddHtlc(*htlc)
       -            if direction == RECEIVED:
       -                preimage = bfh(invoices.pop(bh2u(htlcobj.payment_hash))[0])
       -            else:
       -                preimage = bfh(hex_preimage)
       -            # FIXME use fromisoformat when minimum Python is 3.7
       -            settled.append((datetime.fromtimestamp(date, timezone.utc), direction, htlcobj, preimage))
       -        for preimage, pay_req in invoices.values():
       -            addr = lndecode(pay_req, expected_hrp=constants.net.SEGWIT_HRP)
       -            unsettled.append((addr, bfh(preimage), pay_req))
       -        for pay_req, amount_sat, this_chan_id in self.inflight.values():
       -            if chan_id is not None and bfh(this_chan_id) != chan_id:
       -                continue
       -            addr = lndecode(pay_req, expected_hrp=constants.net.SEGWIT_HRP)
       -            if amount_sat is not None:
       -                addr.amount = Decimal(amount_sat) / COIN
       -            htlc = self.find_htlc_for_addr(addr, None if chan_id is None else [chan_id])
       -            if not htlc:
       -                self.print_error('Warning, in-flight HTLC not found in any channel')
       -            inflight.append((addr, htlc, SENT))
       -        # not adding received htlcs to inflight because they should have been settled
       -        # immediatly and therefore let's not spend time trying to show it in the GUI
       -        return {'settled': settled, 'unsettled': unsettled, 'inflight': inflight}
       -
       -    def find_htlc_for_addr(self, addr, whitelist=None):
       -        channels = [y for x,y in self.channels.items() if whitelist is None or x in whitelist]
       -        for chan in channels:
       -            for htlc in chan.hm.log[LOCAL]['adds'].values():
       -                if htlc.payment_hash == addr.paymenthash:
       -                    return htlc
       +    def get_payments(self):
       +        # note: with AMP we will have several channels per payment
       +        out = {}
       +        for chan in self.channels.values():
       +            out.update(chan.get_payments())
       +        return out
        
            def _read_ln_keystore(self) -> BIP32_KeyStore:
                xprv = self.wallet.storage.get('lightning_privkey2')
       t@@ -447,9 +406,6 @@ class LNWorker(PrintError):
                        break
                else:
                    assert False, 'Found route with short channel ID we don\'t have: ' + repr(route[0].short_channel_id)
       -        self.inflight[bh2u(addr.paymenthash)] = (invoice, amount_sat, bh2u(chan_id))
       -        self.wallet.storage.put('lightning_payments_inflight', self.inflight)
       -        self.wallet.storage.write()
                return addr, peer, self._pay_to_route(route, addr)
        
            async def _pay_to_route(self, route, addr):
       t@@ -545,14 +501,20 @@ class LNWorker(PrintError):
                                                ('c', MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE)]
                                               + routing_hints),
                                   self.node_keypair.privkey)
       -        self.invoices[bh2u(RHASH)] = (bh2u(payment_preimage), pay_req)
       +
       +        self.save_invoice(bh2u(payment_preimage), pay_req, RECEIVED)
       +        return pay_req
       +
       +    def save_invoice(self, preimage, invoice, direction):
       +        lnaddr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
       +        key = bh2u(lnaddr.paymenthash)
       +        self.invoices[key] = preimage, invoice, direction, None
                self.wallet.storage.put('lightning_invoices', self.invoices)
                self.wallet.storage.write()
       -        return pay_req
        
            def get_invoice(self, payment_hash: bytes) -> Tuple[bytes, LnAddr]:
                try:
       -            preimage_hex, pay_req = self.invoices[bh2u(payment_hash)]
       +            preimage_hex, pay_req, direction,timestamp = self.invoices[bh2u(payment_hash)]
                    preimage = bfh(preimage_hex)
                    assert sha256(preimage) == payment_hash
                    return preimage, lndecode(pay_req, expected_hrp=constants.net.SEGWIT_HRP)
   DIR diff --git a/electrum/util.py b/electrum/util.py
       t@@ -80,6 +80,14 @@ PR_UNKNOWN = 2     # sent but not propagated
        PR_PAID    = 3     # send and propagated
        PR_INFLIGHT = 4    # lightning
        
       +pr_tooltips = {
       +    PR_UNPAID:_('Pending'),
       +    PR_PAID:_('Paid'),
       +    PR_UNKNOWN:_('Unknown'),
       +    PR_EXPIRED:_('Expired'),
       +    PR_INFLIGHT:_('Inflight')
       +}
       +
        
        class UnknownBaseUnit(Exception): pass