commit 62be0c481c9fcdd26eb4b7c4a2c72eea8cec9b92
parent e475617b7566556b337ccdc12fca64be57787453
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:
7 files changed, 72 insertions(+), 59 deletions(-)
diff --git a/electrum/gui/qt/channel_details.py b/electrum/gui/qt/channel_details.py
@@ -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:
diff --git a/electrum/gui/qt/invoice_list.py b/electrum/gui/qt/invoice_list.py
@@ -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)
diff --git a/electrum/gui/qt/request_list.py b/electrum/gui/qt/request_list.py
@@ -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)
diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py
@@ -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,
@@ -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()
@@ -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)
diff --git a/electrum/lnsweep.py b/electrum/lnsweep.py
@@ -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
@@ -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
diff --git a/electrum/lnworker.py b/electrum/lnworker.py
@@ -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()
@@ -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
@@ -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:
@@ -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
@@ -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,
@@ -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):
@@ -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):
@@ -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)
@@ -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")
@@ -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)
@@ -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
@@ -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:
diff --git a/electrum/tests/test_lnpeer.py b/electrum/tests/test_lnpeer.py
@@ -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()
@@ -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
@@ -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