Skip to content

Commit d7ffef3

Browse files
committed
f: forward Trampoline payments with MPP
1 parent 569ad96 commit d7ffef3

File tree

1 file changed

+146
-145
lines changed

1 file changed

+146
-145
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 146 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,13 @@ use crate::ln::channel::{self, Channel, ChannelError, ChannelUpdateStatus, Funde
5454
use crate::ln::channel::PendingV2Channel;
5555
use crate::ln::channel_state::ChannelDetails;
5656
use crate::types::features::{Bolt12InvoiceFeatures, ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures};
57-
#[cfg(any(feature = "_test_utils", test))]
5857
use crate::types::features::Bolt11InvoiceFeatures;
59-
use crate::routing::router::{BlindedTail, FixedRouter, InFlightHtlcs, MAX_PATH_LENGTH_ESTIMATE, Path, Payee, PaymentParameters, Route, RouteHop, RouteParameters, RouteParametersConfig, Router};
58+
use crate::routing::router::{BlindedTail, DEFAULT_MAX_PATH_COUNT, FixedRouter, InFlightHtlcs, MAX_PATH_LENGTH_ESTIMATE, Path, Payee, PaymentParameters, Route, RouteHop, RouteParameters, RouteParametersConfig, Router};
6059
use crate::ln::onion_payment::{check_incoming_htlc_cltv, create_recv_pending_htlc_info, create_fwd_pending_htlc_info, decode_incoming_update_add_htlc_onion, HopConnector, InboundHTLCErr, NextPacketDetails};
6160
use crate::ln::msgs;
6261
use crate::ln::onion_utils::{self};
6362
use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING};
64-
use crate::ln::msgs::{BaseMessageHandler, ChannelMessageHandler, CommitmentUpdate, DecodeError, LightningError, MessageSendEvent};
63+
use crate::ln::msgs::{BaseMessageHandler, ChannelMessageHandler, CommitmentUpdate, DecodeError, FinalOnionHopData, LightningError, MessageSendEvent};
6564
#[cfg(test)]
6665
use crate::ln::outbound_payment;
6766
use crate::ln::outbound_payment::{Bolt11PaymentError, OutboundPayments, PendingOutboundPayment, RetryableInvoiceRequest, SendAlongPathArgs, StaleExpiration};
@@ -6244,9 +6243,9 @@ where
62446243
}, skimmed_fee_msat, incoming_amt_msat
62456244
},
62466245
}) => {
6247-
let inter_trampoline_session_priv = SecretKey::from_slice(&self.entropy_source.get_secure_random_bytes()).unwrap();
6248-
let mut htlc_source = HTLCSource::TrampolineForward {
6249-
session_priv: inter_trampoline_session_priv,
6246+
let htlc_source = HTLCSource::TrampolineForward {
6247+
// dummy value
6248+
session_priv: SecretKey::from_slice(&self.entropy_source.get_secure_random_bytes()).unwrap(),
62506249
previous_hop_data: HTLCPreviousHopData {
62516250
short_channel_id: prev_short_channel_id,
62526251
user_channel_id: Some(prev_user_channel_id),
@@ -6304,19 +6303,24 @@ where
63046303
};
63056304

63066305
let usable_channels: Vec<ChannelDetails> = self.list_usable_channels();
6306+
6307+
// assume any Trampoline node supports MPP
6308+
let mut recipient_features = Bolt11InvoiceFeatures::empty();
6309+
recipient_features.set_basic_mpp_optional();
6310+
63076311
let route = match self.router.find_route(
63086312
&self.node_signer.get_node_id(Recipient::Node).unwrap(),
63096313
&RouteParameters {
63106314
payment_params: PaymentParameters {
63116315
payee: Payee::Clear {
63126316
node_id: next_node_id,
63136317
route_hints: vec![],
6314-
features: None,
6318+
features: Some(recipient_features),
63156319
final_cltv_expiry_delta: 0,
63166320
},
63176321
expiry_time: None,
63186322
max_total_cltv_expiry_delta: incoming_cltv_expiry - outgoing_cltv_value,
6319-
max_path_count: 1,
6323+
max_path_count: DEFAULT_MAX_PATH_COUNT,
63206324
max_path_length: MAX_PATH_LENGTH_ESTIMATE / 2,
63216325
max_channel_saturation_power_of_half: 2,
63226326
previously_failed_channels: vec![],
@@ -6335,162 +6339,159 @@ where
63356339
}
63366340
};
63376341

6338-
let hops = match route.paths.first() {
6339-
Some(path) => {
6340-
let inter_trampoline_hops = path.hops.clone();
6341-
if let HTLCSource::TrampolineForward { ref mut hops, .. } = htlc_source {
6342-
*hops = inter_trampoline_hops.clone();
6342+
let inter_trampoline_payment_secret = PaymentSecret(self.entropy_source.get_secure_random_bytes());
6343+
for current_path in route.paths {
6344+
let inter_trampoline_session_priv = SecretKey::from_slice(&self.entropy_source.get_secure_random_bytes()).unwrap();
6345+
let inter_trampoline_hops = current_path.hops.clone();
6346+
let mut current_htlc_source = htlc_source.clone();
6347+
if let HTLCSource::TrampolineForward { ref mut session_priv, ref mut hops, .. } = current_htlc_source {
6348+
*session_priv = inter_trampoline_session_priv;
6349+
*hops = inter_trampoline_hops.clone();
6350+
};
6351+
6352+
let outgoing_scid = match inter_trampoline_hops.first() {
6353+
Some(hop) => hop.short_channel_id,
6354+
None => {
6355+
push_trampoline_forwarding_failure(format!("Could not find route to next Trampoline hop {next_node_id}"), current_htlc_source, None, 0x2000 | 25, Vec::new());
6356+
break;
63436357
}
6344-
inter_trampoline_hops
6345-
},
6346-
None => {
6347-
push_trampoline_forwarding_failure(format!("Could not find route to next Trampoline hop {next_node_id}"), htlc_source, None, 0x2000 | 25, Vec::new());
6348-
continue;
6349-
}
6350-
};
6358+
};
63516359

6352-
let outgoing_scid = match hops.first() {
6353-
Some(hop) => hop.short_channel_id,
6354-
None => {
6355-
push_trampoline_forwarding_failure(format!("Could not find route to next Trampoline hop {next_node_id}"), htlc_source, None, 0x2000 | 25, Vec::new());
6356-
continue;
6360+
let chan_info_opt = self.short_to_chan_info.read().unwrap().get(&outgoing_scid).cloned();
6361+
let (counterparty_node_id, forward_chan_id) = match chan_info_opt {
6362+
Some((cp_id, chan_id)) => (cp_id, chan_id),
6363+
None => {
6364+
push_trampoline_forwarding_failure(format!("Could not find forwarding channel {outgoing_scid} to route to next Trampoline hop {next_node_id}"), current_htlc_source, Some(outgoing_scid), 0x2000 | 25, Vec::new());
6365+
break;
6366+
}
6367+
};
6368+
let per_peer_state = self.per_peer_state.read().unwrap();
6369+
let peer_state_mutex_opt = per_peer_state.get(&counterparty_node_id);
6370+
if peer_state_mutex_opt.is_none() {
6371+
push_trampoline_forwarding_failure(format!("Could not to route to next Trampoline hop {next_node_id} via forwarding channel {outgoing_scid}"), current_htlc_source, Some(outgoing_scid), 0x2000 | 25, Vec::new());
6372+
break;
63576373
}
6358-
};
6374+
let mut peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
6375+
let peer_state = &mut *peer_state_lock;
63596376

6360-
let chan_info_opt = self.short_to_chan_info.read().unwrap().get(&outgoing_scid).cloned();
6361-
let (counterparty_node_id, forward_chan_id) = match chan_info_opt {
6362-
Some((cp_id, chan_id)) => (cp_id, chan_id),
6363-
None => {
6364-
push_trampoline_forwarding_failure(format!("Could not find forwarding channel {outgoing_scid} to route to next Trampoline hop {next_node_id}"), htlc_source, Some(outgoing_scid), 0x2000 | 25, Vec::new());
6365-
continue;
6366-
}
6367-
};
6368-
let per_peer_state = self.per_peer_state.read().unwrap();
6369-
let peer_state_mutex_opt = per_peer_state.get(&counterparty_node_id);
6370-
if peer_state_mutex_opt.is_none() {
6371-
push_trampoline_forwarding_failure(format!("Could not to route to next Trampoline hop {next_node_id} via forwarding channel {outgoing_scid}"), htlc_source, Some(outgoing_scid), 0x2000 | 25, Vec::new());
6372-
continue;
6373-
}
6374-
let mut peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
6375-
let peer_state = &mut *peer_state_lock;
6377+
let (outer_onion_packet, outer_value_msat, outer_cltv) = {
6378+
let path = Path {
6379+
hops: inter_trampoline_hops,
6380+
blinded_tail: None,
6381+
};
6382+
let recipient_onion = RecipientOnionFields::spontaneous_empty();
6383+
let (mut onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(
6384+
&path,
6385+
current_path.final_value_msat(),
6386+
&recipient_onion,
6387+
outgoing_cltv_value,
6388+
&None,
6389+
None,
6390+
None,
6391+
).unwrap();
6392+
6393+
let multipath_trampoline_data = Some(FinalOnionHopData { payment_secret: inter_trampoline_payment_secret, total_msat: outgoing_amt_msat });
6394+
if let Some(last_payload) = onion_payloads.last_mut() {
6395+
match last_payload {
6396+
msgs::OutboundOnionPayload::Receive { sender_intended_htlc_amt_msat, cltv_expiry_height, .. } => {
6397+
*last_payload = match next_blinding_point {
6398+
None => msgs::OutboundOnionPayload::TrampolineEntrypoint {
6399+
amt_to_forward: *sender_intended_htlc_amt_msat,
6400+
outgoing_cltv_value: *cltv_expiry_height,
6401+
multipath_trampoline_data,
6402+
trampoline_packet: onion_packet.clone(),
6403+
},
6404+
Some(blinding_point) => msgs::OutboundOnionPayload::BlindedTrampolineEntrypoint {
6405+
amt_to_forward: *sender_intended_htlc_amt_msat,
6406+
outgoing_cltv_value: *cltv_expiry_height,
6407+
multipath_trampoline_data,
6408+
trampoline_packet: onion_packet.clone(),
6409+
current_path_key: blinding_point,
6410+
}
6411+
};
6412+
}
6413+
_ => {
6414+
unreachable!("Last element must always initially be of type Receive.");
6415+
}
6416+
}
6417+
}
63766418

6377-
let (outer_onion_packet, outer_value_msat, outer_cltv) = {
6378-
let path = Path {
6379-
hops,
6380-
blinded_tail: None,
6419+
let onion_keys = onion_utils::construct_onion_keys(&self.secp_ctx, &path, &inter_trampoline_session_priv);
6420+
let outer_onion_prng_seed = self.entropy_source.get_secure_random_bytes();
6421+
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, outer_onion_prng_seed, &payment_hash).unwrap();
6422+
6423+
(onion_packet, htlc_msat, htlc_cltv)
63816424
};
6382-
let recipient_onion = RecipientOnionFields::spontaneous_empty();
6383-
let (mut onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(
6384-
&path,
6385-
outgoing_amt_msat,
6386-
&recipient_onion,
6387-
outgoing_cltv_value,
6388-
&None,
6389-
None,
6390-
None,
6391-
).unwrap();
6392-
6393-
if let Some(last_payload) = onion_payloads.last_mut() {
6394-
match last_payload {
6395-
msgs::OutboundOnionPayload::Receive { sender_intended_htlc_amt_msat, cltv_expiry_height, .. } => {
6396-
*last_payload = match next_blinding_point {
6397-
None => msgs::OutboundOnionPayload::TrampolineEntrypoint {
6398-
amt_to_forward: *sender_intended_htlc_amt_msat,
6399-
outgoing_cltv_value: *cltv_expiry_height,
6400-
multipath_trampoline_data: None,
6401-
trampoline_packet: onion_packet.clone(),
6402-
},
6403-
Some(blinding_point) => msgs::OutboundOnionPayload::BlindedTrampolineEntrypoint {
6404-
amt_to_forward: *sender_intended_htlc_amt_msat,
6405-
outgoing_cltv_value: *cltv_expiry_height,
6406-
multipath_trampoline_data: None,
6407-
trampoline_packet: onion_packet.clone(),
6408-
current_path_key: blinding_point,
6409-
}
6410-
};
6425+
6426+
// Forward the HTLC over the most appropriate channel with the corresponding peer,
6427+
// applying non-strict forwarding.
6428+
// The channel with the least amount of outbound liquidity will be used to maximize the
6429+
// probability of being able to successfully forward a subsequent HTLC.
6430+
let maybe_optimal_channel = peer_state.channel_by_id.values_mut()
6431+
.filter_map(Channel::as_funded_mut)
6432+
.filter_map(|chan| {
6433+
let balances = chan.get_available_balances(&self.fee_estimator);
6434+
if outer_value_msat <= balances.next_outbound_htlc_limit_msat &&
6435+
outer_value_msat >= balances.next_outbound_htlc_minimum_msat &&
6436+
chan.context.is_usable() {
6437+
Some((chan, balances))
6438+
} else {
6439+
None
64116440
}
6412-
_ => {
6413-
unreachable!("Last element must always initially be of type Receive.");
6441+
})
6442+
.min_by_key(|(_, balances)| balances.next_outbound_htlc_limit_msat).map(|(c, _)| c);
6443+
let optimal_channel = match maybe_optimal_channel {
6444+
Some(chan) => chan,
6445+
None => {
6446+
// Fall back to the specified channel to return an appropriate error.
6447+
if let Some(chan) = peer_state.channel_by_id
6448+
.get_mut(&forward_chan_id)
6449+
.and_then(Channel::as_funded_mut)
6450+
{
6451+
chan
6452+
} else {
6453+
push_trampoline_forwarding_failure(format!("Could not to route to next Trampoline hop {next_node_id} via forwarding channel {outgoing_scid}"), current_htlc_source, Some(outgoing_scid), 0x2000 | 25, Vec::new());
6454+
break;
64146455
}
64156456
}
6416-
}
6417-
6418-
let onion_keys = onion_utils::construct_onion_keys(&self.secp_ctx, &path, &inter_trampoline_session_priv).map_err(|_| {
6419-
APIError::InvalidRoute { err: "Pubkey along hop was maliciously selected".to_owned() }
6420-
}).unwrap();
6421-
let outer_onion_prng_seed = self.entropy_source.get_secure_random_bytes();
6422-
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, outer_onion_prng_seed, &payment_hash).unwrap();
6423-
6424-
(onion_packet, htlc_msat, htlc_cltv)
6425-
};
6457+
};
64266458

6427-
// Forward the HTLC over the most appropriate channel with the corresponding peer,
6428-
// applying non-strict forwarding.
6429-
// The channel with the least amount of outbound liquidity will be used to maximize the
6430-
// probability of being able to successfully forward a subsequent HTLC.
6431-
let maybe_optimal_channel = peer_state.channel_by_id.values_mut()
6432-
.filter_map(Channel::as_funded_mut)
6433-
.filter_map(|chan| {
6434-
let balances = chan.get_available_balances(&self.fee_estimator);
6435-
if outer_value_msat <= balances.next_outbound_htlc_limit_msat &&
6436-
outer_value_msat >= balances.next_outbound_htlc_minimum_msat &&
6437-
chan.context.is_usable() {
6438-
Some((chan, balances))
6459+
let logger = WithChannelContext::from(&self.logger, &optimal_channel.context, Some(payment_hash));
6460+
let channel_description = if optimal_channel.context.get_short_channel_id() == Some(short_chan_id) {
6461+
"specified"
6462+
} else {
6463+
"alternate"
6464+
};
6465+
log_trace!(logger, "Forwarding HTLC from SCID {} with payment_hash {} and next hop SCID {} over {} channel {} with corresponding peer {}",
6466+
prev_short_channel_id, &payment_hash, short_chan_id, channel_description, optimal_channel.context.channel_id(), &counterparty_node_id);
6467+
// Note that for inter-Trampoline forwards, we never add the blinding point to the UpdateAddHTLC message
6468+
if let Err(e) = optimal_channel.queue_add_htlc(outer_value_msat,
6469+
payment_hash, outer_cltv, current_htlc_source.clone(),
6470+
outer_onion_packet.clone(), skimmed_fee_msat, None, &self.fee_estimator,
6471+
&&logger)
6472+
{
6473+
if let ChannelError::Ignore(msg) = e {
6474+
log_trace!(logger, "Failed to forward HTLC with payment_hash {} to peer {}: {}", &payment_hash, &counterparty_node_id, msg);
64396475
} else {
6440-
None
6476+
panic!("Stated return value requirements in send_htlc() were not met");
64416477
}
6442-
})
6443-
.min_by_key(|(_, balances)| balances.next_outbound_htlc_limit_msat).map(|(c, _)| c);
6444-
let optimal_channel = match maybe_optimal_channel {
6445-
Some(chan) => chan,
6446-
None => {
6447-
// Fall back to the specified channel to return an appropriate error.
6478+
64486479
if let Some(chan) = peer_state.channel_by_id
64496480
.get_mut(&forward_chan_id)
64506481
.and_then(Channel::as_funded_mut)
64516482
{
6452-
chan
6483+
let failure_code = 0x1000 | 7;
6484+
let data = self.get_htlc_inbound_temp_fail_data(failure_code);
6485+
failed_forwards.push((current_htlc_source, payment_hash,
6486+
HTLCFailReason::reason(failure_code, data),
6487+
HTLCDestination::NextHopChannel { node_id: Some(chan.context.get_counterparty_node_id()), channel_id: forward_chan_id }
6488+
));
6489+
break;
64536490
} else {
6454-
push_trampoline_forwarding_failure(format!("Could not to route to next Trampoline hop {next_node_id} via forwarding channel {outgoing_scid}"), htlc_source, Some(outgoing_scid), 0x2000 | 25, Vec::new());
6455-
continue;
6491+
push_trampoline_forwarding_failure(format!("Could not to route to next Trampoline hop {next_node_id} via forwarding channel {outgoing_scid}"), current_htlc_source, Some(outgoing_scid), 0x2000 | 25, Vec::new());
6492+
break;
64566493
}
64576494
}
6458-
};
6459-
6460-
let logger = WithChannelContext::from(&self.logger, &optimal_channel.context, Some(payment_hash));
6461-
let channel_description = if optimal_channel.context.get_short_channel_id() == Some(short_chan_id) {
6462-
"specified"
6463-
} else {
6464-
"alternate"
6465-
};
6466-
log_trace!(logger, "Forwarding HTLC from SCID {} with payment_hash {} and next hop SCID {} over {} channel {} with corresponding peer {}",
6467-
prev_short_channel_id, &payment_hash, short_chan_id, channel_description, optimal_channel.context.channel_id(), &counterparty_node_id);
6468-
// Note that for inter-Trampoline forwards, we never add the blinding point to the UpdateAddHTLC message
6469-
if let Err(e) = optimal_channel.queue_add_htlc(outer_value_msat,
6470-
payment_hash, outer_cltv, htlc_source.clone(),
6471-
outer_onion_packet.clone(), skimmed_fee_msat, None, &self.fee_estimator,
6472-
&&logger)
6473-
{
6474-
if let ChannelError::Ignore(msg) = e {
6475-
log_trace!(logger, "Failed to forward HTLC with payment_hash {} to peer {}: {}", &payment_hash, &counterparty_node_id, msg);
6476-
} else {
6477-
panic!("Stated return value requirements in send_htlc() were not met");
6478-
}
6479-
6480-
if let Some(chan) = peer_state.channel_by_id
6481-
.get_mut(&forward_chan_id)
6482-
.and_then(Channel::as_funded_mut)
6483-
{
6484-
let failure_code = 0x1000|7;
6485-
let data = self.get_htlc_inbound_temp_fail_data(failure_code);
6486-
failed_forwards.push((htlc_source, payment_hash,
6487-
HTLCFailReason::reason(failure_code, data),
6488-
HTLCDestination::NextHopChannel { node_id: Some(chan.context.get_counterparty_node_id()), channel_id: forward_chan_id }
6489-
));
6490-
} else {
6491-
push_trampoline_forwarding_failure(format!("Could not to route to next Trampoline hop {next_node_id} via forwarding channel {outgoing_scid}"), htlc_source, Some(outgoing_scid), 0x2000 | 25, Vec::new());
6492-
continue;
6493-
}
64946495
}
64956496
()
64966497
},

0 commit comments

Comments
 (0)