Skip to content

Commit 2abfc25

Browse files
committed
f: forward Trampoline payments with MPP
1 parent b4f7522 commit 2abfc25

File tree

1 file changed

+148
-144
lines changed

1 file changed

+148
-144
lines changed

lightning/src/ln/channelmanager.rs

+148-144
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,12 @@ use crate::ln::channel_state::ChannelDetails;
5656
use crate::types::features::{Bolt12InvoiceFeatures, ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures};
5757
#[cfg(any(feature = "_test_utils", test))]
5858
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};
59+
use crate::routing::router::{BlindedTail, DEFAULT_MAX_PATH_COUNT, FixedRouter, InFlightHtlcs, MAX_PATH_LENGTH_ESTIMATE, Path, Payee, PaymentParameters, Route, RouteHop, RouteParameters, RouteParametersConfig, Router};
6060
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};
6161
use crate::ln::msgs;
6262
use crate::ln::onion_utils::{self};
6363
use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING};
64-
use crate::ln::msgs::{BaseMessageHandler, ChannelMessageHandler, CommitmentUpdate, DecodeError, LightningError, MessageSendEvent};
64+
use crate::ln::msgs::{BaseMessageHandler, ChannelMessageHandler, CommitmentUpdate, DecodeError, FinalOnionHopData, LightningError, MessageSendEvent};
6565
#[cfg(test)]
6666
use crate::ln::outbound_payment;
6767
use crate::ln::outbound_payment::{Bolt11PaymentError, OutboundPayments, PendingOutboundPayment, RetryableInvoiceRequest, SendAlongPathArgs, StaleExpiration};
@@ -6229,9 +6229,9 @@ where
62296229
}, skimmed_fee_msat, incoming_amt_msat
62306230
},
62316231
}) => {
6232-
let inter_trampoline_session_priv = SecretKey::from_slice(&self.entropy_source.get_secure_random_bytes()).unwrap();
6233-
let mut htlc_source = HTLCSource::TrampolineForward {
6234-
session_priv: inter_trampoline_session_priv,
6232+
let htlc_source = HTLCSource::TrampolineForward {
6233+
// dummy value
6234+
session_priv: SecretKey::from_slice(&self.entropy_source.get_secure_random_bytes()).unwrap(),
62356235
previous_hop_data: HTLCPreviousHopData {
62366236
short_channel_id: prev_short_channel_id,
62376237
user_channel_id: Some(prev_user_channel_id),
@@ -6289,19 +6289,24 @@ where
62896289
};
62906290

62916291
let usable_channels: Vec<ChannelDetails> = self.list_usable_channels();
6292+
6293+
// assume any Trampoline node supports MPP
6294+
let mut recipient_features = Bolt11InvoiceFeatures::empty();
6295+
recipient_features.set_basic_mpp_optional();
6296+
62926297
let route = match self.router.find_route(
62936298
&self.node_signer.get_node_id(Recipient::Node).unwrap(),
62946299
&RouteParameters {
62956300
payment_params: PaymentParameters {
62966301
payee: Payee::Clear {
62976302
node_id: next_node_id,
62986303
route_hints: vec![],
6299-
features: None,
6304+
features: Some(recipient_features),
63006305
final_cltv_expiry_delta: 0,
63016306
},
63026307
expiry_time: None,
63036308
max_total_cltv_expiry_delta: incoming_cltv_expiry - outgoing_cltv_value,
6304-
max_path_count: 1,
6309+
max_path_count: DEFAULT_MAX_PATH_COUNT,
63056310
max_path_length: MAX_PATH_LENGTH_ESTIMATE / 2,
63066311
max_channel_saturation_power_of_half: 2,
63076312
previously_failed_channels: vec![],
@@ -6320,162 +6325,161 @@ where
63206325
}
63216326
};
63226327

6323-
let hops = match route.paths.first() {
6324-
Some(path) => {
6325-
let inter_trampoline_hops = path.hops.clone();
6326-
if let HTLCSource::TrampolineForward { ref mut hops, .. } = htlc_source {
6327-
*hops = inter_trampoline_hops.clone();
6328+
let inter_trampoline_payment_secret = PaymentSecret(self.entropy_source.get_secure_random_bytes());
6329+
for current_path in route.paths {
6330+
let inter_trampoline_session_priv = SecretKey::from_slice(&self.entropy_source.get_secure_random_bytes()).unwrap();
6331+
let inter_trampoline_hops = current_path.hops.clone();
6332+
let mut current_htlc_source = htlc_source.clone();
6333+
if let HTLCSource::TrampolineForward { ref mut session_priv, ref mut hops, .. } = current_htlc_source {
6334+
*session_priv = inter_trampoline_session_priv;
6335+
*hops = inter_trampoline_hops.clone();
6336+
};
6337+
6338+
let outgoing_scid = match inter_trampoline_hops.first() {
6339+
Some(hop) => hop.short_channel_id,
6340+
None => {
6341+
push_trampoline_forwarding_failure(format!("Could not find route to next Trampoline hop {next_node_id}"), current_htlc_source, None, 0x2000 | 25, Vec::new());
6342+
break;
63286343
}
6329-
inter_trampoline_hops
6330-
},
6331-
None => {
6332-
push_trampoline_forwarding_failure(format!("Could not find route to next Trampoline hop {next_node_id}"), htlc_source, None, 0x2000 | 25, Vec::new());
6333-
continue;
6334-
}
6335-
};
6344+
};
63366345

6337-
let outgoing_scid = match hops.first() {
6338-
Some(hop) => hop.short_channel_id,
6339-
None => {
6340-
push_trampoline_forwarding_failure(format!("Could not find route to next Trampoline hop {next_node_id}"), htlc_source, None, 0x2000 | 25, Vec::new());
6341-
continue;
6346+
let chan_info_opt = self.short_to_chan_info.read().unwrap().get(&outgoing_scid).cloned();
6347+
let (counterparty_node_id, forward_chan_id) = match chan_info_opt {
6348+
Some((cp_id, chan_id)) => (cp_id, chan_id),
6349+
None => {
6350+
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());
6351+
break;
6352+
}
6353+
};
6354+
let per_peer_state = self.per_peer_state.read().unwrap();
6355+
let peer_state_mutex_opt = per_peer_state.get(&counterparty_node_id);
6356+
if peer_state_mutex_opt.is_none() {
6357+
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());
6358+
break;
63426359
}
6343-
};
6360+
let mut peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
6361+
let peer_state = &mut *peer_state_lock;
63446362

6345-
let chan_info_opt = self.short_to_chan_info.read().unwrap().get(&outgoing_scid).cloned();
6346-
let (counterparty_node_id, forward_chan_id) = match chan_info_opt {
6347-
Some((cp_id, chan_id)) => (cp_id, chan_id),
6348-
None => {
6349-
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());
6350-
continue;
6351-
}
6352-
};
6353-
let per_peer_state = self.per_peer_state.read().unwrap();
6354-
let peer_state_mutex_opt = per_peer_state.get(&counterparty_node_id);
6355-
if peer_state_mutex_opt.is_none() {
6356-
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());
6357-
continue;
6358-
}
6359-
let mut peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
6360-
let peer_state = &mut *peer_state_lock;
6363+
let (outer_onion_packet, outer_value_msat, outer_cltv) = {
6364+
let path = Path {
6365+
hops: inter_trampoline_hops,
6366+
blinded_tail: None,
6367+
};
6368+
let recipient_onion = RecipientOnionFields::spontaneous_empty();
6369+
let (mut onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(
6370+
&path,
6371+
current_path.final_value_msat(),
6372+
&recipient_onion,
6373+
outgoing_cltv_value,
6374+
&None,
6375+
None,
6376+
None,
6377+
).unwrap();
6378+
6379+
let multipath_trampoline_data = Some(FinalOnionHopData { payment_secret: inter_trampoline_payment_secret, total_msat: outgoing_amt_msat });
6380+
if let Some(last_payload) = onion_payloads.last_mut() {
6381+
match last_payload {
6382+
msgs::OutboundOnionPayload::Receive { sender_intended_htlc_amt_msat, cltv_expiry_height, .. } => {
6383+
*last_payload = match next_blinding_point {
6384+
None => msgs::OutboundOnionPayload::TrampolineEntrypoint {
6385+
amt_to_forward: *sender_intended_htlc_amt_msat,
6386+
outgoing_cltv_value: *cltv_expiry_height,
6387+
multipath_trampoline_data,
6388+
trampoline_packet: onion_packet.clone(),
6389+
},
6390+
Some(blinding_point) => msgs::OutboundOnionPayload::BlindedTrampolineEntrypoint {
6391+
amt_to_forward: *sender_intended_htlc_amt_msat,
6392+
outgoing_cltv_value: *cltv_expiry_height,
6393+
multipath_trampoline_data,
6394+
trampoline_packet: onion_packet.clone(),
6395+
current_path_key: blinding_point,
6396+
}
6397+
};
6398+
}
6399+
_ => {
6400+
unreachable!("Last element must always initially be of type Receive.");
6401+
}
6402+
}
6403+
}
6404+
6405+
let onion_keys = onion_utils::construct_onion_keys(&self.secp_ctx, &path, &inter_trampoline_session_priv).map_err(|_| {
6406+
APIError::InvalidRoute { err: "Pubkey along hop was maliciously selected".to_owned() }
6407+
}).unwrap();
6408+
let outer_onion_prng_seed = self.entropy_source.get_secure_random_bytes();
6409+
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, outer_onion_prng_seed, &payment_hash).unwrap();
63616410

6362-
let (outer_onion_packet, outer_value_msat, outer_cltv) = {
6363-
let path = Path {
6364-
hops,
6365-
blinded_tail: None,
6411+
(onion_packet, htlc_msat, htlc_cltv)
63666412
};
6367-
let recipient_onion = RecipientOnionFields::spontaneous_empty();
6368-
let (mut onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(
6369-
&path,
6370-
outgoing_amt_msat,
6371-
&recipient_onion,
6372-
outgoing_cltv_value,
6373-
&None,
6374-
None,
6375-
None,
6376-
).unwrap();
6377-
6378-
if let Some(last_payload) = onion_payloads.last_mut() {
6379-
match last_payload {
6380-
msgs::OutboundOnionPayload::Receive { sender_intended_htlc_amt_msat, cltv_expiry_height, .. } => {
6381-
*last_payload = match next_blinding_point {
6382-
None => msgs::OutboundOnionPayload::TrampolineEntrypoint {
6383-
amt_to_forward: *sender_intended_htlc_amt_msat,
6384-
outgoing_cltv_value: *cltv_expiry_height,
6385-
multipath_trampoline_data: None,
6386-
trampoline_packet: onion_packet.clone(),
6387-
},
6388-
Some(blinding_point) => msgs::OutboundOnionPayload::BlindedTrampolineEntrypoint {
6389-
amt_to_forward: *sender_intended_htlc_amt_msat,
6390-
outgoing_cltv_value: *cltv_expiry_height,
6391-
multipath_trampoline_data: None,
6392-
trampoline_packet: onion_packet.clone(),
6393-
current_path_key: blinding_point,
6394-
}
6395-
};
6413+
6414+
// Forward the HTLC over the most appropriate channel with the corresponding peer,
6415+
// applying non-strict forwarding.
6416+
// The channel with the least amount of outbound liquidity will be used to maximize the
6417+
// probability of being able to successfully forward a subsequent HTLC.
6418+
let maybe_optimal_channel = peer_state.channel_by_id.values_mut()
6419+
.filter_map(Channel::as_funded_mut)
6420+
.filter_map(|chan| {
6421+
let balances = chan.get_available_balances(&self.fee_estimator);
6422+
if outer_value_msat <= balances.next_outbound_htlc_limit_msat &&
6423+
outer_value_msat >= balances.next_outbound_htlc_minimum_msat &&
6424+
chan.context.is_usable() {
6425+
Some((chan, balances))
6426+
} else {
6427+
None
63966428
}
6397-
_ => {
6398-
unreachable!("Last element must always initially be of type Receive.");
6429+
})
6430+
.min_by_key(|(_, balances)| balances.next_outbound_htlc_limit_msat).map(|(c, _)| c);
6431+
let optimal_channel = match maybe_optimal_channel {
6432+
Some(chan) => chan,
6433+
None => {
6434+
// Fall back to the specified channel to return an appropriate error.
6435+
if let Some(chan) = peer_state.channel_by_id
6436+
.get_mut(&forward_chan_id)
6437+
.and_then(Channel::as_funded_mut)
6438+
{
6439+
chan
6440+
} else {
6441+
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());
6442+
break;
63996443
}
64006444
}
6401-
}
6402-
6403-
let onion_keys = onion_utils::construct_onion_keys(&self.secp_ctx, &path, &inter_trampoline_session_priv).map_err(|_| {
6404-
APIError::InvalidRoute { err: "Pubkey along hop was maliciously selected".to_owned() }
6405-
}).unwrap();
6406-
let outer_onion_prng_seed = self.entropy_source.get_secure_random_bytes();
6407-
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, outer_onion_prng_seed, &payment_hash).unwrap();
6408-
6409-
(onion_packet, htlc_msat, htlc_cltv)
6410-
};
6445+
};
64116446

6412-
// Forward the HTLC over the most appropriate channel with the corresponding peer,
6413-
// applying non-strict forwarding.
6414-
// The channel with the least amount of outbound liquidity will be used to maximize the
6415-
// probability of being able to successfully forward a subsequent HTLC.
6416-
let maybe_optimal_channel = peer_state.channel_by_id.values_mut()
6417-
.filter_map(Channel::as_funded_mut)
6418-
.filter_map(|chan| {
6419-
let balances = chan.get_available_balances(&self.fee_estimator);
6420-
if outer_value_msat <= balances.next_outbound_htlc_limit_msat &&
6421-
outer_value_msat >= balances.next_outbound_htlc_minimum_msat &&
6422-
chan.context.is_usable() {
6423-
Some((chan, balances))
6447+
let logger = WithChannelContext::from(&self.logger, &optimal_channel.context, Some(payment_hash));
6448+
let channel_description = if optimal_channel.context.get_short_channel_id() == Some(short_chan_id) {
6449+
"specified"
6450+
} else {
6451+
"alternate"
6452+
};
6453+
log_trace!(logger, "Forwarding HTLC from SCID {} with payment_hash {} and next hop SCID {} over {} channel {} with corresponding peer {}",
6454+
prev_short_channel_id, &payment_hash, short_chan_id, channel_description, optimal_channel.context.channel_id(), &counterparty_node_id);
6455+
// Note that for inter-Trampoline forwards, we never add the blinding point to the UpdateAddHTLC message
6456+
if let Err(e) = optimal_channel.queue_add_htlc(outer_value_msat,
6457+
payment_hash, outer_cltv, current_htlc_source.clone(),
6458+
outer_onion_packet.clone(), skimmed_fee_msat, None, &self.fee_estimator,
6459+
&&logger)
6460+
{
6461+
if let ChannelError::Ignore(msg) = e {
6462+
log_trace!(logger, "Failed to forward HTLC with payment_hash {} to peer {}: {}", &payment_hash, &counterparty_node_id, msg);
64246463
} else {
6425-
None
6464+
panic!("Stated return value requirements in send_htlc() were not met");
64266465
}
6427-
})
6428-
.min_by_key(|(_, balances)| balances.next_outbound_htlc_limit_msat).map(|(c, _)| c);
6429-
let optimal_channel = match maybe_optimal_channel {
6430-
Some(chan) => chan,
6431-
None => {
6432-
// Fall back to the specified channel to return an appropriate error.
6466+
64336467
if let Some(chan) = peer_state.channel_by_id
64346468
.get_mut(&forward_chan_id)
64356469
.and_then(Channel::as_funded_mut)
64366470
{
6437-
chan
6471+
let failure_code = 0x1000 | 7;
6472+
let data = self.get_htlc_inbound_temp_fail_data(failure_code);
6473+
failed_forwards.push((current_htlc_source, payment_hash,
6474+
HTLCFailReason::reason(failure_code, data),
6475+
HTLCDestination::NextHopChannel { node_id: Some(chan.context.get_counterparty_node_id()), channel_id: forward_chan_id }
6476+
));
6477+
break;
64386478
} else {
6439-
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());
6440-
continue;
6479+
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());
6480+
break;
64416481
}
64426482
}
6443-
};
6444-
6445-
let logger = WithChannelContext::from(&self.logger, &optimal_channel.context, Some(payment_hash));
6446-
let channel_description = if optimal_channel.context.get_short_channel_id() == Some(short_chan_id) {
6447-
"specified"
6448-
} else {
6449-
"alternate"
6450-
};
6451-
log_trace!(logger, "Forwarding HTLC from SCID {} with payment_hash {} and next hop SCID {} over {} channel {} with corresponding peer {}",
6452-
prev_short_channel_id, &payment_hash, short_chan_id, channel_description, optimal_channel.context.channel_id(), &counterparty_node_id);
6453-
// Note that for inter-Trampoline forwards, we never add the blinding point to the UpdateAddHTLC message
6454-
if let Err(e) = optimal_channel.queue_add_htlc(outer_value_msat,
6455-
payment_hash, outer_cltv, htlc_source.clone(),
6456-
outer_onion_packet.clone(), skimmed_fee_msat, None, &self.fee_estimator,
6457-
&&logger)
6458-
{
6459-
if let ChannelError::Ignore(msg) = e {
6460-
log_trace!(logger, "Failed to forward HTLC with payment_hash {} to peer {}: {}", &payment_hash, &counterparty_node_id, msg);
6461-
} else {
6462-
panic!("Stated return value requirements in send_htlc() were not met");
6463-
}
6464-
6465-
if let Some(chan) = peer_state.channel_by_id
6466-
.get_mut(&forward_chan_id)
6467-
.and_then(Channel::as_funded_mut)
6468-
{
6469-
let failure_code = 0x1000|7;
6470-
let data = self.get_htlc_inbound_temp_fail_data(failure_code);
6471-
failed_forwards.push((htlc_source, payment_hash,
6472-
HTLCFailReason::reason(failure_code, data),
6473-
HTLCDestination::NextHopChannel { node_id: Some(chan.context.get_counterparty_node_id()), channel_id: forward_chan_id }
6474-
));
6475-
} else {
6476-
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());
6477-
continue;
6478-
}
64796483
}
64806484
()
64816485
},

0 commit comments

Comments
 (0)