diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py index af23a75db94a..e563382e9fb6 100644 --- a/electrum/lnpeer.py +++ b/electrum/lnpeer.py @@ -44,7 +44,8 @@ ln_compare_features, MIN_FINAL_CLTV_DELTA_ACCEPTED, RemoteMisbehaving, ShortChannelID, IncompatibleLightningFeatures, derive_payment_secret_from_payment_preimage, - ChannelType, LNProtocolWarning, validate_features, IncompatibleOrInsaneFeatures) + ChannelType, LNProtocolWarning, validate_features, + IncompatibleOrInsaneFeatures, FeeBudgetExceeded) from .lnutil import FeeUpdate, channel_id_from_funding_tx, PaymentFeeBudget from .lnutil import serialize_htlc_key, Keypair from .lntransport import LNTransport, LNTransportBase, LightningPeerConnectionClosed, HandshakeFailed @@ -2063,11 +2064,12 @@ async def maybe_forward_trampoline( ) except OnionRoutingFailure as e: raise + except FeeBudgetExceeded: + raise OnionRoutingFailure(code=OnionFailureCode.TRAMPOLINE_FEE_INSUFFICIENT, data=b'') except PaymentFailure as e: self.logger.debug( f"maybe_forward_trampoline. PaymentFailure for {payment_hash.hex()=}, {payment_secret.hex()=}: {e!r}") - # FIXME: adapt the error code - raise OnionRoutingFailure(code=OnionFailureCode.TRAMPOLINE_FEE_INSUFFICIENT, data=b'') + raise OnionRoutingFailure(code=OnionFailureCode.UNKNOWN_NEXT_PEER, data=b'') def _maybe_refuse_to_forward_htlc_that_corresponds_to_payreq_we_created(self, payment_hash: bytes) -> bool: """Returns True if the HTLC should be failed. diff --git a/electrum/lnutil.py b/electrum/lnutil.py index 3f64f9f005cb..1ad1703aeb75 100644 --- a/electrum/lnutil.py +++ b/electrum/lnutil.py @@ -452,6 +452,9 @@ class PaymentFailure(UserFacingException): pass class NoPathFound(PaymentFailure): def __str__(self): return _('No path found') +class FeeBudgetExceeded(PaymentFailure): + def __str__(self): + return _('Fee budget exceeded') class LNProtocolError(Exception): diff --git a/electrum/lnworker.py b/electrum/lnworker.py index d949e345a7a0..0a28371fb414 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -67,7 +67,7 @@ NUM_MAX_EDGES_IN_PAYMENT_PATH, SENT, RECEIVED, HTLCOwner, UpdateAddHtlc, Direction, LnFeatures, ShortChannelID, HtlcLog, derive_payment_secret_from_payment_preimage, - NoPathFound, InvalidGossipMsg) + NoPathFound, InvalidGossipMsg, FeeBudgetExceeded) from .lnutil import ln_compare_features, IncompatibleLightningFeatures, PaymentFeeBudget from .transaction import PartialTxOutput, PartialTransaction, PartialTxInput from .lnonion import decode_onion_error, OnionFailureCode, OnionRoutingFailure, OnionPacket @@ -1880,6 +1880,7 @@ async def create_routes_for_payment( and mpp is supported by the receiver, we will split the payment.""" trampoline_features = LnFeatures.VAR_ONION_OPT local_height = self.network.get_local_height() + fee_related_error = None # type: Optional[FeeBudgetExceeded] if channels: my_active_channels = channels else: @@ -1970,8 +1971,9 @@ async def create_routes_for_payment( ) routes.append((shi, per_trampoline_cltv_delta, trampoline_onion)) if per_trampoline_fees != 0: - self.logger.info('not enough margin to pay trampoline fee') - raise NoPathFound() + e = 'not enough margin to pay trampoline fee' + self.logger.info(e) + raise FeeBudgetExceeded(e) else: # We atomically loop through a split configuration. If there was # a failure to find a path for a single part, we try the next configuration @@ -2004,9 +2006,14 @@ async def create_routes_for_payment( routes.append((shi, paysession.min_final_cltv_delta, fwd_trampoline_onion)) except NoPathFound: continue + except FeeBudgetExceeded as e: + fee_related_error = e + continue for route in routes: yield route return + if fee_related_error is not None: + raise fee_related_error raise NoPathFound() @profiler @@ -2079,7 +2086,7 @@ def create_route_for_single_htlc( route, budget=budget, amount_msat_for_dest=amount_msat, cltv_delta_for_dest=min_final_cltv_delta, ): self.logger.info(f"rejecting route (exceeds budget): {route=}. {budget=}") - raise NoPathFound() + raise FeeBudgetExceeded() assert len(route) > 0 if route[-1].end_node != invoice_pubkey: raise LNPathInconsistent("last node_id != invoice pubkey") diff --git a/electrum/trampoline.py b/electrum/trampoline.py index 2c10992baf3e..04777406d5e4 100644 --- a/electrum/trampoline.py +++ b/electrum/trampoline.py @@ -3,7 +3,7 @@ import random from typing import Mapping, DefaultDict, Tuple, Optional, Dict, List, Iterable, Sequence, Set, Any -from .lnutil import LnFeatures, PaymentFeeBudget +from .lnutil import LnFeatures, PaymentFeeBudget, FeeBudgetExceeded from .lnonion import calc_hops_data_for_payment, new_onion_packet, OnionPacket from .lnrouter import RouteEdge, TrampolineEdge, LNPaymentRoute, is_route_within_budget, LNPaymentTRoute from .lnutil import NoPathFound @@ -161,7 +161,7 @@ def _allocate_fee_along_route( assert trampoline_fee_level > 0 MAX_LEVEL = 6 if trampoline_fee_level > MAX_LEVEL: - raise NoPathFound() + raise FeeBudgetExceeded("highest trampoline fee level reached") budget_to_use = budget.fee_msat // (2 ** (MAX_LEVEL - trampoline_fee_level)) _logger.debug(f"_allocate_fee_along_route(). {trampoline_fee_level=}, {budget.fee_msat=}, {budget_to_use=}") # replace placeholder fees @@ -264,7 +264,7 @@ def create_trampoline_route( amount_msat_for_dest=amount_msat, cltv_delta_for_dest=min_final_cltv_delta, ): - raise NoPathFound("route exceeds budget") + raise FeeBudgetExceeded(f"route exceeds budget: budget: {budget}") return route