tlnrouter: filter out unsuitable channels - electrum - Electrum Bitcoin wallet (HTM) git clone https://git.parazyd.org/electrum (DIR) Log (DIR) Files (DIR) Refs (DIR) Submodules --- (DIR) commit 9a59ffaf44a89c33a779286efdf06160961a6a79 (DIR) parent a0acec97203e01d41cc3d203bee893abd5f1d552 (HTM) Author: ThomasV <thomasv@electrum.org> Date: Mon, 15 Oct 2018 12:45:07 +0200 lnrouter: filter out unsuitable channels Diffstat: M electrum/lnaddr.py | 0 M electrum/lnbase.py | 17 ++--------------- M electrum/lnchan.py | 19 +++++++++++++++++++ M electrum/lnrouter.py | 7 +++++-- M electrum/lnworker.py | 4 ++-- 5 files changed, 28 insertions(+), 19 deletions(-) --- (DIR) diff --git a/electrum/lnaddr.py b/electrum/lnaddr.py (DIR) diff --git a/electrum/lnbase.py b/electrum/lnbase.py t@@ -14,6 +14,7 @@ from typing import List import aiorpcx +from .crypto import sha256 from . import bitcoin from . import ecc from .ecc import sig_string_from_r_and_s, get_r_and_s_from_sig_string t@@ -921,22 +922,8 @@ class Peer(PrintError): 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 - # FIXME this below will probably break with multiple HTLCs - msat_local = chan.balance(LOCAL) - amount_msat - msat_remote = chan.balance(REMOTE) + amount_msat + chan.check_can_pay(amount_msat) htlc = {'amount_msat':amount_msat, 'payment_hash':payment_hash, 'cltv_expiry':final_cltv_expiry_with_deltas} - # FIXME if we raise here, this channel will not get blacklisted, and the payment can never succeed, - # as we will just keep retrying this same path. using the current blacklisting is not a solution as - # then no other payment can use this channel either. - # we need finer blacklisting -- e.g. a blacklist for just this "payment session"? - # or blacklist entries could store an msat value and also expire - if len(chan.htlcs(LOCAL, only_pending=True)) + 1 > chan.config[REMOTE].max_accepted_htlcs: - raise PaymentFailure('too many HTLCs already in channel') - if htlcsum(chan.htlcs(LOCAL, only_pending=True)) + amount_msat > chan.config[REMOTE].max_htlc_value_in_flight_msat: - raise PaymentFailure('HTLC value sum would exceed max allowed: {} msat'.format(chan.config[REMOTE].max_htlc_value_in_flight_msat)) - if msat_local < 0: - # FIXME what about channel_reserve_satoshis? will the remote fail the channel if we go below? test. - raise PaymentFailure('not enough local balance') htlc_id = chan.add_htlc(htlc) chan.onion_keys[htlc_id] = secret_key self.attempted_route[(chan.channel_id, htlc_id)] = route (DIR) diff --git a/electrum/lnchan.py b/electrum/lnchan.py t@@ -143,6 +143,25 @@ class Channel(PrintError): def get_state(self): return self._state + def check_can_pay(self, amount_msat): + # FIXME what about channel_reserve_satoshis? will the remote fail the channel if we go below? test. + # FIXME what about tx fees + if self.get_state() != 'OPEN': + raise PaymentFailure('Channel not open') + if self.balance(LOCAL) < amount_msat: + raise PaymentFailure('Not enough local balance') + if len(self.htlcs(LOCAL, only_pending=True)) + 1 > self.config[REMOTE].max_accepted_htlcs: + raise PaymentFailure('Too many HTLCs already in channel') + if htlcsum(self.htlcs(LOCAL, only_pending=True)) + amount_msat > self.config[REMOTE].max_htlc_value_in_flight_msat: + raise PaymentFailure('HTLC value sum would exceed max allowed: {} msat'.format(chan.config[REMOTE].max_htlc_value_in_flight_msat)) + + def can_pay(self, amount_msat): + try: + self.check_can_pay(amount_msat) + except: + return False + return True + def set_funding_txo_spentness(self, is_spent: bool): assert isinstance(is_spent, bool) self._is_funding_txo_spent = is_spent (DIR) diff --git a/electrum/lnrouter.py b/electrum/lnrouter.py t@@ -519,7 +519,7 @@ class LNPathFinder(PrintError): @profiler def find_path_for_payment(self, from_node_id: bytes, to_node_id: bytes, - amount_msat: int=None) -> Sequence[Tuple[bytes, bytes]]: + amount_msat: int=None, my_channels: dict={}) -> Sequence[Tuple[bytes, bytes]]: """Return a path between from_node_id and to_node_id. Returns a list of (node_id, short_channel_id) representing a path. t@@ -527,6 +527,8 @@ class LNPathFinder(PrintError): i.e. an element reads as, "to get to node_id, travel through short_channel_id" """ if amount_msat is not None: assert type(amount_msat) is int + unable_channels = set(map(lambda x: x.short_channel_id, filter(lambda x: not x.can_pay(amount_msat), my_channels.values()))) + # TODO find multiple paths?? # run Dijkstra t@@ -546,7 +548,8 @@ class LNPathFinder(PrintError): # so there are duplicates in the queue, that we discard now: continue for edge_channel_id in self.channel_db.get_channels_for_node(cur_node): - if edge_channel_id in self.blacklist: continue + if edge_channel_id in self.blacklist or edge_channel_id in unable_channels: + continue channel_info = self.channel_db.get_channel_info(edge_channel_id) node1, node2 = channel_info.node_id_1, channel_info.node_id_2 neighbour = node2 if node1 == cur_node else node1 (DIR) diff --git a/electrum/lnworker.py b/electrum/lnworker.py t@@ -280,7 +280,7 @@ class LNWorker(PrintError): for private_route in r_tags: if len(private_route) == 0: continue border_node_pubkey = private_route[0][0] - path = self.network.path_finder.find_path_for_payment(self.node_keypair.pubkey, border_node_pubkey, amount_msat) + path = self.network.path_finder.find_path_for_payment(self.node_keypair.pubkey, border_node_pubkey, amount_msat, self.channels) if path is None: continue route = self.network.path_finder.create_route_from_path(path, self.node_keypair.pubkey) # we need to shift the node pubkey by one towards the destination: t@@ -293,7 +293,7 @@ class LNWorker(PrintError): break # if could not find route using any hint; try without hint now if route is None: - path = self.network.path_finder.find_path_for_payment(self.node_keypair.pubkey, invoice_pubkey, amount_msat) + path = self.network.path_finder.find_path_for_payment(self.node_keypair.pubkey, invoice_pubkey, amount_msat, self.channels) if path is None: raise PaymentFailure(_("No path found")) route = self.network.path_finder.create_route_from_path(path, self.node_keypair.pubkey)