Skip to content

Commit aaf529d

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 aa09c33 commit aaf529d

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
@@ -749,6 +749,14 @@ pub enum Event {
749749
///
750750
/// [`ChannelManager::claim_funds`]: crate::ln::channelmanager::ChannelManager::claim_funds
751751
claim_deadline: Option<u32>,
752+
/// A unique ID describing this payment (derived from the list of HTLCs in the payment).
753+
///
754+
/// Payers may pay for the same [`PaymentHash`] multiple times (though this is unsafe and
755+
/// an intermediary node may steal the funds). Thus, in order to accurately track when
756+
/// payments are received and claimed, you should use this identifier.
757+
///
758+
/// Only filled in for payments received on LDK versions 0.1 and higher.
759+
payment_id: Option<PaymentId>,
752760
},
753761
/// Indicates a payment has been claimed and we've received money!
754762
///
@@ -796,6 +804,14 @@ pub enum Event {
796804
///
797805
/// Payments received on LDK versions prior to 0.0.124 will have this field unset.
798806
onion_fields: Option<RecipientOnionFields>,
807+
/// A unique ID describing this payment (derived from the list of HTLCs in the payment).
808+
///
809+
/// Payers may pay for the same [`PaymentHash`] multiple times (though this is unsafe and
810+
/// an intermediary node may steal the funds). Thus, in order to accurately track when
811+
/// payments are received and claimed, you should use this identifier.
812+
///
813+
/// Only filled in for payments received on LDK versions 0.1 and higher.
814+
payment_id: Option<PaymentId>,
799815
},
800816
/// Indicates that a peer connection with a node is needed in order to send an [`OnionMessage`].
801817
///
@@ -1432,7 +1448,7 @@ impl Writeable for Event {
14321448
},
14331449
&Event::PaymentClaimable { ref payment_hash, ref amount_msat, counterparty_skimmed_fee_msat,
14341450
ref purpose, ref receiver_node_id, ref via_channel_id, ref via_user_channel_id,
1435-
ref claim_deadline, ref onion_fields
1451+
ref claim_deadline, ref onion_fields, ref payment_id,
14361452
} => {
14371453
1u8.write(writer)?;
14381454
let mut payment_secret = None;
@@ -1478,6 +1494,7 @@ impl Writeable for Event {
14781494
(9, onion_fields, option),
14791495
(10, skimmed_fee_opt, option),
14801496
(11, payment_context, option),
1497+
(13, payment_id, option),
14811498
});
14821499
},
14831500
&Event::PaymentSent { ref payment_id, ref payment_preimage, ref payment_hash, ref fee_paid_msat } => {
@@ -1634,7 +1651,10 @@ impl Writeable for Event {
16341651
// We never write the OpenChannelRequest events as, upon disconnection, peers
16351652
// drop any channels which have not yet exchanged funding_signed.
16361653
},
1637-
&Event::PaymentClaimed { ref payment_hash, ref amount_msat, ref purpose, ref receiver_node_id, ref htlcs, ref sender_intended_total_msat, ref onion_fields } => {
1654+
&Event::PaymentClaimed { ref payment_hash, ref amount_msat, ref purpose,
1655+
ref receiver_node_id, ref htlcs, ref sender_intended_total_msat, ref onion_fields,
1656+
ref payment_id,
1657+
} => {
16381658
19u8.write(writer)?;
16391659
write_tlv_fields!(writer, {
16401660
(0, payment_hash, required),
@@ -1644,6 +1664,7 @@ impl Writeable for Event {
16441664
(5, *htlcs, optional_vec),
16451665
(7, sender_intended_total_msat, option),
16461666
(9, onion_fields, option),
1667+
(11, payment_id, option),
16471668
});
16481669
},
16491670
&Event::ProbeSuccessful { ref payment_id, ref payment_hash, ref path } => {
@@ -1767,6 +1788,7 @@ impl MaybeReadable for Event {
17671788
let mut via_user_channel_id = None;
17681789
let mut onion_fields = None;
17691790
let mut payment_context = None;
1791+
let mut payment_id = None;
17701792
read_tlv_fields!(reader, {
17711793
(0, payment_hash, required),
17721794
(1, receiver_node_id, option),
@@ -1780,6 +1802,7 @@ impl MaybeReadable for Event {
17801802
(9, onion_fields, option),
17811803
(10, counterparty_skimmed_fee_msat_opt, option),
17821804
(11, payment_context, option),
1805+
(13, payment_id, option),
17831806
});
17841807
let purpose = match payment_secret {
17851808
Some(secret) => PaymentPurpose::from_parts(payment_preimage, secret, payment_context),
@@ -1796,6 +1819,7 @@ impl MaybeReadable for Event {
17961819
via_user_channel_id,
17971820
claim_deadline,
17981821
onion_fields,
1822+
payment_id,
17991823
}))
18001824
};
18011825
f()
@@ -2037,6 +2061,7 @@ impl MaybeReadable for Event {
20372061
let mut htlcs: Option<Vec<ClaimedHTLC>> = Some(vec![]);
20382062
let mut sender_intended_total_msat: Option<u64> = None;
20392063
let mut onion_fields = None;
2064+
let mut payment_id = None;
20402065
read_tlv_fields!(reader, {
20412066
(0, payment_hash, required),
20422067
(1, receiver_node_id, option),
@@ -2045,6 +2070,7 @@ impl MaybeReadable for Event {
20452070
(5, htlcs, optional_vec),
20462071
(7, sender_intended_total_msat, option),
20472072
(9, onion_fields, option),
2073+
(11, payment_id, option),
20482074
});
20492075
Ok(Some(Event::PaymentClaimed {
20502076
receiver_node_id,
@@ -2054,6 +2080,7 @@ impl MaybeReadable for Event {
20542080
htlcs: htlcs.unwrap_or_default(),
20552081
sender_intended_total_msat,
20562082
onion_fields,
2083+
payment_id,
20572084
}))
20582085
};
20592086
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};
@@ -368,6 +368,7 @@ pub(crate) struct HTLCPreviousHopData {
368368
counterparty_node_id: Option<PublicKey>,
369369
}
370370

371+
#[derive(PartialEq, Eq)]
371372
enum OnionPayload {
372373
/// Indicates this incoming onion payload is for the purpose of paying an invoice.
373374
Invoice {
@@ -380,6 +381,7 @@ enum OnionPayload {
380381
}
381382

382383
/// HTLCs that are to us and can be failed/claimed by the user
384+
#[derive(PartialEq, Eq)]
383385
struct ClaimableHTLC {
384386
prev_hop: HTLCPreviousHopData,
385387
cltv_expiry: u32,
@@ -411,6 +413,23 @@ impl From<&ClaimableHTLC> for events::ClaimedHTLC {
411413
}
412414
}
413415

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

513+
impl PaymentId {
514+
fn for_inbound_from_htlcs<I: Iterator<Item=(ChannelId, u64)>>(key: &[u8; 32], htlcs: I) -> PaymentId {
515+
let mut prev_pair = None;
516+
let mut hasher = HmacEngine::new(key);
517+
for (channel_id, htlc_id) in htlcs {
518+
hasher.input(&channel_id.0);
519+
hasher.input(&htlc_id.to_le_bytes());
520+
if let Some(prev) = prev_pair {
521+
debug_assert!(prev < (channel_id, htlc_id), "HTLCs should be sorted");
522+
}
523+
prev_pair = Some((channel_id, htlc_id));
524+
}
525+
PaymentId(Hmac::<Sha256>::from_engine(hasher).to_byte_array())
526+
}
527+
}
528+
494529
impl Writeable for PaymentId {
495530
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
496531
self.0.write(w)
@@ -764,6 +799,7 @@ struct ClaimingPayment {
764799
htlcs: Vec<events::ClaimedHTLC>,
765800
sender_intended_value: Option<u64>,
766801
onion_fields: Option<RecipientOnionFields>,
802+
payment_id: Option<PaymentId>,
767803
}
768804
impl_writeable_tlv_based!(ClaimingPayment, {
769805
(0, amount_msat, required),
@@ -772,6 +808,7 @@ impl_writeable_tlv_based!(ClaimingPayment, {
772808
(5, htlcs, optional_vec),
773809
(7, sender_intended_value, option),
774810
(9, onion_fields, option),
811+
(11, payment_id, option),
775812
});
776813

777814
struct ClaimablePayment {
@@ -780,6 +817,15 @@ struct ClaimablePayment {
780817
htlcs: Vec<ClaimableHTLC>,
781818
}
782819

820+
impl ClaimablePayment {
821+
fn inbound_payment_id(&self, secret: &[u8; 32]) -> PaymentId {
822+
PaymentId::for_inbound_from_htlcs(
823+
secret,
824+
self.htlcs.iter().map(|htlc| (htlc.prev_hop.channel_id, htlc.prev_hop.htlc_id))
825+
)
826+
}
827+
}
828+
783829
/// Represent the channel funding transaction type.
784830
enum FundingType {
785831
/// This variant is useful when we want LDK to validate the funding transaction and
@@ -5749,10 +5795,9 @@ where
57495795
} else {
57505796
claimable_payment.onion_fields = Some(onion_fields);
57515797
}
5752-
let ref mut htlcs = &mut claimable_payment.htlcs;
57535798
let mut total_value = claimable_htlc.sender_intended_value;
57545799
let mut earliest_expiry = claimable_htlc.cltv_expiry;
5755-
for htlc in htlcs.iter() {
5800+
for htlc in claimable_payment.htlcs.iter() {
57565801
total_value += htlc.sender_intended_value;
57575802
earliest_expiry = cmp::min(earliest_expiry, htlc.cltv_expiry);
57585803
if htlc.total_msat != claimable_htlc.total_msat {
@@ -5774,13 +5819,18 @@ where
57745819
#[allow(unused_assignments)] {
57755820
committed_to_claimable = true;
57765821
}
5777-
htlcs.push(claimable_htlc);
5778-
let amount_msat = htlcs.iter().map(|htlc| htlc.value).sum();
5779-
htlcs.iter_mut().for_each(|htlc| htlc.total_value_received = Some(amount_msat));
5780-
let counterparty_skimmed_fee_msat = htlcs.iter()
5822+
claimable_payment.htlcs.push(claimable_htlc);
5823+
let amount_msat =
5824+
claimable_payment.htlcs.iter().map(|htlc| htlc.value).sum();
5825+
claimable_payment.htlcs.iter_mut()
5826+
.for_each(|htlc| htlc.total_value_received = Some(amount_msat));
5827+
let counterparty_skimmed_fee_msat = claimable_payment.htlcs.iter()
57815828
.map(|htlc| htlc.counterparty_skimmed_fee_msat.unwrap_or(0)).sum();
57825829
debug_assert!(total_value.saturating_sub(amount_msat) <=
57835830
counterparty_skimmed_fee_msat);
5831+
claimable_payment.htlcs.sort();
5832+
let payment_id =
5833+
claimable_payment.inbound_payment_id(&self.inbound_payment_id_secret);
57845834
new_events.push_back((events::Event::PaymentClaimable {
57855835
receiver_node_id: Some(receiver_node_id),
57865836
payment_hash,
@@ -5791,13 +5841,14 @@ where
57915841
via_user_channel_id: Some(prev_user_channel_id),
57925842
claim_deadline: Some(earliest_expiry - HTLC_FAIL_BACK_BUFFER),
57935843
onion_fields: claimable_payment.onion_fields.clone(),
5844+
payment_id: Some(payment_id),
57945845
}, None));
57955846
payment_claimable_generated = true;
57965847
} else {
57975848
// Nothing to do - we haven't reached the total
57985849
// payment value yet, wait until we receive more
57995850
// MPP parts.
5800-
htlcs.push(claimable_htlc);
5851+
claimable_payment.htlcs.push(claimable_htlc);
58015852
#[allow(unused_assignments)] {
58025853
committed_to_claimable = true;
58035854
}
@@ -6594,6 +6645,7 @@ where
65946645
}
65956646
}
65966647

6648+
let payment_id = payment.inbound_payment_id(&self.inbound_payment_id_secret);
65976649
let claiming_payment = claimable_payments.pending_claiming_payments
65986650
.entry(payment_hash)
65996651
.and_modify(|_| {
@@ -6611,6 +6663,7 @@ where
66116663
htlcs,
66126664
sender_intended_value,
66136665
onion_fields: payment.onion_fields,
6666+
payment_id: Some(payment_id),
66146667
}
66156668
});
66166669

@@ -7128,6 +7181,7 @@ where
71287181
htlcs,
71297182
sender_intended_value: sender_intended_total_msat,
71307183
onion_fields,
7184+
payment_id,
71317185
}) = payment {
71327186
self.pending_events.lock().unwrap().push_back((events::Event::PaymentClaimed {
71337187
payment_hash,
@@ -7137,6 +7191,7 @@ where
71377191
htlcs,
71387192
sender_intended_total_msat,
71397193
onion_fields,
7194+
payment_id,
71407195
}, None));
71417196
}
71427197
},
@@ -12863,6 +12918,7 @@ where
1286312918
previous_hop_monitor.provide_payment_preimage(&payment_hash, &payment_preimage, &args.tx_broadcaster, &bounded_fee_estimator, &args.logger);
1286412919
}
1286512920
}
12921+
let payment_id = payment.inbound_payment_id(&inbound_payment_id_secret.unwrap());
1286612922
pending_events_read.push_back((events::Event::PaymentClaimed {
1286712923
receiver_node_id,
1286812924
payment_hash,
@@ -12871,6 +12927,7 @@ where
1287112927
htlcs: payment.htlcs.iter().map(events::ClaimedHTLC::from).collect(),
1287212928
sender_intended_total_msat: payment.htlcs.first().map(|htlc| htlc.total_msat),
1287312929
onion_fields: payment.onion_fields,
12930+
payment_id: Some(payment_id),
1287412931
}, None));
1287512932
}
1287612933
}

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)