Browse Source

lnworker: make PaymentFeeBudget defaults configurable

- make PaymentFeeBudget proportional fee and flat cutoff fee configurable
  - closes https://github.com/spesmilo/electrum/issues/7622
- increase flat cutoff fee default to 10 sat
  - closes https://github.com/spesmilo/electrum/issues/7669
- rm RouteEdge.is_sane_to_use() (per edge limit) and just rely on budgets (per route limit)
master
SomberNight 2 years ago
parent
commit
67d373357b
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 22
      electrum/lnrouter.py
  2. 21
      electrum/lnutil.py
  3. 2
      electrum/lnworker.py
  4. 10
      electrum/simple_config.py
  5. 2
      tests/test_lnpeer.py

22
electrum/lnrouter.py

@ -105,16 +105,6 @@ class RouteEdge(PathEdge):
cltv_delta=channel_policy.cltv_delta,
node_features=node_info.features if node_info else 0)
def is_sane_to_use(self, amount_msat: int) -> bool:
# TODO revise ad-hoc heuristics
# cltv cannot be more than 2 weeks
if self.cltv_delta > 14 * 144:
return False
total_fee = self.fee_for_edge(amount_msat)
if total_fee > get_default_fee_budget_msat(invoice_amount_msat=amount_msat):
return False
return True
def has_feature_varonion(self) -> bool:
features = LnFeatures(self.node_features)
return features.supports(LnFeatures.VAR_ONION_OPT)
@ -153,7 +143,6 @@ def is_route_within_budget(
amt = amount_msat_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_cost_of_route += route_edge.cltv_delta
fee_cost = amt - amount_msat_for_dest
@ -169,12 +158,6 @@ def is_route_within_budget(
return True
def get_default_fee_budget_msat(*, invoice_amount_msat: int) -> int:
# fees <= 1 % of payment are fine
# fees <= 5 sat are fine
return max(5_000, invoice_amount_msat // 100)
class LiquidityHint:
"""Encodes the amounts that can and cannot be sent over the direction of a
channel.
@ -520,8 +503,9 @@ class LNPathFinder(Logger):
start_node=start_node,
end_node=end_node,
node_info=node_info)
if not route_edge.is_sane_to_use(payment_amt_msat):
return float('inf'), 0 # thanks but no thanks
# Cap cltv of any given edge at 2 weeks (the cost function would not work well for extreme cases)
if route_edge.cltv_delta > 14 * 144:
return float('inf'), 0
# Distance metric notes: # TODO constants are ad-hoc
# ( somewhat based on https://github.com/lightningnetwork/lnd/pull/1358 )
# - Edges have a base cost. (more edges -> less likely none will fail)

21
electrum/lnutil.py

@ -1672,9 +1672,24 @@ class PaymentFeeBudget(NamedTuple):
#num_htlc: int
@classmethod
def default(cls, *, invoice_amount_msat: int) -> 'PaymentFeeBudget':
from .lnrouter import get_default_fee_budget_msat
def default(cls, *, invoice_amount_msat: int, config: 'SimpleConfig') -> 'PaymentFeeBudget':
millionths_orig = config.LIGHTNING_PAYMENT_FEE_MAX_MILLIONTHS
millionths = min(max(0, millionths_orig), 250_000) # clamp into [0, 25%]
cutoff_orig = config.LIGHTNING_PAYMENT_FEE_CUTOFF_MSAT
cutoff = min(max(0, cutoff_orig), 10_000_000) # clamp into [0, 10k sat]
if millionths != millionths_orig:
_logger.warning(
f"PaymentFeeBudget. found insane fee millionths in config. "
f"clamped: {millionths_orig}->{millionths}")
if cutoff != cutoff_orig:
_logger.warning(
f"PaymentFeeBudget. found insane fee cutoff in config. "
f"clamped: {cutoff_orig}->{cutoff}")
# for small payments, fees <= constant cutoff are fine
# for large payments, the max fee is percentage-based
fee_msat = invoice_amount_msat * millionths // 1_000_000
fee_msat = max(fee_msat, cutoff)
return PaymentFeeBudget(
fee_msat=get_default_fee_budget_msat(invoice_amount_msat=invoice_amount_msat),
fee_msat=fee_msat,
cltv=NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE,
)

2
electrum/lnworker.py

@ -1516,7 +1516,7 @@ class LNWallet(LNWorker):
f"num_channels={self.channel_db.num_channels}, "
f"num_policies={self.channel_db.num_policies}.")
self.set_invoice_status(key, PR_INFLIGHT)
budget = PaymentFeeBudget.default(invoice_amount_msat=amount_to_pay)
budget = PaymentFeeBudget.default(invoice_amount_msat=amount_to_pay, config=self.config)
success = False
try:
await self.pay_to_node(

10
electrum/simple_config.py

@ -1040,6 +1040,16 @@ Note you are at risk of losing the funds in the swap, if the funding transaction
This will result in longer routes; it might increase your fees and decrease the success rate of your payments."""),
)
INITIAL_TRAMPOLINE_FEE_LEVEL = ConfigVar('initial_trampoline_fee_level', default=1, type_=int)
LIGHTNING_PAYMENT_FEE_MAX_MILLIONTHS = ConfigVar(
'lightning_payment_fee_max_millionths', default=10_000, # 1%
type_=int,
short_desc=lambda: _("Max lightning fees (%) to pay"),
)
LIGHTNING_PAYMENT_FEE_CUTOFF_MSAT = ConfigVar(
'lightning_payment_fee_cutoff_msat', default=10_000, # 10 sat
type_=int,
short_desc=lambda: _("Max lightning fees to pay for small payments"),
)
LIGHTNING_NODE_ALIAS = ConfigVar('lightning_node_alias', default='', type_=str)
EXPERIMENTAL_LN_FORWARD_PAYMENTS = ConfigVar('lightning_forward_payments', default=False, type_=bool)

2
tests/test_lnpeer.py

@ -258,7 +258,7 @@ class MockLNWallet(Logger, EventListener, NetworkRetryManager[LNPeerAddr]):
amount_msat=amount_msat,
paysession=paysession,
full_path=full_path,
budget=PaymentFeeBudget.default(invoice_amount_msat=amount_msat),
budget=PaymentFeeBudget.default(invoice_amount_msat=amount_msat, config=self.config),
)]
get_payments = LNWallet.get_payments

Loading…
Cancel
Save