@ -66,12 +66,12 @@ from .lnutil import (Outpoint, LNPeerAddr,
UpdateAddHtlc , Direction , LnFeatures , ShortChannelID ,
HtlcLog , derive_payment_secret_from_payment_preimage ,
NoPathFound , InvalidGossipMsg )
from . lnutil import ln_compare_features , IncompatibleLightningFeatures
from . lnutil import ln_compare_features , IncompatibleLightningFeatures , PaymentFeeBudget
from . transaction import PartialTxOutput , PartialTransaction , PartialTxInput
from . lnonion import decode_onion_error , OnionFailureCode , OnionRoutingFailure , OnionPacket
from . lnmsg import decode_msg
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 )
from . address_synchronizer import TX_HEIGHT_LOCAL , TX_TIMESTAMP_INF
from . import lnsweep
@ -662,7 +662,7 @@ class PaySession(Logger):
initial_trampoline_fee_level : int ,
invoice_features : int ,
r_tags ,
min_final_cltv_delta : int , # delta for last edg e (typically from invoice)
min_final_cltv_delta : int , # delta for last nod e (typically from invoice)
amount_to_pay : int , # total payment amount final receiver will get
invoice_pubkey : bytes ,
uses_trampoline : bool , # whether sender uses trampoline or gossip
@ -1419,6 +1419,7 @@ class LNWallet(LNWorker):
f " using_trampoline= { self . uses_trampoline ( ) } . "
f " invoice_features= { invoice_features . get_names ( ) } " )
self . set_invoice_status ( key , PR_INFLIGHT )
budget = PaymentFeeBudget . default ( invoice_amount_msat = amount_to_pay )
success = False
try :
await self . pay_to_node (
@ -1431,7 +1432,9 @@ class LNWallet(LNWorker):
invoice_features = invoice_features ,
attempts = attempts ,
full_path = full_path ,
channels = channels )
channels = channels ,
budget = budget ,
)
success = True
except PaymentFailure as e :
self . logger . info ( f ' payment failure: { e !r} ' )
@ -1462,17 +1465,13 @@ class LNWallet(LNWorker):
attempts : int = None ,
full_path : LNPaymentPath = None ,
fwd_trampoline_onion : OnionPacket = None ,
fwd_trampoline_fee : int = None ,
fwd_trampoline_cltv_delta : int = None ,
budget : PaymentFeeBudget ,
channels : Optional [ Sequence [ Channel ] ] = None ,
) - > None :
if fwd_trampoline_onion :
# todo: compare to the fee of the actual route we found
if fwd_trampoline_fee < 1000 :
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 ' ' )
assert budget
assert budget . fee_msat > = 0 , budget
assert budget . cltv > = 0 , budget
payment_key = payment_hash + payment_secret
assert payment_key not in self . _paysessions
@ -1499,12 +1498,14 @@ class LNWallet(LNWorker):
# 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
# 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 (
paysession = paysession ,
amount_msat = amount_to_send ,
full_path = full_path ,
fwd_trampoline_onion = fwd_trampoline_onion ,
channels = channels ,
budget = budget . _replace ( fee_msat = remaining_fee_budget_msat ) ,
)
# 2. send htlcs
async for sent_htlc_info , cltv_delta , trampoline_onion in routes :
@ -1815,6 +1816,7 @@ class LNWallet(LNWorker):
fwd_trampoline_onion : OnionPacket = None ,
full_path : LNPaymentPath = None ,
channels : Optional [ Sequence [ Channel ] ] = None ,
budget : PaymentFeeBudget ,
) - > AsyncGenerator [ Tuple [ SentHtlcInfo , int , Optional [ OnionPacket ] ] , None ] :
""" Creates multiple routes for splitting a payment over the available
@ -1853,7 +1855,7 @@ class LNWallet(LNWorker):
try :
if self . uses_trampoline ( ) :
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 ( ) :
chan = self . channels [ chan_id ]
for part_amount_msat in part_amounts_msat :
@ -1883,7 +1885,9 @@ class LNWallet(LNWorker):
local_height = local_height ,
trampoline_fee_level = paysession . trampoline_fee_level ,
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
per_trampoline_secret = os . urandom ( 32 )
per_trampoline_fees = per_trampoline_amount_with_fees - per_trampoline_amount
@ -1930,7 +1934,7 @@ class LNWallet(LNWorker):
channel = self . channels [ chan_id ]
route = await run_in_thread (
partial (
self . create_route_for_payment ,
self . create_route_for_single_htlc ,
amount_msat = part_amount_msat ,
invoice_pubkey = paysession . invoice_pubkey ,
min_final_cltv_delta = paysession . min_final_cltv_delta ,
@ -1938,6 +1942,7 @@ class LNWallet(LNWorker):
invoice_features = paysession . invoice_features ,
my_sending_channels = [ channel ] if is_multichan_mpp else my_active_channels ,
full_path = full_path ,
budget = budget . _replace ( fee_msat = budget . fee_msat / / sc . config . number_parts ( ) ) ,
)
)
shi = SentHtlcInfo (
@ -1959,15 +1964,17 @@ class LNWallet(LNWorker):
raise NoPathFound ( )
@profiler
def create_route_for_payment (
def create_route_for_single_htlc (
self , * ,
amount_msat : int ,
amount_msat : int , # that final receiver gets
invoice_pubkey : bytes ,
min_final_cltv_delta : int ,
r_tags ,
invoice_features : int ,
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_channels = { chan . short_channel_id : chan for chan in my_sending_channels
@ -2022,9 +2029,10 @@ class LNWallet(LNWorker):
raise NoPathFound ( ) from e
if not route :
raise NoPathFound ( )
# test sanity
if not is_route_sane_to_use ( route , amount_msat_for_dest = amount_msat , cltv_delta_for_dest = min_final_cltv_delta ) :
self . logger . info ( f " rejecting insane route { route } " )
if not is_route_within_budget (
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 ( )
assert len ( route ) > 0
if route [ - 1 ] . end_node != invoice_pubkey :