///
/// [`ChannelManager::claim_funds`]: crate::ln::channelmanager::ChannelManager::claim_funds
claim_deadline: Option<u32>,
+ /// A unique ID describing this payment (derived from the list of HTLCs in the payment).
+ ///
+ /// Payers may pay for the same [`PaymentHash`] multiple times (though this is unsafe and
+ /// an intermediary node may steal the funds). Thus, in order to accurately track when
+ /// payments are received and claimed, you should use this identifier.
+ ///
+ /// Only filled in for payments received on LDK versions 0.1 and higher.
+ payment_id: Option<PaymentId>,
},
/// Indicates a payment has been claimed and we've received money!
///
///
/// Payments received on LDK versions prior to 0.0.124 will have this field unset.
onion_fields: Option<RecipientOnionFields>,
+ /// A unique ID describing this payment (derived from the list of HTLCs in the payment).
+ ///
+ /// Payers may pay for the same [`PaymentHash`] multiple times (though this is unsafe and
+ /// an intermediary node may steal the funds). Thus, in order to accurately track when
+ /// payments are received and claimed, you should use this identifier.
+ ///
+ /// Only filled in for payments received on LDK versions 0.1 and higher.
+ payment_id: Option<PaymentId>,
},
/// Indicates that a peer connection with a node is needed in order to send an [`OnionMessage`].
///
},
&Event::PaymentClaimable { ref payment_hash, ref amount_msat, counterparty_skimmed_fee_msat,
ref purpose, ref receiver_node_id, ref via_channel_id, ref via_user_channel_id,
- ref claim_deadline, ref onion_fields
+ ref claim_deadline, ref onion_fields, ref payment_id,
} => {
1u8.write(writer)?;
let mut payment_secret = None;
(9, onion_fields, option),
(10, skimmed_fee_opt, option),
(11, payment_context, option),
+ (13, payment_id, option),
});
},
&Event::PaymentSent { ref payment_id, ref payment_preimage, ref payment_hash, ref fee_paid_msat } => {
// We never write the OpenChannelRequest events as, upon disconnection, peers
// drop any channels which have not yet exchanged funding_signed.
},
- &Event::PaymentClaimed { ref payment_hash, ref amount_msat, ref purpose, ref receiver_node_id, ref htlcs, ref sender_intended_total_msat, ref onion_fields } => {
+ &Event::PaymentClaimed { ref payment_hash, ref amount_msat, ref purpose,
+ ref receiver_node_id, ref htlcs, ref sender_intended_total_msat, ref onion_fields,
+ ref payment_id,
+ } => {
19u8.write(writer)?;
write_tlv_fields!(writer, {
(0, payment_hash, required),
(5, *htlcs, optional_vec),
(7, sender_intended_total_msat, option),
(9, onion_fields, option),
+ (11, payment_id, option),
});
},
&Event::ProbeSuccessful { ref payment_id, ref payment_hash, ref path } => {
let mut via_user_channel_id = None;
let mut onion_fields = None;
let mut payment_context = None;
+ let mut payment_id = None;
read_tlv_fields!(reader, {
(0, payment_hash, required),
(1, receiver_node_id, option),
(9, onion_fields, option),
(10, counterparty_skimmed_fee_msat_opt, option),
(11, payment_context, option),
+ (13, payment_id, option),
});
let purpose = match payment_secret {
Some(secret) => PaymentPurpose::from_parts(payment_preimage, secret, payment_context),
via_user_channel_id,
claim_deadline,
onion_fields,
+ payment_id,
}))
};
f()
let mut htlcs: Option<Vec<ClaimedHTLC>> = Some(vec![]);
let mut sender_intended_total_msat: Option<u64> = None;
let mut onion_fields = None;
+ let mut payment_id = None;
read_tlv_fields!(reader, {
(0, payment_hash, required),
(1, receiver_node_id, option),
(5, htlcs, optional_vec),
(7, sender_intended_total_msat, option),
(9, onion_fields, option),
+ (11, payment_id, option),
});
Ok(Some(Event::PaymentClaimed {
receiver_node_id,
htlcs: htlcs.unwrap_or_default(),
sender_intended_total_msat,
onion_fields,
+ payment_id,
}))
};
f()
use bitcoin::key::constants::SECRET_KEY_SIZE;
use bitcoin::network::Network;
-use bitcoin::hashes::Hash;
+use bitcoin::hashes::{Hash, HashEngine, HmacEngine};
use bitcoin::hashes::hmac::Hmac;
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hash_types::{BlockHash, Txid};
counterparty_node_id: Option<PublicKey>,
}
+#[derive(PartialEq, Eq)]
enum OnionPayload {
/// Indicates this incoming onion payload is for the purpose of paying an invoice.
Invoice {
}
/// HTLCs that are to us and can be failed/claimed by the user
+#[derive(PartialEq, Eq)]
struct ClaimableHTLC {
prev_hop: HTLCPreviousHopData,
cltv_expiry: u32,
}
}
+impl PartialOrd for ClaimableHTLC {
+ fn partial_cmp(&self, other: &ClaimableHTLC) -> Option<cmp::Ordering> {
+ Some(self.cmp(other))
+ }
+}
+impl Ord for ClaimableHTLC {
+ fn cmp(&self, other: &ClaimableHTLC) -> cmp::Ordering {
+ let res = (self.prev_hop.channel_id, self.prev_hop.htlc_id).cmp(
+ &(other.prev_hop.channel_id, other.prev_hop.htlc_id)
+ );
+ if res.is_eq() {
+ debug_assert!(self == other, "ClaimableHTLCs from the same source should be identical");
+ }
+ res
+ }
+}
+
/// A trait defining behavior for creating and verifing the HMAC for authenticating a given data.
pub trait Verification {
/// Constructs an HMAC to include in [`OffersContext`] for the data along with the given
}
}
+impl PaymentId {
+ fn for_inbound_from_htlcs<I: Iterator<Item=(ChannelId, u64)>>(key: &[u8; 32], htlcs: I) -> PaymentId {
+ let mut prev_pair = None;
+ let mut hasher = HmacEngine::new(key);
+ for (channel_id, htlc_id) in htlcs {
+ hasher.input(&channel_id.0);
+ hasher.input(&htlc_id.to_le_bytes());
+ if let Some(prev) = prev_pair {
+ debug_assert!(prev < (channel_id, htlc_id), "HTLCs should be sorted");
+ }
+ prev_pair = Some((channel_id, htlc_id));
+ }
+ PaymentId(Hmac::<Sha256>::from_engine(hasher).to_byte_array())
+ }
+}
+
impl Writeable for PaymentId {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
self.0.write(w)
htlcs: Vec<events::ClaimedHTLC>,
sender_intended_value: Option<u64>,
onion_fields: Option<RecipientOnionFields>,
+ payment_id: Option<PaymentId>,
}
impl_writeable_tlv_based!(ClaimingPayment, {
(0, amount_msat, required),
(5, htlcs, optional_vec),
(7, sender_intended_value, option),
(9, onion_fields, option),
+ (11, payment_id, option),
});
struct ClaimablePayment {
htlcs: Vec<ClaimableHTLC>,
}
+impl ClaimablePayment {
+ fn inbound_payment_id(&self, secret: &[u8; 32]) -> PaymentId {
+ PaymentId::for_inbound_from_htlcs(
+ secret,
+ self.htlcs.iter().map(|htlc| (htlc.prev_hop.channel_id, htlc.prev_hop.htlc_id))
+ )
+ }
+}
+
/// Represent the channel funding transaction type.
enum FundingType {
/// This variant is useful when we want LDK to validate the funding transaction and
} else {
claimable_payment.onion_fields = Some(onion_fields);
}
- let ref mut htlcs = &mut claimable_payment.htlcs;
let mut total_value = claimable_htlc.sender_intended_value;
let mut earliest_expiry = claimable_htlc.cltv_expiry;
- for htlc in htlcs.iter() {
+ for htlc in claimable_payment.htlcs.iter() {
total_value += htlc.sender_intended_value;
earliest_expiry = cmp::min(earliest_expiry, htlc.cltv_expiry);
if htlc.total_msat != claimable_htlc.total_msat {
#[allow(unused_assignments)] {
committed_to_claimable = true;
}
- htlcs.push(claimable_htlc);
- let amount_msat = htlcs.iter().map(|htlc| htlc.value).sum();
- htlcs.iter_mut().for_each(|htlc| htlc.total_value_received = Some(amount_msat));
- let counterparty_skimmed_fee_msat = htlcs.iter()
+ claimable_payment.htlcs.push(claimable_htlc);
+ let amount_msat =
+ claimable_payment.htlcs.iter().map(|htlc| htlc.value).sum();
+ claimable_payment.htlcs.iter_mut()
+ .for_each(|htlc| htlc.total_value_received = Some(amount_msat));
+ let counterparty_skimmed_fee_msat = claimable_payment.htlcs.iter()
.map(|htlc| htlc.counterparty_skimmed_fee_msat.unwrap_or(0)).sum();
debug_assert!(total_value.saturating_sub(amount_msat) <=
counterparty_skimmed_fee_msat);
+ claimable_payment.htlcs.sort();
+ let payment_id =
+ claimable_payment.inbound_payment_id(&self.inbound_payment_id_secret);
new_events.push_back((events::Event::PaymentClaimable {
receiver_node_id: Some(receiver_node_id),
payment_hash,
via_user_channel_id: Some(prev_user_channel_id),
claim_deadline: Some(earliest_expiry - HTLC_FAIL_BACK_BUFFER),
onion_fields: claimable_payment.onion_fields.clone(),
+ payment_id: Some(payment_id),
}, None));
payment_claimable_generated = true;
} else {
// Nothing to do - we haven't reached the total
// payment value yet, wait until we receive more
// MPP parts.
- htlcs.push(claimable_htlc);
+ claimable_payment.htlcs.push(claimable_htlc);
#[allow(unused_assignments)] {
committed_to_claimable = true;
}
}
}
+ let payment_id = payment.inbound_payment_id(&self.inbound_payment_id_secret);
let claiming_payment = claimable_payments.pending_claiming_payments
.entry(payment_hash)
.and_modify(|_| {
htlcs,
sender_intended_value,
onion_fields: payment.onion_fields,
+ payment_id: Some(payment_id),
}
});
htlcs,
sender_intended_value: sender_intended_total_msat,
onion_fields,
+ payment_id,
}) = payment {
self.pending_events.lock().unwrap().push_back((events::Event::PaymentClaimed {
payment_hash,
htlcs,
sender_intended_total_msat,
onion_fields,
+ payment_id,
}, None));
}
},
previous_hop_monitor.provide_payment_preimage(&payment_hash, &payment_preimage, &args.tx_broadcaster, &bounded_fee_estimator, &args.logger);
}
}
+ let payment_id = payment.inbound_payment_id(&inbound_payment_id_secret.unwrap());
pending_events_read.push_back((events::Event::PaymentClaimed {
receiver_node_id,
payment_hash,
htlcs: payment.htlcs.iter().map(events::ClaimedHTLC::from).collect(),
sender_intended_total_msat: payment.htlcs.first().map(|htlc| htlc.total_msat),
onion_fields: payment.onion_fields,
+ payment_id: Some(payment_id),
}, None));
}
}