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)