Skip to content

Commit 2623a89

Browse files
committed
f: forward Trampoline payments with MPP
1 parent 4ae4c9f commit 2623a89

File tree

1 file changed

+148
-145
lines changed

1 file changed

+148
-145
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 148 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};
@@ -6241,9 +6240,9 @@ where
62416240
}, skimmed_fee_msat, incoming_amt_msat
62426241
},
62436242
}) => {
6244-
let inter_trampoline_session_priv = SecretKey::from_slice(&self.entropy_source.get_secure_random_bytes()).unwrap();
6245-
let mut htlc_source = HTLCSource::TrampolineForward {
6246-
session_priv: inter_trampoline_session_priv,
6243+
let htlc_source = HTLCSource::TrampolineForward {
6244+
// dummy value
6245+
session_priv: SecretKey::from_slice(&self.entropy_source.get_secure_random_bytes()).unwrap(),
62476246
previous_hop_data: HTLCPreviousHopData {
62486247
short_channel_id: prev_short_channel_id,
62496248
user_channel_id: Some(prev_user_channel_id),
@@ -6301,19 +6300,24 @@ where
63016300
};
63026301

63036302
let usable_channels: Vec<ChannelDetails> = self.list_usable_channels();
6303+
6304+
// assume any Trampoline node supports MPP
6305+
let mut recipient_features = Bolt11InvoiceFeatures::empty();
6306+
recipient_features.set_basic_mpp_optional();
6307+
63046308
let route = match self.router.find_route(
63056309
&self.node_signer.get_node_id(Recipient::Node).unwrap(),
63066310
&RouteParameters {
63076311
payment_params: PaymentParameters {
63086312
payee: Payee::Clear {
63096313
node_id: next_node_id,
63106314
route_hints: vec![],
6311-
features: None,
6315+
features: Some(recipient_features),
63126316
final_cltv_expiry_delta: 0,
63136317
},
63146318
expiry_time: None,
63156319
max_total_cltv_expiry_delta: incoming_cltv_expiry - outgoing_cltv_value,
6316-
max_path_count: 1,
6320+
max_path_count: DEFAULT_MAX_PATH_COUNT,
63176321
max_path_length: MAX_PATH_LENGTH_ESTIMATE / 2,
63186322
max_channel_saturation_power_of_half: 2,
63196323
previously_failed_channels: vec![],
@@ -6332,162 +6336,161 @@ where
63326336
}
63336337
};
63346338

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

6349-
let outgoing_scid = match hops.first() {
6350-
Some(hop) => hop.short_channel_id,
6351-
None => {
6352-
push_trampoline_forwarding_failure(format!("Could not find route to next Trampoline hop {next_node_id}"), htlc_source, None, 0x2000 | 25, Vec::new());
6353-
continue;
6357+
let chan_info_opt = self.short_to_chan_info.read().unwrap().get(&outgoing_scid).cloned();
6358+
let (counterparty_node_id, forward_chan_id) = match chan_info_opt {
6359+
Some((cp_id, chan_id)) => (cp_id, chan_id),
6360+
None => {
6361+
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());
6362+
break;
6363+
}
6364+
};
6365+
let per_peer_state = self.per_peer_state.read().unwrap();
6366+
let peer_state_mutex_opt = per_peer_state.get(&counterparty_node_id);
6367+
if peer_state_mutex_opt.is_none() {
6368+
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());
6369+
break;
63546370
}
6355-
};
6371+
let mut peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
6372+
let peer_state = &mut *peer_state_lock;
63566373

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

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

6424-
// Forward the HTLC over the most appropriate channel with the corresponding peer,
6425-
// applying non-strict forwarding.
6426-
// The channel with the least amount of outbound liquidity will be used to maximize the
6427-
// probability of being able to successfully forward a subsequent HTLC.
6428-
let maybe_optimal_channel = peer_state.channel_by_id.values_mut()
6429-
.filter_map(Channel::as_funded_mut)
6430-
.filter_map(|chan| {
6431-
let balances = chan.get_available_balances(&self.fee_estimator);
6432-
if outer_value_msat <= balances.next_outbound_htlc_limit_msat &&
6433-
outer_value_msat >= balances.next_outbound_htlc_minimum_msat &&
6434-
chan.context.is_usable() {
6435-
Some((chan, balances))
6458+
let logger = WithChannelContext::from(&self.logger, &optimal_channel.context, Some(payment_hash));
6459+
let channel_description = if optimal_channel.context.get_short_channel_id() == Some(short_chan_id) {
6460+
"specified"
6461+
} else {
6462+
"alternate"
6463+
};
6464+
log_trace!(logger, "Forwarding HTLC from SCID {} with payment_hash {} and next hop SCID {} over {} channel {} with corresponding peer {}",
6465+
prev_short_channel_id, &payment_hash, short_chan_id, channel_description, optimal_channel.context.channel_id(), &counterparty_node_id);
6466+
// Note that for inter-Trampoline forwards, we never add the blinding point to the UpdateAddHTLC message
6467+
if let Err(e) = optimal_channel.queue_add_htlc(outer_value_msat,
6468+
payment_hash, outer_cltv, current_htlc_source.clone(),
6469+
outer_onion_packet.clone(), skimmed_fee_msat, None, &self.fee_estimator,
6470+
&&logger)
6471+
{
6472+
if let ChannelError::Ignore(msg) = e {
6473+
log_trace!(logger, "Failed to forward HTLC with payment_hash {} to peer {}: {}", &payment_hash, &counterparty_node_id, msg);
64366474
} else {
6437-
None
6475+
panic!("Stated return value requirements in send_htlc() were not met");
64386476
}
6439-
})
6440-
.min_by_key(|(_, balances)| balances.next_outbound_htlc_limit_msat).map(|(c, _)| c);
6441-
let optimal_channel = match maybe_optimal_channel {
6442-
Some(chan) => chan,
6443-
None => {
6444-
// Fall back to the specified channel to return an appropriate error.
6477+
64456478
if let Some(chan) = peer_state.channel_by_id
64466479
.get_mut(&forward_chan_id)
64476480
.and_then(Channel::as_funded_mut)
64486481
{
6449-
chan
6482+
let failure_code = 0x1000 | 7;
6483+
let data = self.get_htlc_inbound_temp_fail_data(failure_code);
6484+
failed_forwards.push((current_htlc_source, payment_hash,
6485+
HTLCFailReason::reason(failure_code, data),
6486+
HTLCDestination::NextHopChannel { node_id: Some(chan.context.get_counterparty_node_id()), channel_id: forward_chan_id }
6487+
));
6488+
break;
64506489
} else {
6451-
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());
6452-
continue;
6490+
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());
6491+
break;
64536492
}
64546493
}
6455-
};
6456-
6457-
let logger = WithChannelContext::from(&self.logger, &optimal_channel.context, Some(payment_hash));
6458-
let channel_description = if optimal_channel.context.get_short_channel_id() == Some(short_chan_id) {
6459-
"specified"
6460-
} else {
6461-
"alternate"
6462-
};
6463-
log_trace!(logger, "Forwarding HTLC from SCID {} with payment_hash {} and next hop SCID {} over {} channel {} with corresponding peer {}",
6464-
prev_short_channel_id, &payment_hash, short_chan_id, channel_description, optimal_channel.context.channel_id(), &counterparty_node_id);
6465-
// Note that for inter-Trampoline forwards, we never add the blinding point to the UpdateAddHTLC message
6466-
if let Err(e) = optimal_channel.queue_add_htlc(outer_value_msat,
6467-
payment_hash, outer_cltv, htlc_source.clone(),
6468-
outer_onion_packet.clone(), skimmed_fee_msat, None, &self.fee_estimator,
6469-
&&logger)
6470-
{
6471-
if let ChannelError::Ignore(msg) = e {
6472-
log_trace!(logger, "Failed to forward HTLC with payment_hash {} to peer {}: {}", &payment_hash, &counterparty_node_id, msg);
6473-
} else {
6474-
panic!("Stated return value requirements in send_htlc() were not met");
6475-
}
6476-
6477-
if let Some(chan) = peer_state.channel_by_id
6478-
.get_mut(&forward_chan_id)
6479-
.and_then(Channel::as_funded_mut)
6480-
{
6481-
let failure_code = 0x1000|7;
6482-
let data = self.get_htlc_inbound_temp_fail_data(failure_code);
6483-
failed_forwards.push((htlc_source, payment_hash,
6484-
HTLCFailReason::reason(failure_code, data),
6485-
HTLCDestination::NextHopChannel { node_id: Some(chan.context.get_counterparty_node_id()), channel_id: forward_chan_id }
6486-
));
6487-
} else {
6488-
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());
6489-
continue;
6490-
}
64916494
}
64926495
()
64936496
},

0 commit comments

Comments
 (0)