// Alternatively, we can fill an outbound HTLC with a HTLCSource::OutboundRoute indicating this is
// our payment, which we can use to decode errors or inform the user that the payment was sent.
+#[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug
+enum PendingHTLCRouting {
+ Forward {
+ onion_packet: msgs::OnionPacket,
+ short_channel_id: u64, // This should be NonZero<u64> eventually when we bump MSRV
+ },
+ Receive {
+ payment_data: Option<msgs::FinalOnionHopData>,
+ },
+}
+
#[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug
pub(super) struct PendingHTLCInfo {
- onion_packet: Option<msgs::OnionPacket>,
+ routing: PendingHTLCRouting,
incoming_shared_secret: [u8; 32],
payment_hash: PaymentHash,
- short_channel_id: u64,
pub(super) amt_to_forward: u64,
pub(super) outgoing_cltv_value: u32,
}
incoming_packet_shared_secret: [u8; 32],
}
+struct ClaimableHTLC {
+ prev_hop: HTLCPreviousHopData,
+ value: u64,
+ /// Filled in when the HTLC was received with a payment_secret packet, which contains a
+ /// total_msat (which may differ from value if this is a Multi-Path Payment) and a
+ /// payment_secret which prevents path-probing attacks and can associate different HTLCs which
+ /// are part of the same payment.
+ payment_data: Option<msgs::FinalOnionHopData>,
+}
+
/// Tracks the inbound corresponding to an outbound HTLC
#[derive(Clone, PartialEq)]
pub(super) enum HTLCSource {
/// payment_preimage type, use to route payment between hop
#[derive(Hash, Copy, Clone, PartialEq, Eq, Debug)]
pub struct PaymentPreimage(pub [u8;32]);
+/// payment_secret type, use to authenticate sender to the receiver and tie MPP HTLCs together
+#[derive(Hash, Copy, Clone, PartialEq, Eq, Debug)]
+pub struct PaymentSecret(pub [u8;32]);
type ShutdownResult = (Option<OutPoint>, ChannelMonitorUpdate, Vec<(HTLCSource, PaymentHash)>);
/// guarantees are made about the existence of a channel with the short id here, nor the short
/// ids in the PendingHTLCInfo!
pub(super) forward_htlcs: HashMap<u64, Vec<HTLCForwardInfo>>,
- /// payment_hash -> Vec<(amount_received, htlc_source)> for tracking things that were to us and
- /// can be failed/claimed by the user
+ /// (payment_hash, payment_secret) -> Vec<HTLCs> for tracking HTLCs that
+ /// were to us and can be failed/claimed by the user
/// Note that while this is held in the same mutex as the channels themselves, no consistency
/// guarantees are made about the channels given here actually existing anymore by the time you
/// go to read them!
- pub(super) claimable_htlcs: HashMap<PaymentHash, Vec<(u64, HTLCPreviousHopData)>>,
+ /// TODO: We need to time out HTLCs sitting here which are waiting on other AMP HTLCs to
+ /// arrive.
+ claimable_htlcs: HashMap<(PaymentHash, Option<PaymentSecret>), Vec<ClaimableHTLC>>,
/// Messages to send to peers - pushed to in the same lock that they are generated in (except
/// for broadcast messages, where ordering isn't as strict).
pub(super) pending_msg_events: Vec<events::MessageSendEvent>,
return_err!("Upstream node set CLTV to the wrong value", 18, &byte_utils::be32_to_array(msg.cltv_expiry));
}
+ let payment_data = match next_hop_data.format {
+ msgs::OnionHopDataFormat::Legacy { .. } => None,
+ msgs::OnionHopDataFormat::NonFinalNode { .. } => return_err!("Got non final data with an HMAC of 0", 0x4000 | 22, &[0;0]),
+ msgs::OnionHopDataFormat::FinalNode { payment_data } => payment_data,
+ };
+
// Note that we could obviously respond immediately with an update_fulfill_htlc
// message, however that would leak that we are the recipient of this payment, so
// instead we stay symmetric with the forwarding case, only responding (after a
// delay) once they've send us a commitment_signed!
PendingHTLCStatus::Forward(PendingHTLCInfo {
- onion_packet: None,
+ routing: PendingHTLCRouting::Receive { payment_data },
payment_hash: msg.payment_hash.clone(),
- short_channel_id: 0,
incoming_shared_secret: shared_secret,
amt_to_forward: next_hop_data.amt_to_forward,
outgoing_cltv_value: next_hop_data.outgoing_cltv_value,
let short_channel_id = match next_hop_data.format {
msgs::OnionHopDataFormat::Legacy { short_channel_id } => short_channel_id,
msgs::OnionHopDataFormat::NonFinalNode { short_channel_id } => short_channel_id,
- msgs::OnionHopDataFormat::FinalNode => {
+ msgs::OnionHopDataFormat::FinalNode { .. } => {
return_err!("Final Node OnionHopData provided for us as an intermediary node", 0x4000 | 22, &[0;0]);
},
};
PendingHTLCStatus::Forward(PendingHTLCInfo {
- onion_packet: Some(outgoing_packet),
+ routing: PendingHTLCRouting::Forward {
+ onion_packet: outgoing_packet,
+ short_channel_id: short_channel_id,
+ },
payment_hash: msg.payment_hash.clone(),
- short_channel_id: short_channel_id,
incoming_shared_secret: shared_secret,
amt_to_forward: next_hop_data.amt_to_forward,
outgoing_cltv_value: next_hop_data.outgoing_cltv_value,
};
channel_state = Some(self.channel_state.lock().unwrap());
- if let &PendingHTLCStatus::Forward(PendingHTLCInfo { ref onion_packet, ref short_channel_id, ref amt_to_forward, ref outgoing_cltv_value, .. }) = &pending_forward_info {
- if onion_packet.is_some() { // If short_channel_id is 0 here, we'll reject them in the body here
+ if let &PendingHTLCStatus::Forward(PendingHTLCInfo { ref routing, ref amt_to_forward, ref outgoing_cltv_value, .. }) = &pending_forward_info {
+ // If short_channel_id is 0 here, we'll reject the HTLC as there cannot be a channel
+ // with a short_channel_id of 0. This is important as various things later assume
+ // short_channel_id is non-0 in any ::Forward.
+ if let &PendingHTLCRouting::Forward { ref short_channel_id, .. } = routing {
let id_option = channel_state.as_ref().unwrap().short_to_id.get(&short_channel_id).cloned();
let forwarding_id = match id_option {
None => { // unknown_next_peer
/// In case of APIError::MonitorUpdateFailed, the commitment update has been irrevocably
/// committed on our end and we're just waiting for a monitor update to send it. Do NOT retry
/// the payment via a different route unless you intend to pay twice!
- pub fn send_payment(&self, route: Route, payment_hash: PaymentHash) -> Result<(), APIError> {
+ ///
+ /// payment_secret is unrelated to payment_hash (or PaymentPreimage) and exists to authenticate
+ /// the sender to the recipient and prevent payment-probing (deanonymization) attacks. For
+ /// newer nodes, it will be provided to you in the invoice. If you do not have one, the Route
+ /// must not contain multiple paths as multi-path payments require a recipient-provided
+ /// payment_secret.
+ /// If a payment_secret *is* provided, we assume that the invoice had the payment_secret feature
+ /// bit set (either as required or as available). If multiple paths are present in the Route,
+ /// we assume the invoice had the basic_mpp feature set.
+ pub fn send_payment(&self, route: Route, payment_hash: PaymentHash, payment_secret: &Option<PaymentSecret>) -> Result<(), APIError> {
if route.hops.len() < 1 || route.hops.len() > 20 {
return Err(APIError::RouteError{err: "Route didn't go anywhere/had bogus size"});
}
let onion_keys = secp_call!(onion_utils::construct_onion_keys(&self.secp_ctx, &route, &session_priv),
APIError::RouteError{err: "Pubkey along hop was maliciously selected"});
- let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route, cur_height)?;
+ let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route, payment_secret, cur_height)?;
if onion_utils::route_size_insane(&onion_payloads) {
return Err(APIError::RouteError{err: "Route size too large considering onion data"});
}
htlc_id: prev_htlc_id,
incoming_packet_shared_secret: forward_info.incoming_shared_secret,
});
- failed_forwards.push((htlc_source, forward_info.payment_hash, 0x4000 | 10, None));
+ failed_forwards.push((htlc_source, forward_info.payment_hash,
+ HTLCFailReason::Reason { failure_code: 0x4000 | 10, data: Vec::new() }
+ ));
},
HTLCForwardInfo::FailHTLC { .. } => {
// Channel went away before we could fail it. This implies
let mut fail_htlc_msgs = Vec::new();
for forward_info in pending_forwards.drain(..) {
match forward_info {
- HTLCForwardInfo::AddHTLC { prev_short_channel_id, prev_htlc_id, forward_info } => {
- log_trace!(self, "Adding HTLC from short id {} with payment_hash {} to channel with short id {} after delay", log_bytes!(forward_info.payment_hash.0), prev_short_channel_id, short_chan_id);
+ HTLCForwardInfo::AddHTLC { prev_short_channel_id, prev_htlc_id, forward_info: PendingHTLCInfo {
+ routing: PendingHTLCRouting::Forward {
+ onion_packet, ..
+ }, incoming_shared_secret, payment_hash, amt_to_forward, outgoing_cltv_value }, } => {
+ log_trace!(self, "Adding HTLC from short id {} with payment_hash {} to channel with short id {} after delay", log_bytes!(payment_hash.0), prev_short_channel_id, short_chan_id);
let htlc_source = HTLCSource::PreviousHopData(HTLCPreviousHopData {
short_channel_id: prev_short_channel_id,
htlc_id: prev_htlc_id,
- incoming_packet_shared_secret: forward_info.incoming_shared_secret,
+ incoming_packet_shared_secret: incoming_shared_secret,
});
- match chan.get_mut().send_htlc(forward_info.amt_to_forward, forward_info.payment_hash, forward_info.outgoing_cltv_value, htlc_source.clone(), forward_info.onion_packet.unwrap()) {
+ match chan.get_mut().send_htlc(amt_to_forward, payment_hash, outgoing_cltv_value, htlc_source.clone(), onion_packet) {
Err(e) => {
if let ChannelError::Ignore(msg) = e {
- log_trace!(self, "Failed to forward HTLC with payment_hash {}: {}", log_bytes!(forward_info.payment_hash.0), msg);
+ log_trace!(self, "Failed to forward HTLC with payment_hash {}: {}", log_bytes!(payment_hash.0), msg);
} else {
panic!("Stated return value requirements in send_htlc() were not met");
}
let chan_update = self.get_channel_update(chan.get()).unwrap();
- failed_forwards.push((htlc_source, forward_info.payment_hash, 0x1000 | 7, Some(chan_update)));
+ failed_forwards.push((htlc_source, payment_hash,
+ HTLCFailReason::Reason { failure_code: 0x1000 | 7, data: chan_update.encode_with_len() }
+ ));
continue;
},
Ok(update_add) => {
}
}
},
+ HTLCForwardInfo::AddHTLC { .. } => {
+ panic!("short_channel_id != 0 should imply any pending_forward entries are of type Forward");
+ },
HTLCForwardInfo::FailHTLC { htlc_id, err_packet } => {
log_trace!(self, "Failing HTLC back to channel with short id {} after delay", short_chan_id);
match chan.get_mut().get_update_fail_htlc(htlc_id, err_packet) {
} else {
for forward_info in pending_forwards.drain(..) {
match forward_info {
- HTLCForwardInfo::AddHTLC { prev_short_channel_id, prev_htlc_id, forward_info } => {
- let prev_hop_data = HTLCPreviousHopData {
+ HTLCForwardInfo::AddHTLC { prev_short_channel_id, prev_htlc_id, forward_info: PendingHTLCInfo {
+ routing: PendingHTLCRouting::Receive { payment_data },
+ incoming_shared_secret, payment_hash, amt_to_forward, .. }, } => {
+ let prev_hop = HTLCPreviousHopData {
short_channel_id: prev_short_channel_id,
htlc_id: prev_htlc_id,
- incoming_packet_shared_secret: forward_info.incoming_shared_secret,
+ incoming_packet_shared_secret: incoming_shared_secret,
};
- match channel_state.claimable_htlcs.entry(forward_info.payment_hash) {
- hash_map::Entry::Occupied(mut entry) => entry.get_mut().push((forward_info.amt_to_forward, prev_hop_data)),
- hash_map::Entry::Vacant(entry) => { entry.insert(vec![(forward_info.amt_to_forward, prev_hop_data)]); },
- };
- new_events.push(events::Event::PaymentReceived {
- payment_hash: forward_info.payment_hash,
- amt: forward_info.amt_to_forward,
+
+ let mut total_value = 0;
+ let payment_secret_opt =
+ if let &Some(ref data) = &payment_data { Some(data.payment_secret.clone()) } else { None };
+ let htlcs = channel_state.claimable_htlcs.entry((payment_hash, payment_secret_opt))
+ .or_insert(Vec::new());
+ htlcs.push(ClaimableHTLC {
+ prev_hop,
+ value: amt_to_forward,
+ payment_data: payment_data.clone(),
});
+ if let &Some(ref data) = &payment_data {
+ for htlc in htlcs.iter() {
+ total_value += htlc.value;
+ if htlc.payment_data.as_ref().unwrap().total_msat != data.total_msat {
+ total_value = msgs::MAX_VALUE_MSAT;
+ }
+ if total_value >= msgs::MAX_VALUE_MSAT { break; }
+ }
+ if total_value >= msgs::MAX_VALUE_MSAT || total_value > data.total_msat {
+ for htlc in htlcs.iter() {
+ failed_forwards.push((HTLCSource::PreviousHopData(HTLCPreviousHopData {
+ short_channel_id: htlc.prev_hop.short_channel_id,
+ htlc_id: htlc.prev_hop.htlc_id,
+ incoming_packet_shared_secret: htlc.prev_hop.incoming_packet_shared_secret,
+ }), payment_hash,
+ HTLCFailReason::Reason { failure_code: 0x4000 | 15, data: byte_utils::be64_to_array(htlc.value).to_vec() }
+ ));
+ }
+ } else if total_value == data.total_msat {
+ new_events.push(events::Event::PaymentReceived {
+ payment_hash: payment_hash,
+ payment_secret: Some(data.payment_secret),
+ amt: total_value,
+ });
+ }
+ } else {
+ new_events.push(events::Event::PaymentReceived {
+ payment_hash: payment_hash,
+ payment_secret: None,
+ amt: amt_to_forward,
+ });
+ }
+ },
+ HTLCForwardInfo::AddHTLC { .. } => {
+ panic!("short_channel_id == 0 should imply any pending_forward entries are of type Receive");
},
HTLCForwardInfo::FailHTLC { .. } => {
panic!("Got pending fail of our own HTLC");
}
}
- for (htlc_source, payment_hash, failure_code, update) in failed_forwards.drain(..) {
- match update {
- None => self.fail_htlc_backwards_internal(self.channel_state.lock().unwrap(), htlc_source, &payment_hash, HTLCFailReason::Reason { failure_code, data: Vec::new() }),
- Some(chan_update) => self.fail_htlc_backwards_internal(self.channel_state.lock().unwrap(), htlc_source, &payment_hash, HTLCFailReason::Reason { failure_code, data: chan_update.encode_with_len() }),
- };
+ for (htlc_source, payment_hash, failure_reason) in failed_forwards.drain(..) {
+ self.fail_htlc_backwards_internal(self.channel_state.lock().unwrap(), htlc_source, &payment_hash, failure_reason);
}
for (their_node_id, err) in handle_errors.drain(..) {
/// along the path (including in our own channel on which we received it).
/// Returns false if no payment was found to fail backwards, true if the process of failing the
/// HTLC backwards has been started.
- pub fn fail_htlc_backwards(&self, payment_hash: &PaymentHash) -> bool {
+ pub fn fail_htlc_backwards(&self, payment_hash: &PaymentHash, payment_secret: &Option<PaymentSecret>) -> bool {
let _ = self.total_consistency_lock.read().unwrap();
let mut channel_state = Some(self.channel_state.lock().unwrap());
- let removed_source = channel_state.as_mut().unwrap().claimable_htlcs.remove(payment_hash);
+ let removed_source = channel_state.as_mut().unwrap().claimable_htlcs.remove(&(*payment_hash, *payment_secret));
if let Some(mut sources) = removed_source {
- for (recvd_value, htlc_with_hash) in sources.drain(..) {
+ for htlc in sources.drain(..) {
if channel_state.is_none() { channel_state = Some(self.channel_state.lock().unwrap()); }
self.fail_htlc_backwards_internal(channel_state.take().unwrap(),
- HTLCSource::PreviousHopData(htlc_with_hash), payment_hash,
- HTLCFailReason::Reason { failure_code: 0x4000 | 15, data: byte_utils::be64_to_array(recvd_value).to_vec() });
+ HTLCSource::PreviousHopData(htlc.prev_hop), payment_hash,
+ HTLCFailReason::Reason { failure_code: 0x4000 | 15, data: byte_utils::be64_to_array(htlc.value).to_vec() });
}
true
} else { false }
/// motivated attackers.
///
/// May panic if called except in response to a PaymentReceived event.
- pub fn claim_funds(&self, payment_preimage: PaymentPreimage, expected_amount: u64) -> bool {
+ pub fn claim_funds(&self, payment_preimage: PaymentPreimage, payment_secret: &Option<PaymentSecret>, expected_amount: u64) -> bool {
let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner());
let _ = self.total_consistency_lock.read().unwrap();
let mut channel_state = Some(self.channel_state.lock().unwrap());
- let removed_source = channel_state.as_mut().unwrap().claimable_htlcs.remove(&payment_hash);
+ let removed_source = channel_state.as_mut().unwrap().claimable_htlcs.remove(&(payment_hash, *payment_secret));
if let Some(mut sources) = removed_source {
- for (received_amount, htlc_with_hash) in sources.drain(..) {
+ for htlc in sources.drain(..) {
if channel_state.is_none() { channel_state = Some(self.channel_state.lock().unwrap()); }
- if received_amount < expected_amount || received_amount > expected_amount * 2 {
- let mut htlc_msat_data = byte_utils::be64_to_array(received_amount).to_vec();
+ if htlc.value < expected_amount || htlc.value > expected_amount * 2 {
+ let mut htlc_msat_data = byte_utils::be64_to_array(htlc.value).to_vec();
let mut height_data = byte_utils::be32_to_array(self.latest_block_height.load(Ordering::Acquire) as u32).to_vec();
htlc_msat_data.append(&mut height_data);
self.fail_htlc_backwards_internal(channel_state.take().unwrap(),
- HTLCSource::PreviousHopData(htlc_with_hash), &payment_hash,
+ HTLCSource::PreviousHopData(htlc.prev_hop), &payment_hash,
HTLCFailReason::Reason { failure_code: 0x4000|15, data: htlc_msat_data });
} else {
- self.claim_funds_internal(channel_state.take().unwrap(), HTLCSource::PreviousHopData(htlc_with_hash), payment_preimage);
+ self.claim_funds_internal(channel_state.take().unwrap(), HTLCSource::PreviousHopData(htlc.prev_hop), payment_preimage);
}
}
true
forward_event = Some(Duration::from_millis(MIN_HTLC_RELAY_HOLDING_CELL_MILLIS))
}
for (forward_info, prev_htlc_id) in pending_forwards.drain(..) {
- match channel_state.forward_htlcs.entry(forward_info.short_channel_id) {
+ match channel_state.forward_htlcs.entry(match forward_info.routing {
+ PendingHTLCRouting::Forward { short_channel_id, .. } => short_channel_id,
+ PendingHTLCRouting::Receive { .. } => 0,
+ }) {
hash_map::Entry::Occupied(mut entry) => {
entry.get_mut().push(HTLCForwardInfo::AddHTLC { prev_short_channel_id, prev_htlc_id, forward_info });
},
impl Writeable for PendingHTLCInfo {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {
- self.onion_packet.write(writer)?;
+ match &self.routing {
+ &PendingHTLCRouting::Forward { ref onion_packet, ref short_channel_id } => {
+ 0u8.write(writer)?;
+ onion_packet.write(writer)?;
+ short_channel_id.write(writer)?;
+ },
+ &PendingHTLCRouting::Receive { ref payment_data } => {
+ 1u8.write(writer)?;
+ payment_data.write(writer)?;
+ },
+ }
self.incoming_shared_secret.write(writer)?;
self.payment_hash.write(writer)?;
- self.short_channel_id.write(writer)?;
self.amt_to_forward.write(writer)?;
self.outgoing_cltv_value.write(writer)?;
Ok(())
impl Readable for PendingHTLCInfo {
fn read<R: ::std::io::Read>(reader: &mut R) -> Result<PendingHTLCInfo, DecodeError> {
Ok(PendingHTLCInfo {
- onion_packet: Readable::read(reader)?,
+ routing: match Readable::read(reader)? {
+ 0u8 => PendingHTLCRouting::Forward {
+ onion_packet: Readable::read(reader)?,
+ short_channel_id: Readable::read(reader)?,
+ },
+ 1u8 => PendingHTLCRouting::Receive {
+ payment_data: Readable::read(reader)?,
+ },
+ _ => return Err(DecodeError::InvalidValue),
+ },
incoming_shared_secret: Readable::read(reader)?,
payment_hash: Readable::read(reader)?,
- short_channel_id: Readable::read(reader)?,
amt_to_forward: Readable::read(reader)?,
outgoing_cltv_value: Readable::read(reader)?,
})
incoming_packet_shared_secret
});
+impl_writeable!(ClaimableHTLC, 0, {
+ prev_hop,
+ value,
+ payment_data
+});
+
impl Writeable for HTLCSource {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {
match self {
for (payment_hash, previous_hops) in channel_state.claimable_htlcs.iter() {
payment_hash.write(writer)?;
(previous_hops.len() as u64).write(writer)?;
- for &(recvd_amt, ref previous_hop) in previous_hops.iter() {
- recvd_amt.write(writer)?;
- previous_hop.write(writer)?;
+ for htlc in previous_hops.iter() {
+ htlc.write(writer)?;
}
}
let previous_hops_len: u64 = Readable::read(reader)?;
let mut previous_hops = Vec::with_capacity(cmp::min(previous_hops_len as usize, 2));
for _ in 0..previous_hops_len {
- previous_hops.push((Readable::read(reader)?, Readable::read(reader)?));
+ previous_hops.push(Readable::read(reader)?);
}
claimable_htlcs.insert(payment_hash, previous_hops);
}