Skip to content

Commit 1fcbb9f

Browse files
committed
Add a PaymentId for inbound payments
We expect our users to have fully idempotent `Event` handling as we may replay events on restart for one of a number of reasons. This isn't a big deal as long as all our events have some kind of identifier users can use to check if the `Event` has already been handled. For outbound payments, this is the `PaymentId` they provide in the send methods, however for inbound payments we don't have a great option. `PaymentHash` largely suffices - users can simply always claim in response to a `PaymentClaimable` of sufficient value and treat a `PaymentClaimed` event as duplicate any time they see a second one for the same `PaymentHash`. This mostly works, but may result in accepting duplicative payments if someone (incorrectly) pays twice for the same `PaymentHash`. Users could also fail for duplicative `PaymentClaimable` events of the same `PaymentHash`, but doing so may result in spuriously failing a payment if the `PaymentClaimable` event is a replay and they never saw a corresponding `PaymentClaimed` event. While none of this will result in spuriously thinking they've been paid when they have not, it does result in some pretty awkward semantics which we'd rather avoid our users having to deal with. Instead, here, we add a new `PaymentId` which is simply an HMAC of the HTLCs (as Channel ID, HTLC ID pairs) which were included in the payment.
1 parent 7014391 commit 1fcbb9f

File tree

3 files changed

+95
-12
lines changed

3 files changed

+95
-12
lines changed

lightning/src/events/mod.rs

+29-2
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,14 @@ pub enum Event {
748748
///
749749
/// [`ChannelManager::claim_funds`]: crate::ln::channelmanager::ChannelManager::claim_funds
750750
claim_deadline: Option<u32>,
751+
/// A unique ID describing this payment (derived from the list of HTLCs in the payment).
752+
///
753+
/// Payers may pay for the same [`PaymentHash`] multiple times (though this is unsafe and
754+
/// an intermediary node may steal the funds). Thus, in order to accurately track when
755+
/// payments are received and claimed, you should use this identifier.
756+
///
757+
/// Only filled in for payments received on LDK versions 0.1 and higher.
758+
payment_id: Option<PaymentId>,
751759
},
752760
/// Indicates a payment has been claimed and we've received money!
753761
///
@@ -795,6 +803,14 @@ pub enum Event {
795803
///
796804
/// Payments received on LDK versions prior to 0.0.124 will have this field unset.
797805
onion_fields: Option<RecipientOnionFields>,
806+
/// A unique ID describing this payment (derived from the list of HTLCs in the payment).
807+
///
808+
/// Payers may pay for the same [`PaymentHash`] multiple times (though this is unsafe and
809+
/// an intermediary node may steal the funds). Thus, in order to accurately track when
810+
/// payments are received and claimed, you should use this identifier.
811+
///
812+
/// Only filled in for payments received on LDK versions 0.1 and higher.
813+
payment_id: Option<PaymentId>,
798814
},
799815
/// Indicates that a peer connection with a node is needed in order to send an [`OnionMessage`].
800816
///
@@ -1431,7 +1447,7 @@ impl Writeable for Event {
14311447
},
14321448
&Event::PaymentClaimable { ref payment_hash, ref amount_msat, counterparty_skimmed_fee_msat,
14331449
ref purpose, ref receiver_node_id, ref via_channel_id, ref via_user_channel_id,
1434-
ref claim_deadline, ref onion_fields
1450+
ref claim_deadline, ref onion_fields, ref payment_id,
14351451
} => {
14361452
1u8.write(writer)?;
14371453
let mut payment_secret = None;
@@ -1477,6 +1493,7 @@ impl Writeable for Event {
14771493
(9, onion_fields, option),
14781494
(10, skimmed_fee_opt, option),
14791495
(11, payment_context, option),
1496+
(13, payment_id, option),
14801497
});
14811498
},
14821499
&Event::PaymentSent { ref payment_id, ref payment_preimage, ref payment_hash, ref fee_paid_msat } => {
@@ -1633,7 +1650,10 @@ impl Writeable for Event {
16331650
// We never write the OpenChannelRequest events as, upon disconnection, peers
16341651
// drop any channels which have not yet exchanged funding_signed.
16351652
},
1636-
&Event::PaymentClaimed { ref payment_hash, ref amount_msat, ref purpose, ref receiver_node_id, ref htlcs, ref sender_intended_total_msat, ref onion_fields } => {
1653+
&Event::PaymentClaimed { ref payment_hash, ref amount_msat, ref purpose,
1654+
ref receiver_node_id, ref htlcs, ref sender_intended_total_msat, ref onion_fields,
1655+
ref payment_id,
1656+
} => {
16371657
19u8.write(writer)?;
16381658
write_tlv_fields!(writer, {
16391659
(0, payment_hash, required),
@@ -1643,6 +1663,7 @@ impl Writeable for Event {
16431663
(5, *htlcs, optional_vec),
16441664
(7, sender_intended_total_msat, option),
16451665
(9, onion_fields, option),
1666+
(11, payment_id, option),
16461667
});
16471668
},
16481669
&Event::ProbeSuccessful { ref payment_id, ref payment_hash, ref path } => {
@@ -1766,6 +1787,7 @@ impl MaybeReadable for Event {
17661787
let mut via_user_channel_id = None;
17671788
let mut onion_fields = None;
17681789
let mut payment_context = None;
1790+
let mut payment_id = None;
17691791
read_tlv_fields!(reader, {
17701792
(0, payment_hash, required),
17711793
(1, receiver_node_id, option),
@@ -1779,6 +1801,7 @@ impl MaybeReadable for Event {
17791801
(9, onion_fields, option),
17801802
(10, counterparty_skimmed_fee_msat_opt, option),
17811803
(11, payment_context, option),
1804+
(13, payment_id, option),
17821805
});
17831806
let purpose = match payment_secret {
17841807
Some(secret) => PaymentPurpose::from_parts(payment_preimage, secret, payment_context),
@@ -1795,6 +1818,7 @@ impl MaybeReadable for Event {
17951818
via_user_channel_id,
17961819
claim_deadline,
17971820
onion_fields,
1821+
payment_id,
17981822
}))
17991823
};
18001824
f()
@@ -2036,6 +2060,7 @@ impl MaybeReadable for Event {
20362060
let mut htlcs: Option<Vec<ClaimedHTLC>> = Some(vec![]);
20372061
let mut sender_intended_total_msat: Option<u64> = None;
20382062
let mut onion_fields = None;
2063+
let mut payment_id = None;
20392064
read_tlv_fields!(reader, {
20402065
(0, payment_hash, required),
20412066
(1, receiver_node_id, option),
@@ -2044,6 +2069,7 @@ impl MaybeReadable for Event {
20442069
(5, htlcs, optional_vec),
20452070
(7, sender_intended_total_msat, option),
20462071
(9, onion_fields, option),
2072+
(11, payment_id, option),
20472073
});
20482074
Ok(Some(Event::PaymentClaimed {
20492075
receiver_node_id,
@@ -2053,6 +2079,7 @@ impl MaybeReadable for Event {
20532079
htlcs: htlcs.unwrap_or_default(),
20542080
sender_intended_total_msat,
20552081
onion_fields,
2082+
payment_id,
20562083
}))
20572084
};
20582085
f()

lightning/src/ln/channelmanager.rs

+65-8
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use bitcoin::constants::ChainHash;
2323
use bitcoin::key::constants::SECRET_KEY_SIZE;
2424
use bitcoin::network::Network;
2525

26-
use bitcoin::hashes::Hash;
26+
use bitcoin::hashes::{Hash, HashEngine, HmacEngine};
2727
use bitcoin::hashes::hmac::Hmac;
2828
use bitcoin::hashes::sha256::Hash as Sha256;
2929
use bitcoin::hash_types::{BlockHash, Txid};
@@ -366,6 +366,7 @@ pub(crate) struct HTLCPreviousHopData {
366366
counterparty_node_id: Option<PublicKey>,
367367
}
368368

369+
#[derive(PartialEq, Eq)]
369370
enum OnionPayload {
370371
/// Indicates this incoming onion payload is for the purpose of paying an invoice.
371372
Invoice {
@@ -378,6 +379,7 @@ enum OnionPayload {
378379
}
379380

380381
/// HTLCs that are to us and can be failed/claimed by the user
382+
#[derive(PartialEq, Eq)]
381383
struct ClaimableHTLC {
382384
prev_hop: HTLCPreviousHopData,
383385
cltv_expiry: u32,
@@ -409,6 +411,23 @@ impl From<&ClaimableHTLC> for events::ClaimedHTLC {
409411
}
410412
}
411413

414+
impl PartialOrd for ClaimableHTLC {
415+
fn partial_cmp(&self, other: &ClaimableHTLC) -> Option<cmp::Ordering> {
416+
Some(self.cmp(other))
417+
}
418+
}
419+
impl Ord for ClaimableHTLC {
420+
fn cmp(&self, other: &ClaimableHTLC) -> cmp::Ordering {
421+
let res = (self.prev_hop.channel_id, self.prev_hop.htlc_id).cmp(
422+
&(other.prev_hop.channel_id, other.prev_hop.htlc_id)
423+
);
424+
if res.is_eq() {
425+
debug_assert!(self == other, "ClaimableHTLCs from the same source should be identical");
426+
}
427+
res
428+
}
429+
}
430+
412431
/// A trait defining behavior for creating and verifing the HMAC for authenticating a given data.
413432
pub trait Verification {
414433
/// Constructs an HMAC to include in [`OffersContext`] for the data along with the given
@@ -471,6 +490,22 @@ impl Verification for PaymentId {
471490
}
472491
}
473492

493+
impl PaymentId {
494+
fn for_inbound_from_htlcs<I: Iterator<Item=(ChannelId, u64)>>(key: &[u8; 32], htlcs: I) -> PaymentId {
495+
let mut prev_pair = None;
496+
let mut hasher = HmacEngine::new(key);
497+
for (channel_id, htlc_id) in htlcs {
498+
hasher.input(&channel_id.0);
499+
hasher.input(&htlc_id.to_le_bytes());
500+
if let Some(prev) = prev_pair {
501+
debug_assert!(prev < (channel_id, htlc_id), "HTLCs should be sorted");
502+
}
503+
prev_pair = Some((channel_id, htlc_id));
504+
}
505+
PaymentId(Hmac::<Sha256>::from_engine(hasher).to_byte_array())
506+
}
507+
}
508+
474509
impl Writeable for PaymentId {
475510
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
476511
self.0.write(w)
@@ -744,6 +779,7 @@ struct ClaimingPayment {
744779
htlcs: Vec<events::ClaimedHTLC>,
745780
sender_intended_value: Option<u64>,
746781
onion_fields: Option<RecipientOnionFields>,
782+
payment_id: Option<PaymentId>,
747783
}
748784
impl_writeable_tlv_based!(ClaimingPayment, {
749785
(0, amount_msat, required),
@@ -752,6 +788,7 @@ impl_writeable_tlv_based!(ClaimingPayment, {
752788
(5, htlcs, optional_vec),
753789
(7, sender_intended_value, option),
754790
(9, onion_fields, option),
791+
(11, payment_id, option),
755792
});
756793

757794
struct ClaimablePayment {
@@ -760,6 +797,15 @@ struct ClaimablePayment {
760797
htlcs: Vec<ClaimableHTLC>,
761798
}
762799

800+
impl ClaimablePayment {
801+
fn inbound_payment_id(&self, secret: &[u8; 32]) -> PaymentId {
802+
PaymentId::for_inbound_from_htlcs(
803+
secret,
804+
self.htlcs.iter().map(|htlc| (htlc.prev_hop.channel_id, htlc.prev_hop.htlc_id))
805+
)
806+
}
807+
}
808+
763809
/// Represent the channel funding transaction type.
764810
enum FundingType {
765811
/// This variant is useful when we want LDK to validate the funding transaction and
@@ -5627,10 +5673,9 @@ where
56275673
} else {
56285674
claimable_payment.onion_fields = Some(onion_fields);
56295675
}
5630-
let ref mut htlcs = &mut claimable_payment.htlcs;
56315676
let mut total_value = claimable_htlc.sender_intended_value;
56325677
let mut earliest_expiry = claimable_htlc.cltv_expiry;
5633-
for htlc in htlcs.iter() {
5678+
for htlc in claimable_payment.htlcs.iter() {
56345679
total_value += htlc.sender_intended_value;
56355680
earliest_expiry = cmp::min(earliest_expiry, htlc.cltv_expiry);
56365681
if htlc.total_msat != claimable_htlc.total_msat {
@@ -5652,13 +5697,18 @@ where
56525697
#[allow(unused_assignments)] {
56535698
committed_to_claimable = true;
56545699
}
5655-
htlcs.push(claimable_htlc);
5656-
let amount_msat = htlcs.iter().map(|htlc| htlc.value).sum();
5657-
htlcs.iter_mut().for_each(|htlc| htlc.total_value_received = Some(amount_msat));
5658-
let counterparty_skimmed_fee_msat = htlcs.iter()
5700+
claimable_payment.htlcs.push(claimable_htlc);
5701+
let amount_msat =
5702+
claimable_payment.htlcs.iter().map(|htlc| htlc.value).sum();
5703+
claimable_payment.htlcs.iter_mut()
5704+
.for_each(|htlc| htlc.total_value_received = Some(amount_msat));
5705+
let counterparty_skimmed_fee_msat = claimable_payment.htlcs.iter()
56595706
.map(|htlc| htlc.counterparty_skimmed_fee_msat.unwrap_or(0)).sum();
56605707
debug_assert!(total_value.saturating_sub(amount_msat) <=
56615708
counterparty_skimmed_fee_msat);
5709+
claimable_payment.htlcs.sort();
5710+
let payment_id =
5711+
claimable_payment.inbound_payment_id(&self.inbound_payment_id_secret);
56625712
new_events.push_back((events::Event::PaymentClaimable {
56635713
receiver_node_id: Some(receiver_node_id),
56645714
payment_hash,
@@ -5669,13 +5719,14 @@ where
56695719
via_user_channel_id: Some(prev_user_channel_id),
56705720
claim_deadline: Some(earliest_expiry - HTLC_FAIL_BACK_BUFFER),
56715721
onion_fields: claimable_payment.onion_fields.clone(),
5722+
payment_id: Some(payment_id),
56725723
}, None));
56735724
payment_claimable_generated = true;
56745725
} else {
56755726
// Nothing to do - we haven't reached the total
56765727
// payment value yet, wait until we receive more
56775728
// MPP parts.
5678-
htlcs.push(claimable_htlc);
5729+
claimable_payment.htlcs.push(claimable_htlc);
56795730
#[allow(unused_assignments)] {
56805731
committed_to_claimable = true;
56815732
}
@@ -6472,6 +6523,7 @@ where
64726523
}
64736524
}
64746525

6526+
let payment_id = payment.inbound_payment_id(&self.inbound_payment_id_secret);
64756527
let claiming_payment = claimable_payments.pending_claiming_payments
64766528
.entry(payment_hash)
64776529
.and_modify(|_| {
@@ -6489,6 +6541,7 @@ where
64896541
htlcs,
64906542
sender_intended_value,
64916543
onion_fields: payment.onion_fields,
6544+
payment_id: Some(payment_id),
64926545
}
64936546
});
64946547

@@ -7006,6 +7059,7 @@ where
70067059
htlcs,
70077060
sender_intended_value: sender_intended_total_msat,
70087061
onion_fields,
7062+
payment_id,
70097063
}) = payment {
70107064
self.pending_events.lock().unwrap().push_back((events::Event::PaymentClaimed {
70117065
payment_hash,
@@ -7015,6 +7069,7 @@ where
70157069
htlcs,
70167070
sender_intended_total_msat,
70177071
onion_fields,
7072+
payment_id,
70187073
}, None));
70197074
}
70207075
},
@@ -12740,6 +12795,7 @@ where
1274012795
previous_hop_monitor.provide_payment_preimage(&payment_hash, &payment_preimage, &args.tx_broadcaster, &bounded_fee_estimator, &args.logger);
1274112796
}
1274212797
}
12798+
let payment_id = payment.inbound_payment_id(&inbound_payment_id_secret.unwrap());
1274312799
pending_events_read.push_back((events::Event::PaymentClaimed {
1274412800
receiver_node_id,
1274512801
payment_hash,
@@ -12748,6 +12804,7 @@ where
1274812804
htlcs: payment.htlcs.iter().map(events::ClaimedHTLC::from).collect(),
1274912805
sender_intended_total_msat: payment.htlcs.first().map(|htlc| htlc.total_msat),
1275012806
onion_fields: payment.onion_fields,
12807+
payment_id: Some(payment_id),
1275112808
}, None));
1275212809
}
1275312810
}

lightning/src/ln/msgs.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -1729,8 +1729,7 @@ pub trait OnionMessageHandler {
17291729
fn provided_init_features(&self, their_node_id: PublicKey) -> InitFeatures;
17301730
}
17311731

1732-
#[derive(Clone)]
1733-
#[cfg_attr(test, derive(Debug, PartialEq))]
1732+
#[derive(Clone, Debug, PartialEq, Eq)]
17341733
/// Information communicated in the onion to the recipient for multi-part tracking and proof that
17351734
/// the payment is associated with an invoice.
17361735
pub struct FinalOnionHopData {

0 commit comments

Comments
 (0)