1
0

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")
This commit is contained in:
SomberNight
2023-10-27 16:01:23 +00:00
parent 53a8453e3b
commit 6506abf583
6 changed files with 95 additions and 58 deletions

View File

@@ -35,7 +35,7 @@ from math import inf
from .util import profiler, with_lock
from .logging import Logger
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
if TYPE_CHECKING:
@@ -111,7 +111,7 @@ class RouteEdge(PathEdge):
if self.cltv_delta > 14 * 144:
return False
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 True
@@ -138,38 +138,41 @@ LNPaymentRoute = Sequence[RouteEdge]
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.
called when we are paying; so e.g. lower cltv is better
"""
if len(route) > NUM_MAX_EDGES_IN_PAYMENT_PATH:
return False
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:]):
if not route_edge.is_sane_to_use(amt): return False
amt += route_edge.fee_for_edge(amt)
cltv_delta += route_edge.cltv_delta
total_fee = amt - amount_msat_for_dest
# TODO revise ad-hoc heuristics
if cltv_delta > NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE:
cltv_cost_of_route += route_edge.cltv_delta
fee_cost = amt - amount_msat_for_dest
# check against budget
if cltv_cost_of_route > budget.cltv:
return False
# FIXME in case of MPP, the fee checks are done independently for each part,
# which is ok for the proportional checks but not for the absolute ones.
# This is not that big of a deal though as we don't split into *too many* parts.
if not is_fee_sane(total_fee, payment_amount_msat=amount_msat_for_dest):
if fee_cost > budget.fee_msat:
return False
# sanity check
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 True
def is_fee_sane(fee_msat: int, *, payment_amount_msat: int) -> bool:
# fees <= 5 sat are fine
if fee_msat <= 5_000:
return True
def get_default_fee_budget_msat(*, invoice_amount_msat: int) -> int:
# fees <= 1 % of payment are fine
if 100 * fee_msat <= payment_amount_msat:
return True
return False
# fees <= 5 sat are fine
return max(5_000, invoice_amount_msat // 100)
class LiquidityHint: