tfix multi-hop payments - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit 56c0983e6906853b37052c3e8e0027e2177a0912 DIR parent ac68c8f53119855ea3293da07205084b0777e7ad HTML Author: SomberNight <somber.night@protonmail.com> Date: Wed, 17 Oct 2018 20:48:21 +0200 fix multi-hop payments Diffstat: M electrum/lnbase.py | 42 +++++++++++++++++++++---------- M electrum/lnrouter.py | 5 ++++- 2 files changed, 33 insertions(+), 14 deletions(-) --- DIR diff --git a/electrum/lnbase.py b/electrum/lnbase.py t@@ -880,6 +880,7 @@ class Peer(PrintError): code = OnionFailureCode(failure_msg.code) data = failure_msg.data self.print_error("UPDATE_FAIL_HTLC", repr(code), data) + self.print_error(f"error reported by {bh2u(route[sender_idx].node_id)}") # handle some specific error codes failure_codes = { OnionFailureCode.TEMPORARY_CHANNEL_FAILURE: 2, t@@ -894,9 +895,12 @@ class Peer(PrintError): channel_update = (258).to_bytes(length=2, byteorder="big") + data[offset:] message_type, payload = decode_msg(channel_update) try: + self.print_error("trying to apply channel update on our db", payload) self.channel_db.on_channel_update(payload) + self.print_error("successfully applied channel update on our db") except NotFoundChanAnnouncementForUpdate: # maybe it is a private channel (and data in invoice was outdated) + self.print_error("maybe channel update is for private channel?") start_node_id = route[sender_idx].node_id self.channel_db.add_channel_update_for_private_channel(payload, start_node_id) else: t@@ -923,29 +927,41 @@ class Peer(PrintError): await self.receive_commitment(chan) self.revoke(chan) + def calc_hops_data_for_payment(self, route: List[RouteEdge], amount_msat: int, min_final_cltv_expiry: int): + """Returns the hops_data to be used for constructing an onion packet, + and the amount_msat and cltv to be used on our immediate channel. + """ + amt = amount_msat + height = self.network.get_local_height() + cltv = height + min_final_cltv_expiry + hops_data = [OnionHopsDataSingle(OnionPerHop(b"\x00" * 8, + amt.to_bytes(8, "big"), + cltv.to_bytes(4, "big")))] + for route_edge in reversed(route[1:]): + hops_data += [OnionHopsDataSingle(OnionPerHop(route_edge.short_channel_id, + amt.to_bytes(8, "big"), + cltv.to_bytes(4, "big")))] + amt += route_edge.fee_for_edge(amt) + cltv += route_edge.cltv_expiry_delta + hops_data.reverse() + return hops_data, amt, cltv + async def pay(self, route: List[RouteEdge], chan, amount_msat, payment_hash, min_final_cltv_expiry): assert chan.get_state() == "OPEN", chan.get_state() assert amount_msat > 0, "amount_msat is not greater zero" - height = self.network.get_local_height() - hops_data = [] - sum_of_deltas = sum(route_edge.cltv_expiry_delta for route_edge in route[1:]) - total_fee = 0 - final_cltv_expiry_without_deltas = (height + min_final_cltv_expiry) - final_cltv_expiry_with_deltas = final_cltv_expiry_without_deltas + sum_of_deltas - for idx, route_edge in enumerate(route[1:]): - hops_data += [OnionHopsDataSingle(OnionPerHop(route_edge.short_channel_id, amount_msat.to_bytes(8, "big"), final_cltv_expiry_without_deltas.to_bytes(4, "big")))] - total_fee += route_edge.fee_base_msat + ( amount_msat * route_edge.fee_proportional_millionths // 1000000 ) + # create onion packet + hops_data, amount_msat, cltv = self.calc_hops_data_for_payment(route, amount_msat, min_final_cltv_expiry) associated_data = payment_hash secret_key = os.urandom(32) - hops_data += [OnionHopsDataSingle(OnionPerHop(b"\x00"*8, amount_msat.to_bytes(8, "big"), (final_cltv_expiry_without_deltas).to_bytes(4, "big")))] onion = new_onion_packet([x.node_id for x in route], secret_key, hops_data, associated_data) - amount_msat += total_fee chan.check_can_pay(amount_msat) - htlc = {'amount_msat':amount_msat, 'payment_hash':payment_hash, 'cltv_expiry':final_cltv_expiry_with_deltas} + # create htlc + htlc = {'amount_msat':amount_msat, 'payment_hash':payment_hash, 'cltv_expiry':cltv} htlc_id = chan.add_htlc(htlc) chan.onion_keys[htlc_id] = secret_key self.attempted_route[(chan.channel_id, htlc_id)] = route - await self.update_channel(chan, "update_add_htlc", channel_id=chan.channel_id, id=htlc_id, cltv_expiry=final_cltv_expiry_with_deltas, amount_msat=amount_msat, payment_hash=payment_hash, onion_routing_packet=onion.to_bytes()) + self.print_error(f"starting payment. route: {route}") + await self.update_channel(chan, "update_add_htlc", channel_id=chan.channel_id, id=htlc_id, cltv_expiry=cltv, amount_msat=amount_msat, payment_hash=payment_hash, onion_routing_packet=onion.to_bytes()) async def receive_revoke(self, m): revoke_and_ack_msg = await self.revoke_and_ack[m.channel_id].get() DIR diff --git a/electrum/lnrouter.py b/electrum/lnrouter.py t@@ -501,7 +501,10 @@ class RouteEdge(NamedTuple("RouteEdge", [('node_id', bytes), ('fee_proportional_millionths', int), ('cltv_expiry_delta', int)])): """if you travel through short_channel_id, you will reach node_id""" - pass + + def fee_for_edge(self, amount_msat): + return self.fee_base_msat \ + + (amount_msat * self.fee_proportional_millionths // 1_000_000) class LNPathFinder(PrintError):