URI: 
       tlightning: Save invoices and preimages separately. Save preimages when forwarding - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 62be0c481c9fcdd26eb4b7c4a2c72eea8cec9b92
   DIR parent e475617b7566556b337ccdc12fca64be57787453
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Wed, 13 Feb 2019 15:46:35 +0100
       
       lightning: Save invoices and preimages separately. Save preimages when forwarding
       
       Diffstat:
         M electrum/gui/qt/channel_details.py  |       2 +-
         M electrum/gui/qt/invoice_list.py     |       2 +-
         M electrum/gui/qt/request_list.py     |       2 +-
         M electrum/lnpeer.py                  |       7 ++++---
         M electrum/lnsweep.py                 |       4 ++--
         M electrum/lnworker.py                |     108 +++++++++++++++++--------------
         M electrum/tests/test_lnpeer.py       |       6 +++++-
       
       7 files changed, 72 insertions(+), 59 deletions(-)
       ---
   DIR diff --git a/electrum/gui/qt/channel_details.py b/electrum/gui/qt/channel_details.py
       t@@ -73,7 +73,7 @@ class ChannelDetailsDialog(QtWidgets.QDialog):
                    chan_id, i, direction, status = item
                    lnaddr = None
                    if pay_hash in invoices:
       -                invoice = invoices[pay_hash][1]
       +                invoice = invoices[pay_hash][0]
                        lnaddr = lndecode(invoice)
                    if status == 'inflight':
                        if lnaddr is not None:
   DIR diff --git a/electrum/gui/qt/invoice_list.py b/electrum/gui/qt/invoice_list.py
       t@@ -84,7 +84,7 @@ class InvoiceList(MyTreeView):
                    self.model().insertRow(idx, items)
        
                lnworker = self.parent.wallet.lnworker
       -        for key, (preimage_hex, invoice, is_received, pay_timestamp) in lnworker.invoices.items():
       +        for key, (invoice, is_received) in lnworker.invoices.items():
                    if is_received:
                        continue
                    status = lnworker.get_invoice_status(key)
   DIR diff --git a/electrum/gui/qt/request_list.py b/electrum/gui/qt/request_list.py
       t@@ -134,7 +134,7 @@ class RequestList(MyTreeView):
                self.filter()
                # lightning
                lnworker = self.wallet.lnworker
       -        for key, (preimage_hex, invoice, is_received, pay_timestamp) in lnworker.invoices.items():
       +        for key, (invoice, is_received) in lnworker.invoices.items():
                    if not is_received:
                        continue
                    status = lnworker.get_invoice_status(key)
   DIR diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py
       t@@ -377,7 +377,7 @@ class Peer(PrintError):
                               sweep_address=self.lnworker.sweep_address,
                               payment_completed=self.lnworker.payment_completed)
                chan.lnwatcher = self.lnwatcher
       -        chan.get_preimage_and_invoice = self.lnworker.get_invoice  # FIXME hack.
       +        chan.get_preimage = self.lnworker.get_preimage  # FIXME hack.
                sig_64, _ = chan.sign_next_commitment()
                self.send_message("funding_created",
                    temporary_channel_id=temp_channel_id,
       t@@ -470,7 +470,7 @@ class Peer(PrintError):
                               sweep_address=self.lnworker.sweep_address,
                               payment_completed=self.lnworker.payment_completed)
                chan.lnwatcher = self.lnwatcher
       -        chan.get_preimage_and_invoice = self.lnworker.get_invoice  # FIXME hack.
       +        chan.get_preimage = self.lnworker.get_preimage  # FIXME hack.
                remote_sig = funding_created['signature']
                chan.receive_new_commitment(remote_sig, [])
                sig_64, _ = chan.sign_next_commitment()
       t@@ -975,7 +975,8 @@ class Peer(PrintError):
                await self.await_local(chan, local_ctn)
                await self.await_remote(chan, remote_ctn)
                try:
       -            preimage, invoice = self.lnworker.get_invoice(payment_hash)
       +            invoice = self.lnworker.get_invoice(payment_hash)
       +            preimage = self.lnworker.get_preimage(payment_hash)
                except UnknownPaymentHash:
                    reason = OnionRoutingFailureMessage(code=OnionFailureCode.UNKNOWN_PAYMENT_HASH, data=b'')
                    await self.fail_htlc(chan, htlc_id, onion_packet, reason)
   DIR diff --git a/electrum/lnsweep.py b/electrum/lnsweep.py
       t@@ -157,7 +157,7 @@ def create_sweeptxs_for_our_latest_ctx(chan: 'Channel', ctx: Transaction,
            def create_txns_for_htlc(htlc: 'UpdateAddHtlc', is_received_htlc: bool) -> Tuple[Optional[Transaction], Optional[Transaction]]:
                if is_received_htlc:
                    try:
       -                preimage, invoice = chan.get_preimage_and_invoice(htlc.payment_hash)
       +                preimage = chan.get_preimage(htlc.payment_hash)
                    except UnknownPaymentHash as e:
                        print_error(f'trying to sweep htlc from our latest ctx but getting {repr(e)}')
                        return None, None
       t@@ -260,7 +260,7 @@ def create_sweeptxs_for_their_latest_ctx(chan: 'Channel', ctx: Transaction,
            def create_sweeptx_for_htlc(htlc: 'UpdateAddHtlc', is_received_htlc: bool) -> Optional[Transaction]:
                if not is_received_htlc:
                    try:
       -                preimage, invoice = chan.get_preimage_and_invoice(htlc.payment_hash)
       +                preimage = chan.get_preimage(htlc.payment_hash)
                    except UnknownPaymentHash as e:
                        print_error(f'trying to sweep htlc from their latest ctx but getting {repr(e)}')
                        return None
   DIR diff --git a/electrum/lnworker.py b/electrum/lnworker.py
       t@@ -68,8 +68,9 @@ class LNWorker(PrintError):
        
            def __init__(self, wallet: 'Abstract_Wallet'):
                self.wallet = wallet
       -        # type: Dict[str, Tuple[str,str,bool,int]]  # RHASH -> (preimage, invoice, is_received, timestamp)
       -        self.invoices = self.wallet.storage.get('lightning_invoices', {})
       +        self.storage = wallet.storage
       +        self.invoices = self.storage.get('lightning_invoices', {})        # RHASH -> (invoice, is_received)
       +        self.preimages = self.storage.get('lightning_preimages', {})      # RHASH -> (preimage, timestamp)
                self.sweep_address = wallet.get_receiving_address()
                self.lock = threading.RLock()
                self.ln_keystore = self._read_ln_keystore()
       t@@ -78,12 +79,12 @@ class LNWorker(PrintError):
                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)
       -            c.get_preimage_and_invoice = self.get_invoice
       +            c.get_preimage = self.get_preimage
                    self.channels[c.channel_id] = c
                    c.set_remote_commitment()
                    c.set_local_commitment(c.current_commitment(LOCAL))
                # timestamps of opening and closing transactions
       -        self.channel_timestamps = self.wallet.storage.get('lightning_channel_timestamps', {})
       +        self.channel_timestamps = self.storage.get('lightning_channel_timestamps', {})
        
            def start_network(self, network: 'Network'):
                self.network = network
       t@@ -106,7 +107,7 @@ class LNWorker(PrintError):
                if self.first_timestamp_requested is None:
                    self.first_timestamp_requested = time.time()
                    first_request = True
       -        first_timestamp = self.wallet.storage.get('lightning_gossip_until', 0)
       +        first_timestamp = self.storage.get('lightning_gossip_until', 0)
                if first_timestamp == 0:
                    self.print_error('requesting whole channel graph')
                else:
       t@@ -120,28 +121,21 @@ class LNWorker(PrintError):
                while True:
                    await asyncio.sleep(GRAPH_DOWNLOAD_SECONDS)
                    yesterday = int(time.time()) - 24*60*60 # now minus a day
       -            self.wallet.storage.put('lightning_gossip_until', yesterday)
       -            self.wallet.storage.write()
       +            self.storage.put('lightning_gossip_until', yesterday)
       +            self.storage.write()
                    self.print_error('saved lightning gossip timestamp')
        
            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, is_received, timestamp = self.invoices.get(key)
       -        if direction == SENT:
       -            preimage = bh2u(_preimage)
       -        now = time.time()
       -        self.invoices[key] = preimage, invoice, is_received, now
       -        self.wallet.storage.put('lightning_invoices', self.invoices)
       -        self.wallet.storage.write()
       -        self.network.trigger_callback('ln_payment_completed', now, direction, htlc, preimage, chan_id)
       +        preimage = _preimage if _preimage else self.get_preimage(htlc.payment_hash)
       +        timestamp = time.time()
       +        self.save_preimage(htlc.payment_hash, preimage, timestamp)
       +        self.network.trigger_callback('ln_payment_completed', timestamp, direction, htlc, preimage, chan_id)
        
            def get_invoice_status(self, payment_hash):
       -        if payment_hash not in self.invoices:
       +        if payment_hash not in self.preimages:
                    return PR_UNKNOWN
       -        preimage, _addr, is_received, timestamp = self.invoices.get(payment_hash)
       +        preimage, timestamp = self.preimages.get(payment_hash)
                if timestamp is None:
                    return PR_UNPAID
                return PR_PAID
       t@@ -157,7 +151,7 @@ class LNWorker(PrintError):
                out = []
                for chan_id, htlc, direction, status in self.get_payments().values():
                    key = bh2u(htlc.payment_hash)
       -            timestamp = self.invoices[key][3] if key in self.invoices else None
       +            timestamp = self.preimages[key][1] if key in self.preimages else None
                    item = {
                        'type':'payment',
                        'timestamp':timestamp or 0,
       t@@ -205,21 +199,21 @@ class LNWorker(PrintError):
                return out
        
            def _read_ln_keystore(self) -> BIP32_KeyStore:
       -        xprv = self.wallet.storage.get('lightning_privkey2')
       +        xprv = self.storage.get('lightning_privkey2')
                if xprv is None:
                    # TODO derive this deterministically from wallet.keystore at keystore generation time
                    # probably along a hardened path ( lnd-equivalent would be m/1017'/coinType'/ )
                    seed = os.urandom(32)
                    xprv, xpub = bip32_root(seed, xtype='standard')
       -            self.wallet.storage.put('lightning_privkey2', xprv)
       +            self.storage.put('lightning_privkey2', xprv)
                return keystore.from_xprv(xprv)
        
            def get_and_inc_counter_for_channel_keys(self):
                with self.lock:
       -            ctr = self.wallet.storage.get('lightning_channel_key_der_ctr', -1)
       +            ctr = self.storage.get('lightning_channel_key_der_ctr', -1)
                    ctr += 1
       -            self.wallet.storage.put('lightning_channel_key_der_ctr', ctr)
       -            self.wallet.storage.write()
       +            self.storage.put('lightning_channel_key_der_ctr', ctr)
       +            self.storage.write()
                    return ctr
        
            def _add_peers_from_config(self):
       t@@ -264,8 +258,8 @@ class LNWorker(PrintError):
                with self.lock:
                    self.channels[openchannel.channel_id] = openchannel
                    dumped = [x.serialize() for x in self.channels.values()]
       -        self.wallet.storage.put("channels", dumped)
       -        self.wallet.storage.write()
       +        self.storage.put("channels", dumped)
       +        self.storage.write()
                self.network.trigger_callback('channel', openchannel)
        
            def save_short_chan_id(self, chan):
       t@@ -300,7 +294,7 @@ class LNWorker(PrintError):
                    return
                self.print_error('on_channel_open', funding_outpoint)
                self.channel_timestamps[bh2u(chan.channel_id)] = funding_txid, funding_height.height, funding_height.timestamp, None, None, None
       -        self.wallet.storage.put('lightning_channel_timestamps', self.channel_timestamps)
       +        self.storage.put('lightning_channel_timestamps', self.channel_timestamps)
                chan.set_funding_txo_spentness(False)
                # send event to GUI
                self.network.trigger_callback('channel', chan)
       t@@ -312,7 +306,7 @@ class LNWorker(PrintError):
                    return
                self.print_error('on_channel_closed', funding_outpoint)
                self.channel_timestamps[bh2u(chan.channel_id)] = funding_txid, funding_height.height, funding_height.timestamp, closing_txid, closing_height.height, closing_height.timestamp
       -        self.wallet.storage.put('lightning_channel_timestamps', self.channel_timestamps)
       +        self.storage.put('lightning_channel_timestamps', self.channel_timestamps)
                chan.set_funding_txo_spentness(True)
                if chan.get_state() != 'FORCE_CLOSING':
                    chan.set_state("CLOSED")
       t@@ -473,7 +467,7 @@ class LNWorker(PrintError):
                if not chan:
                    raise Exception("PathFinder returned path with short_channel_id {} that is not in channel list".format(bh2u(short_channel_id)))
                peer = self.peers[route[0].node_id]
       -        self.save_invoice(None, pay_req, SENT)
       +        self.save_invoice(addr.paymenthash, pay_req, SENT)
                htlc = await peer.pay(route, chan, int(addr.amount * COIN * 1000), addr.paymenthash, addr.get_min_final_cltv_expiry())
                self.network.trigger_callback('htlc_added', htlc, addr, SENT)
        
       t@@ -546,34 +540,50 @@ class LNWorker(PrintError):
        
            def add_invoice(self, amount_sat, message):
                payment_preimage = os.urandom(32)
       -        RHASH = sha256(payment_preimage)
       +        payment_hash = sha256(payment_preimage)
                amount_btc = amount_sat/Decimal(COIN) if amount_sat else None
                routing_hints = self._calc_routing_hints_for_invoice(amount_sat)
                if not routing_hints:
                    self.print_error("Warning. No routing hints added to invoice. "
                                     "Other clients will likely not be able to send to us.")
       -        pay_req = lnencode(LnAddr(RHASH, amount_btc,
       +        invoice = lnencode(LnAddr(payment_hash, amount_btc,
                                          tags=[('d', message),
                                                ('c', MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE)]
                                               + routing_hints),
                                   self.node_keypair.privkey)
       +        self.save_invoice(payment_hash, invoice, RECEIVED)
       +        self.save_preimage(payment_hash, payment_preimage, 0)
       +        return invoice
       +
       +    def save_preimage(self, payment_hash:bytes, preimage:bytes, timestamp:int):
       +        assert sha256(preimage) == payment_hash
       +        key = bh2u(payment_hash)
       +        self.preimages[key] = bh2u(preimage), timestamp
       +        self.storage.put('lightning_preimages', self.preimages)
       +        self.storage.write()
       +
       +    def get_preimage_and_timestamp(self, payment_hash: bytes) -> bytes:
       +        try:
       +            preimage_hex, timestamp = self.preimages[bh2u(payment_hash)]
       +            preimage = bfh(preimage_hex)
       +            assert sha256(preimage) == payment_hash
       +            return preimage, timestamp
       +        except KeyError as e:
       +            raise UnknownPaymentHash(payment_hash) from e
        
       -        self.save_invoice(bh2u(payment_preimage), pay_req, RECEIVED)
       -        return pay_req
       +    def get_preimage(self, payment_hash: bytes) -> bytes:
       +        return self.get_preimage_and_timestamp(payment_hash)[0]
        
       -    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==RECEIVED, None
       -        self.wallet.storage.put('lightning_invoices', self.invoices)
       -        self.wallet.storage.write()
       +    def save_invoice(self, payment_hash:bytes, invoice, direction):
       +        key = bh2u(payment_hash)
       +        self.invoices[key] = invoice, direction==RECEIVED
       +        self.storage.put('lightning_invoices', self.invoices)
       +        self.storage.write()
        
       -    def get_invoice(self, payment_hash: bytes) -> Tuple[bytes, LnAddr]:
       +    def get_invoice(self, payment_hash: bytes) -> LnAddr:
                try:
       -            preimage_hex, pay_req, is_received, 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)
       +            invoice, is_received = self.invoices[bh2u(payment_hash)]
       +            return lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
                except KeyError as e:
                    raise UnknownPaymentHash(payment_hash) from e
        
       t@@ -618,14 +628,12 @@ class LNWorker(PrintError):
                return routing_hints
        
            def delete_invoice(self, payment_hash_hex: str):
       -        # FIXME we will now LOSE the preimage!! is this feature a good idea?
       -        # maybe instead of deleting, we could have a feature to "hide" invoices (e.g. for GUI)
                try:
                    del self.invoices[payment_hash_hex]
                except KeyError:
                    return
       -        self.wallet.storage.put('lightning_invoices', self.invoices)
       -        self.wallet.storage.write()
       +        self.storage.put('lightning_invoices', self.invoices)
       +        self.storage.write()
        
            def get_balance(self):
                with self.lock:
   DIR diff --git a/electrum/tests/test_lnpeer.py b/electrum/tests/test_lnpeer.py
       t@@ -82,6 +82,7 @@ class MockLNWorker:
                self.network = MockNetwork(tx_queue)
                self.channels = {self.chan.channel_id: self.chan}
                self.invoices = {}
       +        self.preimages = {}
                self.inflight = {}
                self.wallet = MockWallet()
        
       t@@ -112,6 +113,8 @@ class MockLNWorker:
                pass
        
            get_invoice = LNWorker.get_invoice
       +    get_preimage = LNWorker.get_preimage
       +    get_preimage_and_timestamp = LNWorker.get_preimage_and_timestamp
            _create_route_from_invoice = LNWorker._create_route_from_invoice
            _check_invoice = staticmethod(LNWorker._check_invoice)
            _pay_to_route = LNWorker._pay_to_route
       t@@ -204,7 +207,8 @@ class TestPeer(unittest.TestCase):
                                  ('d', 'coffee')
                                 ])
                pay_req = lnencode(addr, w2.node_keypair.privkey)
       -        w2.invoices[bh2u(RHASH)] = (bh2u(payment_preimage), pay_req, True, None)
       +        w2.preimages[bh2u(RHASH)] = (bh2u(payment_preimage), 0)
       +        w2.invoices[bh2u(RHASH)] = (pay_req, True)
                return pay_req
        
            @staticmethod