Skip to content

Commit 7b33c21

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 996756d commit 7b33c21

File tree

3 files changed

+93
-12
lines changed

3 files changed

+93
-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, random, ID describing this 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, random, ID describing this 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

+63-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 user-provided identifier in [`ChannelManager::send_payment`] used to uniquely identify
413432
/// a payment and ensure idempotency in LDK.
414433
///
@@ -435,6 +454,20 @@ impl PaymentId {
435454
) -> Result<(), ()> {
436455
signer::verify_payment_id(*self, hmac, nonce, expanded_key)
437456
}
457+
458+
fn for_inbound_from_htlcs<I: Iterator<Item=(ChannelId, u64)>>(key: &[u8; 32], htlcs: I) -> PaymentId {
459+
let mut prev_pair = None;
460+
let mut hasher = HmacEngine::new(key);
461+
for (channel_id, htlc_id) in htlcs {
462+
hasher.input(&channel_id.0);
463+
hasher.input(&htlc_id.to_le_bytes());
464+
if let Some(prev) = prev_pair {
465+
debug_assert!(prev < (channel_id, htlc_id), "HTLCs should be sorted");
466+
}
467+
prev_pair = Some((channel_id, htlc_id));
468+
}
469+
PaymentId(Hmac::<Sha256>::from_engine(hasher).to_byte_array())
470+
}
438471
}
439472

440473
impl Writeable for PaymentId {
@@ -710,6 +743,7 @@ struct ClaimingPayment {
710743
htlcs: Vec<events::ClaimedHTLC>,
711744
sender_intended_value: Option<u64>,
712745
onion_fields: Option<RecipientOnionFields>,
746+
payment_id: Option<PaymentId>,
713747
}
714748
impl_writeable_tlv_based!(ClaimingPayment, {
715749
(0, amount_msat, required),
@@ -718,6 +752,7 @@ impl_writeable_tlv_based!(ClaimingPayment, {
718752
(5, htlcs, optional_vec),
719753
(7, sender_intended_value, option),
720754
(9, onion_fields, option),
755+
(11, payment_id, option),
721756
});
722757

723758
struct ClaimablePayment {
@@ -726,6 +761,15 @@ struct ClaimablePayment {
726761
htlcs: Vec<ClaimableHTLC>,
727762
}
728763

764+
impl ClaimablePayment {
765+
fn inbound_payment_id(&self, secret: &[u8; 32]) -> PaymentId {
766+
PaymentId::for_inbound_from_htlcs(
767+
secret,
768+
self.htlcs.iter().map(|htlc| (htlc.prev_hop.channel_id, htlc.prev_hop.htlc_id))
769+
)
770+
}
771+
}
772+
729773
/// Represent the channel funding transaction type.
730774
enum FundingType {
731775
/// This variant is useful when we want LDK to validate the funding transaction and
@@ -5593,10 +5637,9 @@ where
55935637
} else {
55945638
claimable_payment.onion_fields = Some(onion_fields);
55955639
}
5596-
let ref mut htlcs = &mut claimable_payment.htlcs;
55975640
let mut total_value = claimable_htlc.sender_intended_value;
55985641
let mut earliest_expiry = claimable_htlc.cltv_expiry;
5599-
for htlc in htlcs.iter() {
5642+
for htlc in claimable_payment.htlcs.iter() {
56005643
total_value += htlc.sender_intended_value;
56015644
earliest_expiry = cmp::min(earliest_expiry, htlc.cltv_expiry);
56025645
if htlc.total_msat != claimable_htlc.total_msat {
@@ -5618,13 +5661,18 @@ where
56185661
#[allow(unused_assignments)] {
56195662
committed_to_claimable = true;
56205663
}
5621-
htlcs.push(claimable_htlc);
5622-
let amount_msat = htlcs.iter().map(|htlc| htlc.value).sum();
5623-
htlcs.iter_mut().for_each(|htlc| htlc.total_value_received = Some(amount_msat));
5624-
let counterparty_skimmed_fee_msat = htlcs.iter()
5664+
claimable_payment.htlcs.push(claimable_htlc);
5665+
let amount_msat =
5666+
claimable_payment.htlcs.iter().map(|htlc| htlc.value).sum();
5667+
claimable_payment.htlcs.iter_mut()
5668+
.for_each(|htlc| htlc.total_value_received = Some(amount_msat));
5669+
let counterparty_skimmed_fee_msat = claimable_payment.htlcs.iter()
56255670
.map(|htlc| htlc.counterparty_skimmed_fee_msat.unwrap_or(0)).sum();
56265671
debug_assert!(total_value.saturating_sub(amount_msat) <=
56275672
counterparty_skimmed_fee_msat);
5673+
claimable_payment.htlcs.sort();
5674+
let payment_id =
5675+
claimable_payment.inbound_payment_id(&self.inbound_payment_id_secret);
56285676
new_events.push_back((events::Event::PaymentClaimable {
56295677
receiver_node_id: Some(receiver_node_id),
56305678
payment_hash,
@@ -5635,13 +5683,14 @@ where
56355683
via_user_channel_id: Some(prev_user_channel_id),
56365684
claim_deadline: Some(earliest_expiry - HTLC_FAIL_BACK_BUFFER),
56375685
onion_fields: claimable_payment.onion_fields.clone(),
5686+
payment_id: Some(payment_id),
56385687
}, None));
56395688
payment_claimable_generated = true;
56405689
} else {
56415690
// Nothing to do - we haven't reached the total
56425691
// payment value yet, wait until we receive more
56435692
// MPP parts.
5644-
htlcs.push(claimable_htlc);
5693+
claimable_payment.htlcs.push(claimable_htlc);
56455694
#[allow(unused_assignments)] {
56465695
committed_to_claimable = true;
56475696
}
@@ -6438,6 +6487,7 @@ where
64386487
}
64396488
}
64406489

6490+
let payment_id = payment.inbound_payment_id(&self.inbound_payment_id_secret);
64416491
let claiming_payment = claimable_payments.pending_claiming_payments
64426492
.entry(payment_hash)
64436493
.and_modify(|_| {
@@ -6455,6 +6505,7 @@ where
64556505
htlcs,
64566506
sender_intended_value,
64576507
onion_fields: payment.onion_fields,
6508+
payment_id: Some(payment_id),
64586509
}
64596510
});
64606511

@@ -6972,6 +7023,7 @@ where
69727023
htlcs,
69737024
sender_intended_value: sender_intended_total_msat,
69747025
onion_fields,
7026+
payment_id,
69757027
}) = payment {
69767028
self.pending_events.lock().unwrap().push_back((events::Event::PaymentClaimed {
69777029
payment_hash,
@@ -6981,6 +7033,7 @@ where
69817033
htlcs,
69827034
sender_intended_total_msat,
69837035
onion_fields,
7036+
payment_id,
69847037
}, None));
69857038
}
69867039
},
@@ -12646,6 +12699,7 @@ where
1264612699
previous_hop_monitor.provide_payment_preimage(&payment_hash, &payment_preimage, &args.tx_broadcaster, &bounded_fee_estimator, &args.logger);
1264712700
}
1264812701
}
12702+
let payment_id = payment.inbound_payment_id(&inbound_payment_id_secret.unwrap());
1264912703
pending_events_read.push_back((events::Event::PaymentClaimed {
1265012704
receiver_node_id,
1265112705
payment_hash,
@@ -12654,6 +12708,7 @@ where
1265412708
htlcs: payment.htlcs.iter().map(events::ClaimedHTLC::from).collect(),
1265512709
sender_intended_total_msat: payment.htlcs.first().map(|htlc| htlc.total_msat),
1265612710
onion_fields: payment.onion_fields,
12711+
payment_id: Some(payment_id),
1265712712
}, None));
1265812713
}
1265912714
}

lightning/src/ln/msgs.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -1715,8 +1715,7 @@ pub trait OnionMessageHandler {
17151715
fn provided_init_features(&self, their_node_id: &PublicKey) -> InitFeatures;
17161716
}
17171717

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

0 commit comments

Comments
 (0)