@@ -1196,8 +1196,8 @@ where
11961196 payment_data : & SendPaymentData ,
11971197 ) -> Result < Vec < PaymentHopData > , PathFindError > {
11981198 info ! (
1199- "entered build_route: amount: {}, amount_low_bound: {:?}" ,
1200- amount, amount_low_bound
1199+ "entered build_route: amount: {}, amount_low_bound: {:?}, max_fee_amount: {:?} " ,
1200+ amount, amount_low_bound, max_fee_amount
12011201 ) ;
12021202 let source = self . get_source_pubkey ( ) ;
12031203 let target = payment_data. target_pubkey ;
@@ -1307,36 +1307,8 @@ where
13071307 // Prefer the per-call override from `build_route` when present, else fall back to
13081308 // the budget carried in `payment_data`.
13091309 let max_fee_amount = max_fee_amount. or ( payment_data. max_fee_amount ) ;
1310- let total_fee_budget = max_fee_amount. unwrap_or ( 0 ) ;
13111310
13121311 let secp = Secp256k1 :: new ( ) ;
1313- let trampoline_forward_expiry_delta =
1314- |base_final : u64 ,
1315- remaining_trampoline_hops : & [ crate :: fiber:: payment:: TrampolineHop ] ,
1316- tlc_expiry_limit : u64 |
1317- -> Result < u64 , PathFindError > {
1318- let slack = remaining_trampoline_hops
1319- . iter ( )
1320- . map ( |h| h. tlc_expiry_delta . unwrap_or ( DEFAULT_TLC_EXPIRY_DELTA ) )
1321- . try_fold ( 0u64 , |acc, d| {
1322- acc. checked_add ( d) . ok_or_else ( || {
1323- PathFindError :: Other ( "trampoline tlc_expiry_delta overflow" . to_string ( ) )
1324- } )
1325- } ) ?;
1326-
1327- let total = base_final. checked_add ( slack) . ok_or_else ( || {
1328- PathFindError :: Other ( "trampoline tlc_expiry_delta overflow" . to_string ( ) )
1329- } ) ?;
1330-
1331- if total > tlc_expiry_limit {
1332- return Err ( PathFindError :: Other ( format ! (
1333- "trampoline tlc_expiry_delta exceeds tlc_expiry_limit: {} > {}" ,
1334- total, tlc_expiry_limit
1335- ) ) ) ;
1336- }
1337-
1338- Ok ( total)
1339- } ;
13401312
13411313 if let Some ( hops) = payment_data. trampoline_hops ( ) . filter ( |h| !h. is_empty ( ) ) {
13421314 let first = hops[ 0 ] . pubkey ;
@@ -1372,7 +1344,7 @@ where
13721344 // `forward_amounts[idx]` is the *minimum incoming amount* required by the next hop to
13731345 // cover its own trampoline service fee.
13741346 let mut forward_amounts = vec ! [ 0u128 ; hops. len( ) ] ;
1375- let mut min_incoming_for_service = vec ! [ 0u128 ; hops. len( ) ] ;
1347+ let mut forwarding_amounts = vec ! [ 0u128 ; hops. len( ) ] ;
13761348 let mut next_amount_to_forward = final_amount;
13771349 for ( idx, hop) in hops. iter ( ) . enumerate ( ) . rev ( ) {
13781350 forward_amounts[ idx] = next_amount_to_forward;
@@ -1388,55 +1360,57 @@ where
13881360 ) ?;
13891361
13901362 next_amount_to_forward = next_amount_to_forward. saturating_add ( fee) ;
1391- min_incoming_for_service [ idx] = next_amount_to_forward;
1363+ forwarding_amounts [ idx] = next_amount_to_forward;
13921364 }
13931365
13941366 let amount_to_first_trampoline = next_amount_to_forward;
13951367 let trampoline_service_fee_total =
13961368 amount_to_first_trampoline. saturating_sub ( final_amount) ;
13971369
1398- // Only enforce the service-fee budget constraint when the caller explicitly
1399- // provided `max_fee_amount`. When unset, we treat it as "no explicit fee cap"
1400- // (matching the historical behavior of these graph unit tests).
1401- if max_fee_amount. is_some ( ) && trampoline_service_fee_total > total_fee_budget {
1402- return Err ( PathFindError :: Other ( format ! (
1403- "max_fee_amount too low for trampoline service fees: required={}, budget={}" ,
1404- trampoline_service_fee_total, total_fee_budget
1405- ) ) ) ;
1370+ // if max_fee_amount is not specified, use default fee calculation
1371+ let single_forward_fee = calculate_tlc_forward_fee (
1372+ amount_to_first_trampoline,
1373+ crate :: fiber:: channel:: DEFAULT_FEE_RATE as u128 ,
1374+ )
1375+ . map_err ( |e| PathFindError :: Other ( format ! ( "invalid default fee calculation: {e}" ) ) ) ?;
1376+ let default_min_fee_amount =
1377+ trampoline_service_fee_total + hops. len ( ) as u128 * single_forward_fee;
1378+
1379+ // Give a recommend max fee amount that is 10x the single forward fee for routing
1380+ let default_max_fee_amount =
1381+ trampoline_service_fee_total + hops. len ( ) as u128 * single_forward_fee * 10 ;
1382+
1383+ if let Some ( total_fee) = max_fee_amount {
1384+ if default_min_fee_amount > total_fee {
1385+ return Err ( PathFindError :: Other ( format ! (
1386+ "max_fee_amount too low for trampoline service fees: recommend_minimal_fee={}, maximal_fee={} current_fee={}" ,
1387+ default_min_fee_amount, default_max_fee_amount, total_fee
1388+ ) ) ) ;
1389+ }
14061390 }
14071391
1392+ let total_fee_budget = max_fee_amount. unwrap_or ( default_min_fee_amount) ;
14081393 let remaining_budget = total_fee_budget. saturating_sub ( trampoline_service_fee_total) ;
14091394
1410- // Split remaining budget proportionally to forwarded amounts :
1395+ // Split remaining budget evenly across :
14111396 // - index 0: payer routing to first trampoline
14121397 // - index i+1: routing budget available at trampoline i
1413- let mut budget_weights: Vec < u128 > = Vec :: with_capacity ( hops. len ( ) + 1 ) ;
1414- budget_weights. push ( amount_to_first_trampoline) ;
1415- debug_assert_eq ! ( forward_amounts. len( ) , hops. len( ) ) ;
1416- for amount in & forward_amounts {
1417- budget_weights. push ( * amount) ;
1418- }
1419-
1420- let weight_sum: u128 = budget_weights. iter ( ) . copied ( ) . sum ( ) ;
1421- let mut budgets = vec ! [ 0u128 ; budget_weights. len( ) ] ;
1422- if remaining_budget > 0 && weight_sum > 0 {
1423- let mut allocated = 0u128 ;
1424- for ( idx, w) in budget_weights. iter ( ) . copied ( ) . enumerate ( ) {
1425- let b = remaining_budget. saturating_mul ( w) / weight_sum;
1426- budgets[ idx] = b;
1427- allocated = allocated. saturating_add ( b) ;
1428- }
1429-
1430- // Distribute rounding remainder deterministically.
1431- let remainder = remaining_budget. saturating_sub ( allocated) ;
1432- debug_assert ! ( remainder < budgets. len( ) as u128 ) ;
1433- let r = remainder as usize ;
1434- for budget in budgets. iter_mut ( ) . take ( r) {
1398+ let mut budgets = vec ! [ 0u128 ; hops. len( ) + 1 ] ;
1399+ if remaining_budget > 0 {
1400+ let slots = budgets. len ( ) as u128 ;
1401+ let base = remaining_budget / slots;
1402+ let remainder = ( remaining_budget % slots) as usize ;
1403+ budgets. iter_mut ( ) . for_each ( |b| * b = base) ;
1404+ for budget in budgets. iter_mut ( ) . take ( remainder) {
14351405 * budget = budget. saturating_add ( 1 ) ;
14361406 }
14371407 }
14381408
1439- let payer_routing_budget = budgets[ 0 ] ;
1409+ let payer_routing_budget = if max_fee_amount. is_some ( ) {
1410+ Some ( budgets[ 0 ] )
1411+ } else {
1412+ None
1413+ } ;
14401414 let per_trampoline_routing_budgets = & budgets[ 1 ..] ;
14411415
14421416 // The first trampoline must receive the total expected incoming amount, which consists of:
@@ -1449,13 +1423,17 @@ where
14491423 let amount_to_trampoline = amount_to_first_trampoline
14501424 . saturating_add ( per_trampoline_routing_budgets. first ( ) . copied ( ) . unwrap_or ( 0 ) ) ;
14511425
1426+ debug ! (
1427+ "now got amount_to_trampoline: {:?} budgets: {:?}" ,
1428+ amount_to_trampoline, budgets
1429+ ) ;
14521430 let route_to_trampoline = self . find_path (
14531431 source,
14541432 first,
14551433 Some ( amount_to_trampoline) ,
1456- Some ( payer_routing_budget) ,
1434+ payer_routing_budget,
14571435 payment_data. udt_type_script . clone ( ) ,
1458- trampoline_forward_expiry_delta (
1436+ self . trampoline_forward_expiry_delta (
14591437 payment_data. final_tlc_expiry_delta ,
14601438 hops,
14611439 payment_data. tlc_expiry_limit ,
@@ -1512,7 +1490,7 @@ where
15121490 next_node_id,
15131491 amount_to_forward,
15141492 build_max_fee_amount,
1515- tlc_expiry_delta : trampoline_forward_expiry_delta (
1493+ tlc_expiry_delta : self . trampoline_forward_expiry_delta (
15161494 payment_data. final_tlc_expiry_delta ,
15171495 remaining_trampoline_hops,
15181496 payment_data. tlc_expiry_limit ,
@@ -1546,7 +1524,7 @@ where
15461524 hops : route_to_trampoline,
15471525 amount : amount_to_trampoline,
15481526 trampoline_onion : Some ( trampoline_onion) ,
1549- final_hop_expiry_delta_override : Some ( trampoline_forward_expiry_delta (
1527+ final_hop_expiry_delta_override : Some ( self . trampoline_forward_expiry_delta (
15501528 payment_data. final_tlc_expiry_delta ,
15511529 hops,
15521530 payment_data. tlc_expiry_limit ,
@@ -1558,6 +1536,35 @@ where
15581536 Err ( PathFindError :: NoPathFound )
15591537 }
15601538
1539+ fn trampoline_forward_expiry_delta (
1540+ & self ,
1541+ base_final : u64 ,
1542+ remaining_trampoline_hops : & [ crate :: fiber:: payment:: TrampolineHop ] ,
1543+ tlc_expiry_limit : u64 ,
1544+ ) -> Result < u64 , PathFindError > {
1545+ let slack = remaining_trampoline_hops
1546+ . iter ( )
1547+ . map ( |h| h. tlc_expiry_delta . unwrap_or ( DEFAULT_TLC_EXPIRY_DELTA ) )
1548+ . try_fold ( 0u64 , |acc, d| {
1549+ acc. checked_add ( d) . ok_or_else ( || {
1550+ PathFindError :: Other ( "trampoline tlc_expiry_delta overflow" . to_string ( ) )
1551+ } )
1552+ } ) ?;
1553+
1554+ let total = base_final. checked_add ( slack) . ok_or_else ( || {
1555+ PathFindError :: Other ( "trampoline tlc_expiry_delta overflow" . to_string ( ) )
1556+ } ) ?;
1557+
1558+ if total > tlc_expiry_limit {
1559+ return Err ( PathFindError :: Other ( format ! (
1560+ "trampoline tlc_expiry_delta exceeds tlc_expiry_limit: {} > {}" ,
1561+ total, tlc_expiry_limit
1562+ ) ) ) ;
1563+ }
1564+
1565+ Ok ( total)
1566+ }
1567+
15611568 fn find_path_with_payment_data (
15621569 & self ,
15631570 source : Pubkey ,
@@ -2125,8 +2132,8 @@ where
21252132 route : Option < & [ RouterHop ] > ,
21262133 ) -> Result < Vec < RouterHop > , PathFindError > {
21272134 debug ! (
2128- "begin inner_find_path from {:?} to {:?} amount: {:?}" ,
2129- source, target, amount
2135+ "begin inner_find_path from {:?} to {:?} amount: {:?} max_fee: {:?} " ,
2136+ source, target, amount, max_fee_amount
21302137 ) ;
21312138 let started_time = crate :: time:: Instant :: now ( ) ;
21322139 let nodes_len = self . nodes . len ( ) ;
0 commit comments