electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit af4f0b6daf1590929183c1975a5a9823c71685a7
parent ae3971259de76dda850ffdf949f786a1677d8c24
Author: ThomasV <thomasv@electrum.org>
Date:   Wed, 30 May 2018 13:42:25 +0200

lnworker: separate invoice creation from payment flow

Diffstat:
Melectrum/commands.py | 4++--
Mgui/qt/lightning_channels_list.py | 4++--
Mgui/qt/lightning_invoice_list.py | 6+++---
Mlib/lnbase.py | 111+++++++++++++++++++++++++++++--------------------------------------------------
Mlib/lnworker.py | 53+++++++++++------------------------------------------
5 files changed, 58 insertions(+), 120 deletions(-)

diff --git a/electrum/commands.py b/electrum/commands.py @@ -777,8 +777,8 @@ class Commands: self.wallet.lnworker.pay(invoice) @command('wn') - def lnreceive(self): - self.wallet.lnworker.get_paid() + def addinvoice(self, amount, message): + return self.wallet.lnworker.add_invoice(satoshis(amount), message) @command('wn') def listchannels(self): diff --git a/gui/qt/lightning_channels_list.py b/gui/qt/lightning_channels_list.py @@ -83,8 +83,8 @@ class LightningChannelsList(QtWidgets.QWidget): self.lnworker = lnworker - lnworker.subscribe_channel_list_updates_from_other_thread(self.update_rows.emit) - lnworker.subscribe_single_channel_update_from_other_thread(self.update_single_row.emit) + #lnworker.subscribe_channel_list_updates_from_other_thread(self.update_rows.emit) + #lnworker.subscribe_single_channel_update_from_other_thread(self.update_single_row.emit) self._tv=QtWidgets.QTreeWidget(self) self._tv.setHeaderLabels([mapping[i] for i in range(len(mapping))]) diff --git a/gui/qt/lightning_invoice_list.py b/gui/qt/lightning_invoice_list.py @@ -59,7 +59,7 @@ class LightningInvoiceList(QtWidgets.QWidget): #} #treeView.insertTopLevelItem(0, addInvoiceRow(obj)) idx += 1 - lnworker.add_invoice_from_other_thread(amt) + lnworker.add_invoice(amt) def create_menu(self, position): menu = QtWidgets.QMenu() @@ -96,8 +96,8 @@ class LightningInvoiceList(QtWidgets.QWidget): self.payment_received_signal.connect(self.paymentReceived) self.invoice_added_signal.connect(self.invoice_added_handler) - lnworker.subscribe_payment_received_from_other_thread(self.payment_received_signal.emit) - lnworker.subscribe_invoice_added_from_other_thread(self.invoice_added_signal.emit) + #lnworker.subscribe_payment_received_from_other_thread(self.payment_received_signal.emit) + #lnworker.subscribe_invoice_added_from_other_thread(self.invoice_added_signal.emit) self._tv=QtWidgets.QTreeWidget(self) self._tv.setHeaderLabels([mapping[i] for i in range(len(mapping))]) diff --git a/lib/lnbase.py b/lib/lnbase.py @@ -568,8 +568,7 @@ def is_synced(network): class Peer(PrintError): - def __init__(self, host, port, pubkey, privkey, network, channel_db, path_finder, channel_state, channels, request_initial_sync=False): - self.update_add_htlc_event = asyncio.Event() + def __init__(self, host, port, pubkey, privkey, network, channel_db, path_finder, channel_state, channels, invoices, request_initial_sync=False): self.channel_update_event = asyncio.Event() self.host = host self.port = port @@ -591,6 +590,7 @@ class Peer(PrintError): self.channel_state = channel_state self.nodes = {} self.channels = channels + self.invoices = invoices def diagnostic_name(self): return self.host @@ -759,9 +759,6 @@ class Peer(PrintError): self.channel_db.on_channel_announcement(payload) self.channel_update_event.set() - #def open_channel(self, funding_sat, push_msat): - # self.send_message(gen_msg('open_channel', funding_sat=funding_sat, push_msat=push_msat)) - @aiosafe async def main_loop(self): self.reader, self.writer = await asyncio.open_connection(self.host, self.port) @@ -975,23 +972,23 @@ class Peer(PrintError): def on_update_fail_htlc(self, payload): print("UPDATE_FAIL_HTLC", decode_onion_error(payload["reason"], self.node_keys, self.secret_key)) - async def pay(self, wallet, chan, amount_msat, payment_hash, pubkey_in_invoice, min_final_cltv_expiry): - def derive_and_incr(): - nonlocal chan - last_small_num = chan.local_state.ctn - next_small_num = last_small_num + 2 - this_small_num = last_small_num + 1 - last_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-last_small_num-1) - this_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-this_small_num-1) - this_point = secret_to_pubkey(int.from_bytes(this_secret, 'big')) - next_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-next_small_num-1) - next_point = secret_to_pubkey(int.from_bytes(next_secret, 'big')) - chan = chan._replace( - local_state=chan.local_state._replace( - ctn=chan.local_state.ctn + 1 - ) + def derive_and_incr(self, chan): + last_small_num = chan.local_state.ctn + next_small_num = last_small_num + 2 + this_small_num = last_small_num + 1 + last_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-last_small_num-1) + this_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-this_small_num-1) + this_point = secret_to_pubkey(int.from_bytes(this_secret, 'big')) + next_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-next_small_num-1) + next_point = secret_to_pubkey(int.from_bytes(next_secret, 'big')) + chan = chan._replace( + local_state=chan.local_state._replace( + ctn=chan.local_state.ctn + 1 ) - return last_secret, this_point, next_point + ) + return chan, last_secret, this_point, next_point + + async def pay(self, wallet, chan, amount_msat, payment_hash, pubkey_in_invoice, min_final_cltv_expiry): assert self.channel_state[chan.channel_id] == "OPEN" their_revstore = chan.remote_state.revocation_store while not is_synced(wallet.network): @@ -1070,7 +1067,7 @@ class Peer(PrintError): revoke_and_ack_msg = await self.revoke_and_ack[chan.channel_id].get() # TODO check revoke_and_ack results - last_secret, _, next_point = derive_and_incr() + chan, last_secret, _, next_point = self.derive_and_incr(chan) their_revstore.add_next_entry(last_secret) self.send_message(gen_msg("revoke_and_ack", channel_id=chan.channel_id, @@ -1084,7 +1081,7 @@ class Peer(PrintError): print("waiting for commitment_signed") commitment_signed_msg = await self.commitment_signed[chan.channel_id].get() - last_secret, _, next_point = derive_and_incr() + chan, last_secret, _, next_point = self.derive_and_incr(chan) their_revstore.add_next_entry(last_secret) self.send_message(gen_msg("revoke_and_ack", channel_id=chan.channel_id, @@ -1117,76 +1114,44 @@ class Peer(PrintError): ) ) - async def receive_commitment_revoke_ack(self, chan, expected_received_msat, payment_preimage): - def derive_and_incr(): - nonlocal chan - last_small_num = chan.local_state.ctn - next_small_num = last_small_num + 2 - this_small_num = last_small_num + 1 - last_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-last_small_num-1) - this_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-this_small_num-1) - this_point = secret_to_pubkey(int.from_bytes(this_secret, 'big')) - next_secret = get_per_commitment_secret_from_seed(chan.local_state.per_commitment_secret_seed, 2**48-next_small_num-1) - next_point = secret_to_pubkey(int.from_bytes(next_secret, 'big')) - chan = chan._replace( - local_state=chan.local_state._replace( - ctn=chan.local_state.ctn + 1 - ) - ) - return last_secret, this_point, next_point - - assert self.channel_state[chan.channel_id] == "OPEN" + async def receive_commitment_revoke_ack(self, htlc, expected_received_msat, payment_preimage): - their_revstore = chan.remote_state.revocation_store - - channel_id = chan.channel_id - commitment_signed_msg = await self.commitment_signed[channel_id].get() - - if int.from_bytes(commitment_signed_msg["num_htlcs"], "big") < 1: - while len(self.unfulfilled_htlcs) < 1: - print("waiting for add_update_htlc") - await asyncio.sleep(1) + htlc_id = int.from_bytes(htlc["id"], 'big') + for chan in self.channels: + if htlc_id == chan.remote_state.next_htlc_id: + break else: - print("commitment signed message had htlcs") - assert len(self.unfulfilled_htlcs) == 1 + raise Exception('cannot find channel for htlc', htlc_id) - htlc = self.unfulfilled_htlcs.pop(0) - htlc_id = int.from_bytes(htlc["id"], 'big') - assert htlc_id == chan.remote_state.next_htlc_id, (htlc_id, chan.remote_state.next_htlc_id) + channel_id = chan.channel_id + assert self.channel_state[channel_id] == "OPEN" + their_revstore = chan.remote_state.revocation_store cltv_expiry = int.from_bytes(htlc["cltv_expiry"], 'big') # TODO verify sanity of their cltv expiry amount_msat = int.from_bytes(htlc["amount_msat"], 'big') assert amount_msat == expected_received_msat payment_hash = htlc["payment_hash"] - - last_secret, this_point, next_point = derive_and_incr() - + chan, last_secret, this_point, next_point = self.derive_and_incr(chan) remote_htlc_pubkey = derive_pubkey(chan.remote_config.htlc_basepoint.pubkey, this_point) local_htlc_pubkey = derive_pubkey(chan.local_config.htlc_basepoint.pubkey, this_point) - remote_revocation_pubkey = derive_blinded_pubkey(chan.remote_config.revocation_basepoint.pubkey, this_point) - htlcs_in_local = [ ( make_received_htlc(remote_revocation_pubkey, remote_htlc_pubkey, local_htlc_pubkey, payment_hash, cltv_expiry), amount_msat ) ] - new_commitment = make_commitment_using_open_channel(chan, chan.local_state.ctn, True, this_point, chan.local_state.amount_msat, chan.remote_state.amount_msat - expected_received_msat, htlcs_in_local) - preimage_hex = new_commitment.serialize_preimage(0) pre_hash = bitcoin.Hash(bfh(preimage_hex)) if not ecc.verify_signature(chan.remote_config.multisig_key.pubkey, commitment_signed_msg["signature"], pre_hash): raise Exception('failed verifying signature of our updated commitment transaction') - htlc_sigs_len = len(commitment_signed_msg["htlc_signature"]) if htlc_sigs_len != 64: raise Exception("unexpected number of htlc signatures: " + str(htlc_sigs_len)) - htlc_tx = make_htlc_tx_with_open_channel(chan, this_point, True, True, amount_msat, cltv_expiry, payment_hash, new_commitment, 0) pre_hash = bitcoin.Hash(bfh(htlc_tx.serialize_preimage(0))) remote_htlc_pubkey = derive_pubkey(chan.remote_config.htlc_basepoint.pubkey, this_point) @@ -1194,7 +1159,6 @@ class Peer(PrintError): raise Exception("failed verifying signature an HTLC tx spending from one of our commit tx'es HTLC outputs") their_revstore.add_next_entry(last_secret) - self.send_message(gen_msg("revoke_and_ack", channel_id=channel_id, per_commitment_secret=last_secret, @@ -1212,14 +1176,11 @@ class Peer(PrintError): remote_ctx = make_commitment_using_open_channel(chan, chan.remote_state.ctn + 1, False, chan.remote_state.next_per_commitment_point, chan.remote_state.amount_msat - expected_received_msat, chan.local_state.amount_msat, htlcs_in_remote) sig_64 = sign_and_get_sig_string(remote_ctx, chan.local_config, chan.remote_config) - htlc_tx = make_htlc_tx_with_open_channel(chan, chan.remote_state.next_per_commitment_point, False, True, amount_msat, cltv_expiry, payment_hash, remote_ctx, 0) - # htlc_sig signs the HTLC transaction that spends from THEIR commitment transaction's offered_htlc output sig = bfh(htlc_tx.sign_txin(0, their_remote_htlc_privkey)) r, s = sigdecode_der(sig[:-1], SECP256k1.generator.order()) htlc_sig = sigencode_string_canonize(r, s, SECP256k1.generator.order()) - self.send_message(gen_msg("commitment_signed", channel_id=channel_id, signature=sig_64, num_htlcs=1, htlc_signature=htlc_sig)) revoke_and_ack_msg = await self.revoke_and_ack[channel_id].get() @@ -1237,6 +1198,7 @@ class Peer(PrintError): sig_64 = sign_and_get_sig_string(bare_ctx, chan.local_config, chan.remote_config) self.send_message(gen_msg("commitment_signed", channel_id=channel_id, signature=sig_64, num_htlcs=0)) + revoke_and_ack_msg = await self.revoke_and_ack[channel_id].get() # TODO check revoke_and_ack results @@ -1245,7 +1207,7 @@ class Peer(PrintError): # TODO check commitment_signed results - last_secret, _, next_point = derive_and_incr() + chan, last_secret, _, next_point = self.derive_and_incr(chan) their_revstore.add_next_entry(last_secret) @@ -1285,7 +1247,14 @@ class Peer(PrintError): self.print_error('on_update_add_htlc', payload) assert self.unfulfilled_htlcs == [] self.unfulfilled_htlcs.append(payload) - self.update_add_htlc_event.set() + # check if this in our list of requests + payment_hash = payload["payment_hash"] + for k in self.invoices.keys(): + preimage = bfh(k) + if sha256(preimage) == payment_hash: + req = self.invoices[k] + coro = self.receive_commitment_revoke_ack(payload, chan, expected_received_msat, payment_preimage) + future = asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop) def on_revoke_and_ack(self, payload): channel_id = int.from_bytes(payload["channel_id"], 'big') diff --git a/lib/lnworker.py b/lib/lnworker.py @@ -99,6 +99,7 @@ class LNWorker(PrintError): self.channel_db = lnrouter.ChannelDB() self.path_finder = lnrouter.LNPathFinder(self.channel_db) self.channels = [reconstruct_namedtuples(x) for x in wallet.storage.get("channels", {})] + self.invoices = wallet.storage.get('lightning_invoices', {}) peer_list = network.config.get('lightning_peers', node_list) self.channel_state = {chan.channel_id: "OPENING" for chan in self.channels} for host, port, pubkey in peer_list: @@ -110,7 +111,7 @@ class LNWorker(PrintError): def add_peer(self, host, port, pubkey): node_id = bfh(pubkey) channels = list(filter(lambda x: x.node_id == node_id, self.channels)) - peer = Peer(host, int(port), node_id, self.privkey, self.network, self.channel_db, self.path_finder, self.channel_state, channels, request_initial_sync=True) + peer = Peer(host, int(port), node_id, self.privkey, self.network, self.channel_db, self.path_finder, self.channel_state, channels, self.invoices, request_initial_sync=True) self.network.futures.append(asyncio.run_coroutine_threadsafe(peer.main_loop(), asyncio.get_event_loop())) self.peers[node_id] = peer @@ -174,22 +175,11 @@ class LNWorker(PrintError): def open_channel(self, node_id, local_amt, push_amt, pw): coro = self._open_channel_coroutine(node_id, local_amt, push_amt, None if pw == "" else pw) return asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop).result() - #chan = fut.result() - # https://api.lightning.community/#listchannels - #std_chan = {"chan_id": chan.channel_id} - #emit_function({"channels": [std_chan]}) - - def get_paid(self): - coro = self._get_paid_coroutine() - return asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop).result() def pay(self, invoice): coro = self._pay_coroutine(invoice) return asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop).result() - def list_channels(self): - return serialize_channels(self.channels) - # not aiosafe because we call .result() which will propagate an exception async def _pay_coroutine(self, invoice): openchannel = self.channels[0] @@ -201,37 +191,16 @@ class LNWorker(PrintError): openchannel = await peer.pay(self.wallet, openchannel, msat_amt, payment_hash, pubkey, addr.min_final_cltv_expiry) self.save_channel(openchannel) - # not aiosafe because we call .result() which will propagate an exception - async def _get_paid_coroutine(self): - openchannel = self.channels[0] + def add_invoice(self, amount, message='one cup of coffee'): payment_preimage = os.urandom(32) RHASH = sha256(payment_preimage) - expected_received_sat = 200000 - expected_received_msat = expected_received_sat * 1000 - peer = self.peers[openchannel.node_id] - pay_req = lnencode(LnAddr(RHASH, amount=1/Decimal(COIN)*expected_received_sat, tags=[('d', 'one cup of coffee')]), peer.privkey) - decoded = lndecode(pay_req, expected_hrp="sb") + pay_req = lnencode(LnAddr(RHASH, amount/Decimal(COIN), tags=[('d', message)]), self.privkey) + decoded = lndecode(pay_req, expected_hrp=constants.net.SEGWIT_HRP) assert decoded.pubkey.serialize() == privkey_to_pubkey(self.privkey) - print("payment request", pay_req) - openchannel = await peer.receive_commitment_revoke_ack(openchannel, expected_received_msat, payment_preimage) - self.save_channel(openchannel) - - def subscribe_payment_received_from_other_thread(self, emit_function): - pass - - def subscribe_channel_list_updates_from_other_thread(self, emit_function): - pass - - def subscribe_single_channel_update_from_other_thread(self, emit_function): - pass - - def add_invoice_from_other_thread(self, amt): - pass - - def subscribe_invoice_added_from_other_thread(self, emit_function): - pass - - def pay_invoice_from_other_thread(self, lnaddr): - pass - + self.invoices[bh2u(payment_preimage)] = pay_req + self.wallet.storage.put('lightning_invoices', self.invoices) + self.wallet.storage.write() + return pay_req + def list_channels(self): + return serialize_channels(self.channels)