commit 9a59ffaf44a89c33a779286efdf06160961a6a79
parent a0acec97203e01d41cc3d203bee893abd5f1d552
Author: ThomasV <thomasv@electrum.org>
Date: Mon, 15 Oct 2018 12:45:07 +0200
lnrouter: filter out unsuitable channels
Diffstat:
5 files changed, 28 insertions(+), 19 deletions(-)
diff --git a/electrum/lnaddr.py b/electrum/lnaddr.py
diff --git a/electrum/lnbase.py b/electrum/lnbase.py
@@ -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
@@ -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
diff --git a/electrum/lnchan.py b/electrum/lnchan.py
@@ -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
diff --git a/electrum/lnrouter.py b/electrum/lnrouter.py
@@ -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.
@@ -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
@@ -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
diff --git a/electrum/lnworker.py b/electrum/lnworker.py
@@ -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:
@@ -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)