Skip to content

Commit a79ba44

Browse files
committed
Create TrampolineForward HTLCSource variant
To process errors returned from downstream nodes when forwarding between Trampoline nodes, we need to store information beyond what's available in `HTLCPreviousHopData`, such as the used hops and the newly generated outer onion's session_priv. To that end, we add a new variant to `HTLCSource` in this commit, which also future-proofs mapping an outbound forward to an incoming MPP.
1 parent a258d01 commit a79ba44

File tree

4 files changed

+246
-4
lines changed

4 files changed

+246
-4
lines changed

lightning/src/chain/channelmonitor.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -2432,6 +2432,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
24322432
None => panic!("Outbound HTLCs should have a source"),
24332433
Some(&HTLCSource::PreviousHopData(_)) => false,
24342434
Some(&HTLCSource::OutboundRoute { .. }) => true,
2435+
Some(&HTLCSource::TrampolineForward { .. }) => false,
24352436
};
24362437
return Some(Balance::MaybeTimeoutClaimableHTLC {
24372438
amount_satoshis: htlc.amount_msat / 1000,
@@ -2646,6 +2647,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
26462647
None => panic!("Outbound HTLCs should have a source"),
26472648
Some(HTLCSource::PreviousHopData(_)) => false,
26482649
Some(HTLCSource::OutboundRoute { .. }) => true,
2650+
Some(HTLCSource::TrampolineForward { .. }) => false,
26492651
};
26502652
if outbound_payment {
26512653
outbound_payment_htlc_rounded_msat += rounded_value_msat;
@@ -3171,7 +3173,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
31713173
} else { false }
31723174
}));
31733175
}
3174-
self.counterparty_fulfilled_htlcs.insert(*claimed_htlc_id, *claimed_preimage);
3176+
self.counterparty_fulfilled_htlcs.insert(claimed_htlc_id.clone(), *claimed_preimage);
31753177
}
31763178
}
31773179

lightning/src/ln/channelmanager.rs

+236-3
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ 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, InFlightHtlcs, Path, Payee, PaymentParameters, RouteParameters, RouteParametersConfig, Router, FixedRouter, Route};
60-
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, invalid_payment_err_data};
59+
use crate::routing::router::{BlindedTail, FixedRouter, InFlightHtlcs, Path, Payee, PaymentParameters, Route, RouteHop, RouteParameters, RouteParametersConfig, Router};
60+
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, invalid_payment_err_data, NextPacketDetails};
6161
use crate::ln::msgs;
6262
use crate::ln::onion_utils::{self};
6363
use crate::ln::onion_utils::{HTLCFailReason, LocalHTLCFailureReason};
@@ -626,10 +626,17 @@ impl Readable for InterceptId {
626626
}
627627

628628
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
629+
pub(crate) struct PreviousHopIdData {
630+
short_channel_id: u64,
631+
htlc_id: u64,
632+
}
633+
634+
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
629635
/// Uniquely describes an HTLC by its source. Just the guaranteed-unique subset of [`HTLCSource`].
630636
pub(crate) enum SentHTLCId {
631637
PreviousHopData { short_channel_id: u64, htlc_id: u64 },
632638
OutboundRoute { session_priv: [u8; SECRET_KEY_SIZE] },
639+
TrampolineForward { session_priv: [u8; SECRET_KEY_SIZE], previous_hop_data: Vec<PreviousHopIdData> }
633640
}
634641
impl SentHTLCId {
635642
pub(crate) fn from_source(source: &HTLCSource) -> Self {
@@ -640,9 +647,20 @@ impl SentHTLCId {
640647
},
641648
HTLCSource::OutboundRoute { session_priv, .. } =>
642649
Self::OutboundRoute { session_priv: session_priv.secret_bytes() },
650+
HTLCSource::TrampolineForward { previous_hop_data, session_priv, .. } => Self::TrampolineForward {
651+
session_priv: session_priv.secret_bytes(),
652+
previous_hop_data: previous_hop_data.iter().map(|hop_data| PreviousHopIdData {
653+
short_channel_id: hop_data.short_channel_id,
654+
htlc_id: hop_data.htlc_id,
655+
}).collect(),
656+
},
643657
}
644658
}
645659
}
660+
impl_writeable_tlv_based!(PreviousHopIdData, {
661+
(0, short_channel_id, required),
662+
(2, htlc_id, required),
663+
});
646664
impl_writeable_tlv_based_enum!(SentHTLCId,
647665
(0, PreviousHopData) => {
648666
(0, short_channel_id, required),
@@ -651,6 +669,10 @@ impl_writeable_tlv_based_enum!(SentHTLCId,
651669
(2, OutboundRoute) => {
652670
(0, session_priv, required),
653671
},
672+
(4, TrampolineForward) => {
673+
(0, session_priv, required),
674+
(2, previous_hop_data, required_vec),
675+
},
654676
);
655677

656678
mod fuzzy_channelmanager {
@@ -661,6 +683,16 @@ mod fuzzy_channelmanager {
661683
#[derive(Clone, Debug, PartialEq, Eq)]
662684
pub enum HTLCSource {
663685
PreviousHopData(HTLCPreviousHopData),
686+
TrampolineForward {
687+
/// We might be forwarding an incoming payment that was received over MPP, and therefore
688+
/// need to store the vector of corresponding `HTLCPreviousHopData` values.
689+
previous_hop_data: Vec<HTLCPreviousHopData>,
690+
incoming_trampoline_shared_secret: [u8; 32],
691+
hops: Vec<RouteHop>,
692+
/// In order to decode inter-Trampoline errors, we need to store the session_priv key
693+
/// given we're effectively creating new outbound routes.
694+
session_priv: SecretKey,
695+
},
664696
OutboundRoute {
665697
path: Path,
666698
session_priv: SecretKey,
@@ -712,6 +744,13 @@ impl core::hash::Hash for HTLCSource {
712744
payment_id.hash(hasher);
713745
first_hop_htlc_msat.hash(hasher);
714746
},
747+
HTLCSource::TrampolineForward { previous_hop_data, incoming_trampoline_shared_secret, hops, session_priv } => {
748+
2u8.hash(hasher);
749+
previous_hop_data.hash(hasher);
750+
incoming_trampoline_shared_secret.hash(hasher);
751+
hops.hash(hasher);
752+
session_priv[..].hash(hasher);
753+
},
715754
}
716755
}
717756
}
@@ -6999,7 +7038,7 @@ where
69997038
// Note that we MUST NOT end up calling methods on self.chain_monitor here - we're called
70007039
// from block_connected which may run during initialization prior to the chain_monitor
70017040
// being fully configured. See the docs for `ChannelManagerReadArgs` for more.
7002-
let mut push_forward_event;
7041+
let mut push_forward_event = true;
70037042
match source {
70047043
HTLCSource::OutboundRoute { ref path, ref session_priv, ref payment_id, .. } => {
70057044
push_forward_event = self.pending_outbound_payments.fail_htlc(source, payment_hash, onion_error, path,
@@ -7056,6 +7095,65 @@ where
70567095
failed_next_destination: destination,
70577096
}, None));
70587097
},
7098+
HTLCSource::TrampolineForward { previous_hop_data, incoming_trampoline_shared_secret, .. } => {
7099+
// todo: what do we want to do with this given we do not wish to propagate it directly?
7100+
let _decoded_onion_failure = onion_error.decode_onion_failure(&self.secp_ctx, &self.logger, &source);
7101+
7102+
for current_hop_data in previous_hop_data {
7103+
let incoming_packet_shared_secret = current_hop_data.incoming_packet_shared_secret;
7104+
let channel_id = current_hop_data.channel_id;
7105+
let short_channel_id = current_hop_data.short_channel_id;
7106+
let htlc_id = current_hop_data.htlc_id;
7107+
let blinded_failure = current_hop_data.blinded_failure;
7108+
log_trace!(
7109+
WithContext::from(&self.logger, None, Some(channel_id), Some(*payment_hash)),
7110+
"Failing {}HTLC with payment_hash {} backwards from us following Trampoline forwarding failure: {:?}",
7111+
if blinded_failure.is_some() { "blinded " } else { "" }, &payment_hash, onion_error
7112+
);
7113+
let failure = match blinded_failure {
7114+
Some(BlindedFailure::FromIntroductionNode) => {
7115+
let blinded_onion_error = HTLCFailReason::reason(LocalHTLCFailureReason::InvalidOnionBlinding, vec![0; 32]);
7116+
let err_packet = blinded_onion_error.get_encrypted_failure_packet(
7117+
&incoming_packet_shared_secret, &Some(incoming_trampoline_shared_secret.clone())
7118+
);
7119+
HTLCForwardInfo::FailHTLC { htlc_id, err_packet }
7120+
},
7121+
Some(BlindedFailure::FromBlindedNode) => {
7122+
HTLCForwardInfo::FailMalformedHTLC {
7123+
htlc_id,
7124+
failure_code: LocalHTLCFailureReason::InvalidOnionBlinding.failure_code(),
7125+
sha256_of_onion: [0; 32]
7126+
}
7127+
},
7128+
None => {
7129+
let err_packet = HTLCFailReason::reason(LocalHTLCFailureReason::TemporaryTrampolineFailure, Vec::new())
7130+
.get_encrypted_failure_packet(&incoming_packet_shared_secret, &Some(incoming_trampoline_shared_secret.clone()));
7131+
HTLCForwardInfo::FailHTLC { htlc_id, err_packet }
7132+
}
7133+
};
7134+
7135+
push_forward_event = self.decode_update_add_htlcs.lock().unwrap().is_empty();
7136+
let mut forward_htlcs = self.forward_htlcs.lock().unwrap();
7137+
push_forward_event &= forward_htlcs.is_empty();
7138+
7139+
match forward_htlcs.entry(short_channel_id) {
7140+
hash_map::Entry::Occupied(mut entry) => {
7141+
entry.get_mut().push(failure);
7142+
},
7143+
hash_map::Entry::Vacant(entry) => {
7144+
entry.insert(vec!(failure));
7145+
}
7146+
}
7147+
7148+
mem::drop(forward_htlcs);
7149+
7150+
let mut pending_events = self.pending_events.lock().unwrap();
7151+
pending_events.push_back((events::Event::HTLCHandlingFailed {
7152+
prev_channel_id: channel_id,
7153+
failed_next_destination: destination.clone(),
7154+
}, None));
7155+
}
7156+
},
70597157
}
70607158
push_forward_event
70617159
}
@@ -7514,6 +7612,63 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
75147612
}
75157613
});
75167614
},
7615+
HTLCSource::TrampolineForward { previous_hop_data, .. } => {
7616+
for current_previous_hop_data in previous_hop_data {
7617+
let prev_channel_id = current_previous_hop_data.channel_id;
7618+
let prev_user_channel_id = current_previous_hop_data.user_channel_id;
7619+
let prev_node_id = current_previous_hop_data.counterparty_node_id;
7620+
let completed_blocker = RAAMonitorUpdateBlockingAction::from_prev_hop_data(&current_previous_hop_data);
7621+
self.claim_funds_from_hop(current_previous_hop_data, payment_preimage, None,
7622+
|htlc_claim_value_msat, definitely_duplicate| {
7623+
let chan_to_release = Some(EventUnblockedChannel {
7624+
counterparty_node_id: next_channel_counterparty_node_id,
7625+
funding_txo: next_channel_outpoint,
7626+
channel_id: next_channel_id,
7627+
blocking_action: completed_blocker,
7628+
});
7629+
7630+
if definitely_duplicate && startup_replay {
7631+
// On startup we may get redundant claims which are related to
7632+
// monitor updates still in flight. In that case, we shouldn't
7633+
// immediately free, but instead let that monitor update complete
7634+
// in the background.
7635+
(None, None)
7636+
} else if definitely_duplicate {
7637+
if let Some(other_chan) = chan_to_release {
7638+
(Some(MonitorUpdateCompletionAction::FreeOtherChannelImmediately {
7639+
downstream_counterparty_node_id: other_chan.counterparty_node_id,
7640+
downstream_funding_outpoint: other_chan.funding_txo,
7641+
downstream_channel_id: other_chan.channel_id,
7642+
blocking_action: other_chan.blocking_action,
7643+
}), None)
7644+
} else { (None, None) }
7645+
} else {
7646+
let total_fee_earned_msat = if let Some(forwarded_htlc_value) = forwarded_htlc_value_msat {
7647+
if let Some(claimed_htlc_value) = htlc_claim_value_msat {
7648+
Some(claimed_htlc_value - forwarded_htlc_value)
7649+
} else { None }
7650+
} else { None };
7651+
debug_assert!(skimmed_fee_msat <= total_fee_earned_msat,
7652+
"skimmed_fee_msat must always be included in total_fee_earned_msat");
7653+
(Some(MonitorUpdateCompletionAction::EmitEventAndFreeOtherChannel {
7654+
event: events::Event::PaymentForwarded {
7655+
prev_channel_id: Some(prev_channel_id),
7656+
next_channel_id: Some(next_channel_id),
7657+
prev_user_channel_id,
7658+
next_user_channel_id,
7659+
prev_node_id,
7660+
next_node_id: Some(next_channel_counterparty_node_id),
7661+
total_fee_earned_msat,
7662+
skimmed_fee_msat,
7663+
claim_from_onchain_tx: from_onchain,
7664+
outbound_amount_forwarded_msat: forwarded_htlc_value_msat,
7665+
},
7666+
downstream_counterparty_and_funding_outpoint: chan_to_release,
7667+
}), None)
7668+
}
7669+
});
7670+
}
7671+
},
75177672
}
75187673
}
75197674

@@ -13163,6 +13318,24 @@ impl Readable for HTLCSource {
1316313318
})
1316413319
}
1316513320
1 => Ok(HTLCSource::PreviousHopData(Readable::read(reader)?)),
13321+
2 => {
13322+
let mut previous_hop_data = Vec::new();
13323+
let mut incoming_trampoline_shared_secret: crate::util::ser::RequiredWrapper<[u8; 32]> = crate::util::ser::RequiredWrapper(None);
13324+
let mut session_priv: crate::util::ser::RequiredWrapper<SecretKey> = crate::util::ser::RequiredWrapper(None);
13325+
let mut hops = Vec::new();
13326+
read_tlv_fields!(reader, {
13327+
(0, previous_hop_data, required_vec),
13328+
(2, incoming_trampoline_shared_secret, required),
13329+
(4, session_priv, required),
13330+
(6, hops, required_vec),
13331+
});
13332+
Ok(HTLCSource::TrampolineForward {
13333+
previous_hop_data,
13334+
incoming_trampoline_shared_secret: incoming_trampoline_shared_secret.0.unwrap(),
13335+
hops,
13336+
session_priv: session_priv.0.unwrap(),
13337+
})
13338+
},
1316613339
_ => Err(DecodeError::UnknownRequiredFeature),
1316713340
}
1316813341
}
@@ -13188,6 +13361,17 @@ impl Writeable for HTLCSource {
1318813361
1u8.write(writer)?;
1318913362
field.write(writer)?;
1319013363
}
13364+
HTLCSource::TrampolineForward { previous_hop_data: previous_hop_data_ref, ref incoming_trampoline_shared_secret, ref session_priv, hops: hops_ref } => {
13365+
2u8.write(writer)?;
13366+
let previous_hop_data = previous_hop_data_ref.clone();
13367+
let hops = hops_ref.clone();
13368+
write_tlv_fields!(writer, {
13369+
(0, previous_hop_data, required_vec),
13370+
(2, incoming_trampoline_shared_secret, required),
13371+
(4, session_priv, required),
13372+
(6, hops, required_vec),
13373+
});
13374+
}
1319113375
}
1319213376
Ok(())
1319313377
}
@@ -14368,6 +14552,55 @@ where
1436814552
} else { true }
1436914553
});
1437014554
},
14555+
HTLCSource::TrampolineForward { previous_hop_data, .. } => {
14556+
for current_previous_hop_data in previous_hop_data {
14557+
let pending_forward_matches_htlc = |info: &PendingAddHTLCInfo| {
14558+
info.prev_funding_outpoint == current_previous_hop_data.outpoint &&
14559+
info.prev_htlc_id == current_previous_hop_data.htlc_id
14560+
};
14561+
// The ChannelMonitor is now responsible for this HTLC's
14562+
// failure/success and will let us know what its outcome is. If we
14563+
// still have an entry for this HTLC in `forward_htlcs` or
14564+
// `pending_intercepted_htlcs`, we were apparently not persisted after
14565+
// the monitor was when forwarding the payment.
14566+
decode_update_add_htlcs.retain(|scid, update_add_htlcs| {
14567+
update_add_htlcs.retain(|update_add_htlc| {
14568+
let matches = *scid == current_previous_hop_data.short_channel_id &&
14569+
update_add_htlc.htlc_id == current_previous_hop_data.htlc_id;
14570+
if matches {
14571+
log_info!(logger, "Removing pending to-decode HTLC with hash {} as it was forwarded to the closed channel {}",
14572+
&htlc.payment_hash, &monitor.channel_id());
14573+
}
14574+
!matches
14575+
});
14576+
!update_add_htlcs.is_empty()
14577+
});
14578+
forward_htlcs.retain(|_, forwards| {
14579+
forwards.retain(|forward| {
14580+
if let HTLCForwardInfo::AddHTLC(htlc_info) = forward {
14581+
if pending_forward_matches_htlc(&htlc_info) {
14582+
log_info!(logger, "Removing pending to-forward HTLC with hash {} as it was forwarded to the closed channel {}",
14583+
&htlc.payment_hash, &monitor.channel_id());
14584+
false
14585+
} else { true }
14586+
} else { true }
14587+
});
14588+
!forwards.is_empty()
14589+
});
14590+
pending_intercepted_htlcs.as_mut().unwrap().retain(|intercepted_id, htlc_info| {
14591+
if pending_forward_matches_htlc(&htlc_info) {
14592+
log_info!(logger, "Removing pending intercepted HTLC with hash {} as it was forwarded to the closed channel {}",
14593+
&htlc.payment_hash, &monitor.channel_id());
14594+
pending_events_read.retain(|(event, _)| {
14595+
if let Event::HTLCIntercepted { intercept_id: ev_id, .. } = event {
14596+
intercepted_id != ev_id
14597+
} else { true }
14598+
});
14599+
false
14600+
} else { true }
14601+
});
14602+
}
14603+
}
1437114604
HTLCSource::OutboundRoute { payment_id, session_priv, path, .. } => {
1437214605
if let Some(preimage) = preimage_opt {
1437314606
let pending_events = Mutex::new(pending_events_read);

lightning/src/ln/onion_utils.rs

+5
Original file line numberDiff line numberDiff line change
@@ -992,8 +992,13 @@ pub fn process_onion_failure<T: secp256k1::Signing, L: Deref>(
992992
where
993993
L::Target: Logger,
994994
{
995+
let mut trampoline_forward_path_option = None;
995996
let (path, primary_session_priv) = match htlc_source {
996997
HTLCSource::OutboundRoute { ref path, ref session_priv, .. } => (path, session_priv),
998+
HTLCSource::TrampolineForward { ref hops, ref session_priv, .. } => {
999+
trampoline_forward_path_option.replace(Path { hops: hops.clone(), blinded_tail: None });
1000+
(trampoline_forward_path_option.as_ref().unwrap(), session_priv)
1001+
},
9971002
_ => unreachable!(),
9981003
};
9991004

lightning/src/util/ser.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1075,12 +1075,14 @@ impl Readable for Vec<u8> {
10751075

10761076
impl_for_vec!(ecdsa::Signature);
10771077
impl_for_vec!(crate::chain::channelmonitor::ChannelMonitorUpdate);
1078+
impl_for_vec!(crate::ln::channelmanager::HTLCPreviousHopData);
10781079
impl_for_vec!(crate::ln::channelmanager::MonitorUpdateCompletionAction);
10791080
impl_for_vec!(crate::ln::channelmanager::PaymentClaimDetails);
10801081
impl_for_vec!(crate::ln::msgs::SocketAddress);
10811082
impl_for_vec!((A, B), A, B);
10821083
impl_writeable_for_vec!(&crate::routing::router::BlindedTail);
10831084
impl_readable_for_vec!(crate::routing::router::BlindedTail);
1085+
impl_for_vec!(crate::routing::router::RouteHop);
10841086
impl_for_vec!(crate::routing::router::TrampolineHop);
10851087
impl_for_vec_with_element_length_prefix!(crate::ln::msgs::UpdateAddHTLC);
10861088
impl_writeable_for_vec_with_element_length_prefix!(&crate::ln::msgs::UpdateAddHTLC);

0 commit comments

Comments
 (0)