Browse Source

create_trampoline_route: check that we can pay the amount and the fees, and that the route is sane

patch-4
ThomasV 4 years ago
parent
commit
c9d6d11604
  1. 149
      electrum/lnworker.py

149
electrum/lnworker.py

@ -1281,6 +1281,7 @@ class LNWallet(LNWorker):
def create_trampoline_route( def create_trampoline_route(
self, amount_msat:int, self, amount_msat:int,
min_cltv_expiry:int,
invoice_pubkey:bytes, invoice_pubkey:bytes,
invoice_features:int, invoice_features:int,
channels: List[Channel], channels: List[Channel],
@ -1304,86 +1305,92 @@ class LNWallet(LNWorker):
else: else:
is_legacy = True is_legacy = True
# Find a trampoline. We assume we have a direct channel to trampoline
for chan in channels:
if not self.is_trampoline_peer(chan.node_id):
continue
if chan.is_active() and chan.can_pay(amount_msat, check_frozen=True):
trampoline_short_channel_id = chan.short_channel_id
trampoline_node_id = chan.node_id
break
else:
raise NoPathFound()
# use attempt number to decide fee and second trampoline
# we need a state with the list of nodes we have not tried
# add optional second trampoline
trampoline2 = None
if is_legacy:
for node_id in self.trampoline2_list:
if node_id != trampoline_node_id:
trampoline2 = node_id
break
# fee level. the same fee is used for all trampolines # fee level. the same fee is used for all trampolines
if self.trampoline_fee_level < len(TRAMPOLINE_FEES): if self.trampoline_fee_level < len(TRAMPOLINE_FEES):
params = TRAMPOLINE_FEES[self.trampoline_fee_level] params = TRAMPOLINE_FEES[self.trampoline_fee_level]
else: else:
raise NoPathFound() raise NoPathFound()
self.logger.info(f'create route with trampoline: fee_level={self.trampoline_fee_level}, is legacy: {is_legacy}') # Find a trampoline. We assume we have a direct channel to trampoline
self.logger.info(f'first trampoline: {trampoline_node_id.hex()}') for chan in channels:
self.logger.info(f'second trampoline: {trampoline2.hex() if trampoline2 else None}') if not self.is_trampoline_peer(chan.node_id):
self.logger.info(f'params: {params}') continue
# node_features is only used to determine is_tlv trampoline_short_channel_id = chan.short_channel_id
trampoline_features = LnFeatures.VAR_ONION_OPT trampoline_node_id = chan.node_id
# hop to trampoline # use attempt number to decide fee and second trampoline
route = [ # we need a state with the list of nodes we have not tried
RouteEdge( # add optional second trampoline
node_id=trampoline_node_id, trampoline2 = None
short_channel_id=trampoline_short_channel_id, if is_legacy:
fee_base_msat=0, for node_id in self.trampoline2_list:
fee_proportional_millionths=0, if node_id != trampoline_node_id:
cltv_expiry_delta=0, trampoline2 = node_id
node_features=trampoline_features) break
] # node_features is only used to determine is_tlv
# trampoline hop trampoline_features = LnFeatures.VAR_ONION_OPT
route.append( # hop to trampoline
TrampolineEdge( route = [
node_id=trampoline_node_id, RouteEdge(
fee_base_msat=params['fee_base_msat'], node_id=trampoline_node_id,
fee_proportional_millionths=params['fee_proportional_millionths'], short_channel_id=trampoline_short_channel_id,
cltv_expiry_delta=params['cltv_expiry_delta'], fee_base_msat=0,
node_features=trampoline_features)) fee_proportional_millionths=0,
if trampoline2: cltv_expiry_delta=0,
node_features=trampoline_features)
]
# trampoline hop
route.append( route.append(
TrampolineEdge( TrampolineEdge(
node_id=trampoline2, node_id=trampoline_node_id,
fee_base_msat=params['fee_base_msat'], fee_base_msat=params['fee_base_msat'],
fee_proportional_millionths=params['fee_proportional_millionths'], fee_proportional_millionths=params['fee_proportional_millionths'],
cltv_expiry_delta=params['cltv_expiry_delta'], cltv_expiry_delta=params['cltv_expiry_delta'],
node_features=trampoline_features)) node_features=trampoline_features))
# add routing info if trampoline2:
if is_legacy: route.append(
invoice_routing_info = self.encode_routing_info(r_tags) TrampolineEdge(
route[-1].invoice_routing_info = invoice_routing_info node_id=trampoline2,
route[-1].invoice_features = invoice_features fee_base_msat=params['fee_base_msat'],
fee_proportional_millionths=params['fee_proportional_millionths'],
cltv_expiry_delta=params['cltv_expiry_delta'],
node_features=trampoline_features))
# add routing info
if is_legacy:
invoice_routing_info = self.encode_routing_info(r_tags)
route[-1].invoice_routing_info = invoice_routing_info
route[-1].invoice_features = invoice_features
else:
if t_tag:
pubkey, feebase, feerate, cltv = t_tag
if route[-1].node_id != pubkey:
route.append(
TrampolineEdge(
node_id=pubkey,
fee_base_msat=feebase,
fee_proportional_millionths=feerate,
cltv_expiry_delta=cltv,
node_features=trampoline_features))
# Fake edge (not part of actual route, needed by calc_hops_data)
route.append(
TrampolineEdge(
node_id=invoice_pubkey,
fee_base_msat=0,
fee_proportional_millionths=0,
cltv_expiry_delta=0,
node_features=trampoline_features))
# check that we can pay amount and fees
for edge in route[::-1]:
amount_msat += edge.fee_for_edge(amount_msat)
if not chan.can_pay(amount_msat, check_frozen=True):
continue
if not is_route_sane_to_use(route, amount_msat, min_cltv_expiry):
continue
break
else: else:
if t_tag: raise NoPathFound()
pubkey, feebase, feerate, cltv = t_tag self.logger.info(f'created route with trampoline: fee_level={self.trampoline_fee_level}, is legacy: {is_legacy}')
if route[-1].node_id != pubkey: self.logger.info(f'first trampoline: {trampoline_node_id.hex()}')
route.append( self.logger.info(f'second trampoline: {trampoline2.hex() if trampoline2 else None}')
TrampolineEdge( self.logger.info(f'params: {params}')
node_id=pubkey,
fee_base_msat=feebase,
fee_proportional_millionths=feerate,
cltv_expiry_delta=cltv,
node_features=trampoline_features))
# Fake edge (not part of actual route, needed by calc_hops_data)
route.append(
TrampolineEdge(
node_id=invoice_pubkey,
fee_base_msat=0,
fee_proportional_millionths=0,
cltv_expiry_delta=0,
node_features=trampoline_features))
return route return route
@profiler @profiler
@ -1422,7 +1429,6 @@ class LNWallet(LNWorker):
# preference -funds = (low rating=high preference). # preference -funds = (low rating=high preference).
split_configurations = suggest_splits(amount_msat, channels_with_funds) split_configurations = suggest_splits(amount_msat, channels_with_funds)
for s in split_configurations: for s in split_configurations:
self.logger.info(f"trying split configuration: {s[0]} rating: {s[1]}")
routes = [] routes = []
try: try:
for chanid, part_amount_msat in s[0].items(): for chanid, part_amount_msat in s[0].items():
@ -1441,11 +1447,12 @@ class LNWallet(LNWorker):
channel, channel,
full_path=None) full_path=None)
routes.append((route, amt)) routes.append((route, amt))
self.logger.info(f"found acceptable split configuration: {list(s[0].values())} rating: {s[1]}")
break break
except NoPathFound: except NoPathFound:
continue continue
else: else:
raise NoPathFound raise NoPathFound()
return routes return routes
def create_route_for_payment( def create_route_for_payment(
@ -1461,7 +1468,7 @@ class LNWallet(LNWorker):
channels = [outgoing_channel] if outgoing_channel else list(self.channels.values()) channels = [outgoing_channel] if outgoing_channel else list(self.channels.values())
if not self.channel_db: if not self.channel_db:
route = self.create_trampoline_route( route = self.create_trampoline_route(
amount_msat, invoice_pubkey, invoice_features, channels, r_tags, t_tags) amount_msat, min_cltv_expiry, invoice_pubkey, invoice_features, channels, r_tags, t_tags)
return route, amount_msat return route, amount_msat
route = None route = None

Loading…
Cancel
Save