Browse Source

lnworker: use PaymentFeeBudget

- introduce PaymentFeeBudget, which contains limits for fee budget and cltv budget
  - when splitting a payment,
    - the fee budget is linearly distributed between the parts
      - this resolves a FIXME in lnrouter ("FIXME in case of MPP")
    - the cltv budget is simply copied
  - we could also add other kinds of budgets later, e.g. for the num in-flight htlcs
- resolves TODO in lnworker ("todo: compare to the fee of the actual route we found")
master
SomberNight 2 years ago
parent
commit
6506abf583
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 26
      electrum/lnpeer.py
  2. 41
      electrum/lnrouter.py
  3. 16
      electrum/lnutil.py
  4. 50
      electrum/lnworker.py
  5. 8
      electrum/tests/test_lnpeer.py
  6. 12
      electrum/trampoline.py

26
electrum/lnpeer.py

@ -43,7 +43,7 @@ from .lnutil import (Outpoint, LocalConfig, RECEIVED, UpdateAddHtlc, ChannelConf
RemoteMisbehaving, ShortChannelID, RemoteMisbehaving, ShortChannelID,
IncompatibleLightningFeatures, derive_payment_secret_from_payment_preimage, IncompatibleLightningFeatures, derive_payment_secret_from_payment_preimage,
ChannelType, LNProtocolWarning, validate_features, IncompatibleOrInsaneFeatures) ChannelType, LNProtocolWarning, validate_features, IncompatibleOrInsaneFeatures)
from .lnutil import FeeUpdate, channel_id_from_funding_tx from .lnutil import FeeUpdate, channel_id_from_funding_tx, PaymentFeeBudget
from .lntransport import LNTransport, LNTransportBase from .lntransport import LNTransport, LNTransportBase
from .lnmsg import encode_msg, decode_msg, UnknownOptionalMsgType, FailedToParseMsg from .lnmsg import encode_msg, decode_msg, UnknownOptionalMsgType, FailedToParseMsg
from .interface import GracefulDisconnect from .interface import GracefulDisconnect
@ -1795,11 +1795,13 @@ class Peer(Logger):
# these are the fee/cltv paid by the sender # these are the fee/cltv paid by the sender
# pay_to_node will raise if they are not sufficient # pay_to_node will raise if they are not sufficient
trampoline_cltv_delta = inc_cltv_abs - out_cltv_abs # cltv budget
total_msat = outer_onion.hop_data.payload["payment_data"]["total_msat"] total_msat = outer_onion.hop_data.payload["payment_data"]["total_msat"]
trampoline_fee = total_msat - amt_to_forward budget = PaymentFeeBudget(
self.logger.info(f'trampoline forwarding. fee_budget={trampoline_fee}') fee_msat=total_msat - amt_to_forward,
self.logger.info(f'trampoline forwarding. cltv_budget={trampoline_cltv_delta}. (inc={inc_cltv_abs}. out={out_cltv_abs})') cltv=inc_cltv_abs - out_cltv_abs,
)
self.logger.info(f'trampoline forwarding. budget={budget}')
self.logger.info(f'trampoline forwarding. {inc_cltv_abs=}, {out_cltv_abs=}')
# To convert abs vs rel cltvs, we need to guess blockheight used by original sender as "current blockheight". # To convert abs vs rel cltvs, we need to guess blockheight used by original sender as "current blockheight".
# Blocks might have been mined since. # Blocks might have been mined since.
# - if we skew towards the past, we decrease our own cltv_budget accordingly (which is ok) # - if we skew towards the past, we decrease our own cltv_budget accordingly (which is ok)
@ -1809,22 +1811,24 @@ class Peer(Logger):
local_height_of_onion_creator = self.network.get_local_height() - 1 local_height_of_onion_creator = self.network.get_local_height() - 1
cltv_budget_for_rest_of_route = out_cltv_abs - local_height_of_onion_creator cltv_budget_for_rest_of_route = out_cltv_abs - local_height_of_onion_creator
if budget.fee_msat < 1000:
raise OnionRoutingFailure(code=OnionFailureCode.TRAMPOLINE_FEE_INSUFFICIENT, data=b'')
if budget.cltv < 576:
raise OnionRoutingFailure(code=OnionFailureCode.TRAMPOLINE_EXPIRY_TOO_SOON, data=b'')
try: try:
await self.lnworker.pay_to_node( await self.lnworker.pay_to_node(
node_pubkey=outgoing_node_id, node_pubkey=outgoing_node_id,
payment_hash=payment_hash, payment_hash=payment_hash,
payment_secret=payment_secret, payment_secret=payment_secret,
amount_to_pay=amt_to_forward, amount_to_pay=amt_to_forward,
# FIXME this API (min_final_cltv_delta) is confusing. The value will be added to local_height
# to form the abs cltv used on the last edge on the path to the *next trampoline* node.
# We should rewrite pay_to_node to operate on a cltv-budget (and fee-budget).
min_final_cltv_delta=cltv_budget_for_rest_of_route, min_final_cltv_delta=cltv_budget_for_rest_of_route,
r_tags=r_tags, r_tags=r_tags,
invoice_features=invoice_features, invoice_features=invoice_features,
fwd_trampoline_onion=next_trampoline_onion, fwd_trampoline_onion=next_trampoline_onion,
fwd_trampoline_fee=trampoline_fee, budget=budget,
fwd_trampoline_cltv_delta=trampoline_cltv_delta, attempts=1,
attempts=1) )
except OnionRoutingFailure as e: except OnionRoutingFailure as e:
raise raise
except PaymentFailure as e: except PaymentFailure as e:

41
electrum/lnrouter.py

@ -35,7 +35,7 @@ from math import inf
from .util import profiler, with_lock from .util import profiler, with_lock
from .logging import Logger from .logging import Logger
from .lnutil import (NUM_MAX_EDGES_IN_PAYMENT_PATH, ShortChannelID, LnFeatures, from .lnutil import (NUM_MAX_EDGES_IN_PAYMENT_PATH, ShortChannelID, LnFeatures,
NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE) NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE, PaymentFeeBudget)
from .channel_db import ChannelDB, Policy, NodeInfo from .channel_db import ChannelDB, Policy, NodeInfo
if TYPE_CHECKING: if TYPE_CHECKING:
@ -111,7 +111,7 @@ class RouteEdge(PathEdge):
if self.cltv_delta > 14 * 144: if self.cltv_delta > 14 * 144:
return False return False
total_fee = self.fee_for_edge(amount_msat) total_fee = self.fee_for_edge(amount_msat)
if not is_fee_sane(total_fee, payment_amount_msat=amount_msat): if total_fee > get_default_fee_budget_msat(invoice_amount_msat=amount_msat):
return False return False
return True return True
@ -138,38 +138,41 @@ LNPaymentRoute = Sequence[RouteEdge]
LNPaymentTRoute = Sequence[TrampolineEdge] LNPaymentTRoute = Sequence[TrampolineEdge]
def is_route_sane_to_use(route: LNPaymentRoute, *, amount_msat_for_dest: int, cltv_delta_for_dest: int) -> bool: def is_route_within_budget(
route: LNPaymentRoute,
*,
budget: PaymentFeeBudget,
amount_msat_for_dest: int, # that final receiver gets
cltv_delta_for_dest: int, # that final receiver gets
) -> bool:
"""Run some sanity checks on the whole route, before attempting to use it. """Run some sanity checks on the whole route, before attempting to use it.
called when we are paying; so e.g. lower cltv is better called when we are paying; so e.g. lower cltv is better
""" """
if len(route) > NUM_MAX_EDGES_IN_PAYMENT_PATH: if len(route) > NUM_MAX_EDGES_IN_PAYMENT_PATH:
return False return False
amt = amount_msat_for_dest amt = amount_msat_for_dest
cltv_delta = cltv_delta_for_dest cltv_cost_of_route = 0 # excluding cltv_delta_for_dest
for route_edge in reversed(route[1:]): for route_edge in reversed(route[1:]):
if not route_edge.is_sane_to_use(amt): return False if not route_edge.is_sane_to_use(amt): return False
amt += route_edge.fee_for_edge(amt) amt += route_edge.fee_for_edge(amt)
cltv_delta += route_edge.cltv_delta cltv_cost_of_route += route_edge.cltv_delta
total_fee = amt - amount_msat_for_dest fee_cost = amt - amount_msat_for_dest
# TODO revise ad-hoc heuristics # check against budget
if cltv_delta > NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE: if cltv_cost_of_route > budget.cltv:
return False return False
# FIXME in case of MPP, the fee checks are done independently for each part, if fee_cost > budget.fee_msat:
# which is ok for the proportional checks but not for the absolute ones. return False
# This is not that big of a deal though as we don't split into *too many* parts. # sanity check
if not is_fee_sane(total_fee, payment_amount_msat=amount_msat_for_dest): total_cltv_delta = cltv_cost_of_route + cltv_delta_for_dest
if total_cltv_delta > NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE:
return False return False
return True return True
def is_fee_sane(fee_msat: int, *, payment_amount_msat: int) -> bool: def get_default_fee_budget_msat(*, invoice_amount_msat: int) -> int:
# fees <= 5 sat are fine
if fee_msat <= 5_000:
return True
# fees <= 1 % of payment are fine # fees <= 1 % of payment are fine
if 100 * fee_msat <= payment_amount_msat: # fees <= 5 sat are fine
return True return max(5_000, invoice_amount_msat // 100)
return False
class LiquidityHint: class LiquidityHint:

16
electrum/lnutil.py

@ -1637,3 +1637,19 @@ class OnionFailureCodeMetaFlag(IntFlag):
UPDATE = 0x1000 UPDATE = 0x1000
class PaymentFeeBudget(NamedTuple):
fee_msat: int
# The cltv budget covers the cost of route to get to the destination, but excluding the
# cltv-delta the destination wants for itself. (e.g. "min_final_cltv_delta" is excluded)
cltv: int # this is cltv-delta-like, no absolute heights here!
#num_htlc: int
@classmethod
def default(cls, *, invoice_amount_msat: int) -> 'PaymentFeeBudget':
from .lnrouter import get_default_fee_budget_msat
return PaymentFeeBudget(
fee_msat=get_default_fee_budget_msat(invoice_amount_msat=invoice_amount_msat),
cltv=NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE,
)

50
electrum/lnworker.py

@ -66,12 +66,12 @@ from .lnutil import (Outpoint, LNPeerAddr,
UpdateAddHtlc, Direction, LnFeatures, ShortChannelID, UpdateAddHtlc, Direction, LnFeatures, ShortChannelID,
HtlcLog, derive_payment_secret_from_payment_preimage, HtlcLog, derive_payment_secret_from_payment_preimage,
NoPathFound, InvalidGossipMsg) NoPathFound, InvalidGossipMsg)
from .lnutil import ln_compare_features, IncompatibleLightningFeatures from .lnutil import ln_compare_features, IncompatibleLightningFeatures, PaymentFeeBudget
from .transaction import PartialTxOutput, PartialTransaction, PartialTxInput from .transaction import PartialTxOutput, PartialTransaction, PartialTxInput
from .lnonion import decode_onion_error, OnionFailureCode, OnionRoutingFailure, OnionPacket from .lnonion import decode_onion_error, OnionFailureCode, OnionRoutingFailure, OnionPacket
from .lnmsg import decode_msg from .lnmsg import decode_msg
from .i18n import _ from .i18n import _
from .lnrouter import (RouteEdge, LNPaymentRoute, LNPaymentPath, is_route_sane_to_use, from .lnrouter import (RouteEdge, LNPaymentRoute, LNPaymentPath, is_route_within_budget,
NoChannelPolicy, LNPathInconsistent) NoChannelPolicy, LNPathInconsistent)
from .address_synchronizer import TX_HEIGHT_LOCAL, TX_TIMESTAMP_INF from .address_synchronizer import TX_HEIGHT_LOCAL, TX_TIMESTAMP_INF
from . import lnsweep from . import lnsweep
@ -662,7 +662,7 @@ class PaySession(Logger):
initial_trampoline_fee_level: int, initial_trampoline_fee_level: int,
invoice_features: int, invoice_features: int,
r_tags, r_tags,
min_final_cltv_delta: int, # delta for last edge (typically from invoice) min_final_cltv_delta: int, # delta for last node (typically from invoice)
amount_to_pay: int, # total payment amount final receiver will get amount_to_pay: int, # total payment amount final receiver will get
invoice_pubkey: bytes, invoice_pubkey: bytes,
uses_trampoline: bool, # whether sender uses trampoline or gossip uses_trampoline: bool, # whether sender uses trampoline or gossip
@ -1419,6 +1419,7 @@ class LNWallet(LNWorker):
f"using_trampoline={self.uses_trampoline()}. " f"using_trampoline={self.uses_trampoline()}. "
f"invoice_features={invoice_features.get_names()}") f"invoice_features={invoice_features.get_names()}")
self.set_invoice_status(key, PR_INFLIGHT) self.set_invoice_status(key, PR_INFLIGHT)
budget = PaymentFeeBudget.default(invoice_amount_msat=amount_to_pay)
success = False success = False
try: try:
await self.pay_to_node( await self.pay_to_node(
@ -1431,7 +1432,9 @@ class LNWallet(LNWorker):
invoice_features=invoice_features, invoice_features=invoice_features,
attempts=attempts, attempts=attempts,
full_path=full_path, full_path=full_path,
channels=channels) channels=channels,
budget=budget,
)
success = True success = True
except PaymentFailure as e: except PaymentFailure as e:
self.logger.info(f'payment failure: {e!r}') self.logger.info(f'payment failure: {e!r}')
@ -1462,17 +1465,13 @@ class LNWallet(LNWorker):
attempts: int = None, attempts: int = None,
full_path: LNPaymentPath = None, full_path: LNPaymentPath = None,
fwd_trampoline_onion: OnionPacket = None, fwd_trampoline_onion: OnionPacket = None,
fwd_trampoline_fee: int = None, budget: PaymentFeeBudget,
fwd_trampoline_cltv_delta: int = None,
channels: Optional[Sequence[Channel]] = None, channels: Optional[Sequence[Channel]] = None,
) -> None: ) -> None:
if fwd_trampoline_onion: assert budget
# todo: compare to the fee of the actual route we found assert budget.fee_msat >= 0, budget
if fwd_trampoline_fee < 1000: assert budget.cltv >= 0, budget
raise OnionRoutingFailure(code=OnionFailureCode.TRAMPOLINE_FEE_INSUFFICIENT, data=b'')
if fwd_trampoline_cltv_delta < 576:
raise OnionRoutingFailure(code=OnionFailureCode.TRAMPOLINE_EXPIRY_TOO_SOON, data=b'')
payment_key = payment_hash + payment_secret payment_key = payment_hash + payment_secret
assert payment_key not in self._paysessions assert payment_key not in self._paysessions
@ -1499,12 +1498,14 @@ class LNWallet(LNWorker):
# 1. create a set of routes for remaining amount. # 1. create a set of routes for remaining amount.
# note: path-finding runs in a separate thread so that we don't block the asyncio loop # note: path-finding runs in a separate thread so that we don't block the asyncio loop
# graph updates might occur during the computation # graph updates might occur during the computation
remaining_fee_budget_msat = (budget.fee_msat * amount_to_send) // amount_to_pay
routes = self.create_routes_for_payment( routes = self.create_routes_for_payment(
paysession=paysession, paysession=paysession,
amount_msat=amount_to_send, amount_msat=amount_to_send,
full_path=full_path, full_path=full_path,
fwd_trampoline_onion=fwd_trampoline_onion, fwd_trampoline_onion=fwd_trampoline_onion,
channels=channels, channels=channels,
budget=budget._replace(fee_msat=remaining_fee_budget_msat),
) )
# 2. send htlcs # 2. send htlcs
async for sent_htlc_info, cltv_delta, trampoline_onion in routes: async for sent_htlc_info, cltv_delta, trampoline_onion in routes:
@ -1815,6 +1816,7 @@ class LNWallet(LNWorker):
fwd_trampoline_onion: OnionPacket = None, fwd_trampoline_onion: OnionPacket = None,
full_path: LNPaymentPath = None, full_path: LNPaymentPath = None,
channels: Optional[Sequence[Channel]] = None, channels: Optional[Sequence[Channel]] = None,
budget: PaymentFeeBudget,
) -> AsyncGenerator[Tuple[SentHtlcInfo, int, Optional[OnionPacket]], None]: ) -> AsyncGenerator[Tuple[SentHtlcInfo, int, Optional[OnionPacket]], None]:
"""Creates multiple routes for splitting a payment over the available """Creates multiple routes for splitting a payment over the available
@ -1853,7 +1855,7 @@ class LNWallet(LNWorker):
try: try:
if self.uses_trampoline(): if self.uses_trampoline():
per_trampoline_channel_amounts = defaultdict(list) per_trampoline_channel_amounts = defaultdict(list)
# categorize by trampoline nodes for trampolin mpp construction # categorize by trampoline nodes for trampoline mpp construction
for (chan_id, _), part_amounts_msat in sc.config.items(): for (chan_id, _), part_amounts_msat in sc.config.items():
chan = self.channels[chan_id] chan = self.channels[chan_id]
for part_amount_msat in part_amounts_msat: for part_amount_msat in part_amounts_msat:
@ -1883,7 +1885,9 @@ class LNWallet(LNWorker):
local_height=local_height, local_height=local_height,
trampoline_fee_level=paysession.trampoline_fee_level, trampoline_fee_level=paysession.trampoline_fee_level,
use_two_trampolines=paysession.use_two_trampolines, use_two_trampolines=paysession.use_two_trampolines,
failed_routes=paysession.failed_trampoline_routes) failed_routes=paysession.failed_trampoline_routes,
budget=budget._replace(fee_msat=budget.fee_msat // len(per_trampoline_channel_amounts)),
)
# node_features is only used to determine is_tlv # node_features is only used to determine is_tlv
per_trampoline_secret = os.urandom(32) per_trampoline_secret = os.urandom(32)
per_trampoline_fees = per_trampoline_amount_with_fees - per_trampoline_amount per_trampoline_fees = per_trampoline_amount_with_fees - per_trampoline_amount
@ -1930,7 +1934,7 @@ class LNWallet(LNWorker):
channel = self.channels[chan_id] channel = self.channels[chan_id]
route = await run_in_thread( route = await run_in_thread(
partial( partial(
self.create_route_for_payment, self.create_route_for_single_htlc,
amount_msat=part_amount_msat, amount_msat=part_amount_msat,
invoice_pubkey=paysession.invoice_pubkey, invoice_pubkey=paysession.invoice_pubkey,
min_final_cltv_delta=paysession.min_final_cltv_delta, min_final_cltv_delta=paysession.min_final_cltv_delta,
@ -1938,6 +1942,7 @@ class LNWallet(LNWorker):
invoice_features=paysession.invoice_features, invoice_features=paysession.invoice_features,
my_sending_channels=[channel] if is_multichan_mpp else my_active_channels, my_sending_channels=[channel] if is_multichan_mpp else my_active_channels,
full_path=full_path, full_path=full_path,
budget=budget._replace(fee_msat=budget.fee_msat // sc.config.number_parts()),
) )
) )
shi = SentHtlcInfo( shi = SentHtlcInfo(
@ -1959,15 +1964,17 @@ class LNWallet(LNWorker):
raise NoPathFound() raise NoPathFound()
@profiler @profiler
def create_route_for_payment( def create_route_for_single_htlc(
self, *, self, *,
amount_msat: int, amount_msat: int, # that final receiver gets
invoice_pubkey: bytes, invoice_pubkey: bytes,
min_final_cltv_delta: int, min_final_cltv_delta: int,
r_tags, r_tags,
invoice_features: int, invoice_features: int,
my_sending_channels: List[Channel], my_sending_channels: List[Channel],
full_path: Optional[LNPaymentPath]) -> LNPaymentRoute: full_path: Optional[LNPaymentPath],
budget: PaymentFeeBudget,
) -> LNPaymentRoute:
my_sending_aliases = set(chan.get_local_scid_alias() for chan in my_sending_channels) my_sending_aliases = set(chan.get_local_scid_alias() for chan in my_sending_channels)
my_sending_channels = {chan.short_channel_id: chan for chan in my_sending_channels my_sending_channels = {chan.short_channel_id: chan for chan in my_sending_channels
@ -2022,9 +2029,10 @@ class LNWallet(LNWorker):
raise NoPathFound() from e raise NoPathFound() from e
if not route: if not route:
raise NoPathFound() raise NoPathFound()
# test sanity if not is_route_within_budget(
if not is_route_sane_to_use(route, amount_msat_for_dest=amount_msat, cltv_delta_for_dest=min_final_cltv_delta): route, budget=budget, amount_msat_for_dest=amount_msat, cltv_delta_for_dest=min_final_cltv_delta,
self.logger.info(f"rejecting insane route {route}") ):
self.logger.info(f"rejecting route (exceeds budget): {route=}. {budget=}")
raise NoPathFound() raise NoPathFound()
assert len(route) > 0 assert len(route) > 0
if route[-1].end_node != invoice_pubkey: if route[-1].end_node != invoice_pubkey:

8
electrum/tests/test_lnpeer.py

@ -27,7 +27,7 @@ from electrum.bitcoin import COIN, sha256
from electrum.util import NetworkRetryManager, bfh, OldTaskGroup, EventListener, InvoiceError from electrum.util import NetworkRetryManager, bfh, OldTaskGroup, EventListener, InvoiceError
from electrum.lnpeer import Peer from electrum.lnpeer import Peer
from electrum.lnutil import LNPeerAddr, Keypair, privkey_to_pubkey from electrum.lnutil import LNPeerAddr, Keypair, privkey_to_pubkey
from electrum.lnutil import PaymentFailure, LnFeatures, HTLCOwner from electrum.lnutil import PaymentFailure, LnFeatures, HTLCOwner, PaymentFeeBudget
from electrum.lnchannel import ChannelState, PeerState, Channel from electrum.lnchannel import ChannelState, PeerState, Channel
from electrum.lnrouter import LNPathFinder, PathEdge, LNPathInconsistent from electrum.lnrouter import LNPathFinder, PathEdge, LNPathInconsistent
from electrum.channel_db import ChannelDB from electrum.channel_db import ChannelDB
@ -250,7 +250,9 @@ class MockLNWallet(Logger, EventListener, NetworkRetryManager[LNPeerAddr]):
return [r async for r in self.create_routes_for_payment( return [r async for r in self.create_routes_for_payment(
amount_msat=amount_msat, amount_msat=amount_msat,
paysession=paysession, paysession=paysession,
full_path=full_path)] full_path=full_path,
budget=PaymentFeeBudget.default(invoice_amount_msat=amount_msat),
)]
get_payments = LNWallet.get_payments get_payments = LNWallet.get_payments
get_payment_secret = LNWallet.get_payment_secret get_payment_secret = LNWallet.get_payment_secret
@ -265,7 +267,7 @@ class MockLNWallet(Logger, EventListener, NetworkRetryManager[LNPeerAddr]):
htlc_failed = LNWallet.htlc_failed htlc_failed = LNWallet.htlc_failed
save_preimage = LNWallet.save_preimage save_preimage = LNWallet.save_preimage
get_preimage = LNWallet.get_preimage get_preimage = LNWallet.get_preimage
create_route_for_payment = LNWallet.create_route_for_payment create_route_for_single_htlc = LNWallet.create_route_for_single_htlc
create_routes_for_payment = LNWallet.create_routes_for_payment create_routes_for_payment = LNWallet.create_routes_for_payment
_check_invoice = LNWallet._check_invoice _check_invoice = LNWallet._check_invoice
pay_to_route = LNWallet.pay_to_route pay_to_route = LNWallet.pay_to_route

12
electrum/trampoline.py

@ -4,9 +4,9 @@ import random
from typing import Mapping, DefaultDict, Tuple, Optional, Dict, List, Iterable, Sequence, Set from typing import Mapping, DefaultDict, Tuple, Optional, Dict, List, Iterable, Sequence, Set
from .lnutil import LnFeatures from .lnutil import LnFeatures, PaymentFeeBudget
from .lnonion import calc_hops_data_for_payment, new_onion_packet, OnionPacket from .lnonion import calc_hops_data_for_payment, new_onion_packet, OnionPacket
from .lnrouter import RouteEdge, TrampolineEdge, LNPaymentRoute, is_route_sane_to_use, LNPaymentTRoute from .lnrouter import RouteEdge, TrampolineEdge, LNPaymentRoute, is_route_within_budget, LNPaymentTRoute
from .lnutil import NoPathFound, LNPeerAddr from .lnutil import NoPathFound, LNPeerAddr
from . import constants from . import constants
from .logging import get_logger from .logging import get_logger
@ -222,6 +222,7 @@ def create_trampoline_route(
trampoline_fee_level: int, trampoline_fee_level: int,
use_two_trampolines: bool, use_two_trampolines: bool,
failed_routes: Iterable[Sequence[str]], failed_routes: Iterable[Sequence[str]],
budget: PaymentFeeBudget,
) -> LNPaymentTRoute: ) -> LNPaymentTRoute:
# we decide whether to convert to a legacy payment # we decide whether to convert to a legacy payment
is_legacy, invoice_trampolines = is_legacy_relay(invoice_features, r_tags) is_legacy, invoice_trampolines = is_legacy_relay(invoice_features, r_tags)
@ -268,12 +269,13 @@ def create_trampoline_route(
# Also needed for fees for last TF! # Also needed for fees for last TF!
_extend_trampoline_route(route, end_node=invoice_pubkey, trampoline_fee_level=trampoline_fee_level) _extend_trampoline_route(route, end_node=invoice_pubkey, trampoline_fee_level=trampoline_fee_level)
# check that we can pay amount and fees # check that we can pay amount and fees
if not is_route_sane_to_use( if not is_route_within_budget(
route=route, route=route,
budget=budget,
amount_msat_for_dest=amount_msat, amount_msat_for_dest=amount_msat,
cltv_delta_for_dest=min_final_cltv_delta, cltv_delta_for_dest=min_final_cltv_delta,
): ):
raise NoPathFound("We cannot afford to pay the fees.") raise NoPathFound("route exceeds budget")
return route return route
@ -342,6 +344,7 @@ def create_trampoline_route_and_onion(
trampoline_fee_level: int, trampoline_fee_level: int,
use_two_trampolines: bool, use_two_trampolines: bool,
failed_routes: Iterable[Sequence[str]], failed_routes: Iterable[Sequence[str]],
budget: PaymentFeeBudget,
) -> Tuple[LNPaymentTRoute, OnionPacket, int, int]: ) -> Tuple[LNPaymentTRoute, OnionPacket, int, int]:
# create route for the trampoline_onion # create route for the trampoline_onion
trampoline_route = create_trampoline_route( trampoline_route = create_trampoline_route(
@ -355,6 +358,7 @@ def create_trampoline_route_and_onion(
trampoline_fee_level=trampoline_fee_level, trampoline_fee_level=trampoline_fee_level,
use_two_trampolines=use_two_trampolines, use_two_trampolines=use_two_trampolines,
failed_routes=failed_routes, failed_routes=failed_routes,
budget=budget,
) )
# compute onion and fees # compute onion and fees
final_cltv_abs = local_height + min_final_cltv_delta final_cltv_abs = local_height + min_final_cltv_delta

Loading…
Cancel
Save