Browse Source

lightning: Save invoices and preimages separately. Save preimages when forwarding

dependabot/pip/contrib/deterministic-build/ecdsa-0.13.3
ThomasV 6 years ago
parent
commit
62be0c481c
  1. 2
      electrum/gui/qt/channel_details.py
  2. 2
      electrum/gui/qt/invoice_list.py
  3. 2
      electrum/gui/qt/request_list.py
  4. 7
      electrum/lnpeer.py
  5. 4
      electrum/lnsweep.py
  6. 108
      electrum/lnworker.py
  7. 6
      electrum/tests/test_lnpeer.py

2
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:

2
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)

2
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)

7
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)

4
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

108
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:

6
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

Loading…
Cancel
Save