Skip to content

Commit 56a87cc

Browse files
authored
Merge pull request #2970 from jkczyz/2024-03-offer-id
Include a `PaymentContext` in `PaymentPurpose`
2 parents ac9a2c8 + 478911d commit 56a87cc

16 files changed

+781
-209
lines changed

lightning/src/blinded_path/payment.rs

Lines changed: 120 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ use crate::ln::channelmanager::CounterpartyForwardingInfo;
1212
use crate::ln::features::BlindedHopFeatures;
1313
use crate::ln::msgs::DecodeError;
1414
use crate::offers::invoice::BlindedPayInfo;
15+
use crate::offers::invoice_request::InvoiceRequestFields;
16+
use crate::offers::offer::OfferId;
1517
use crate::util::ser::{HighZeroBytesDroppedBigSize, Readable, Writeable, Writer};
1618

1719
#[allow(unused_imports)]
@@ -53,6 +55,8 @@ pub struct ReceiveTlvs {
5355
pub payment_secret: PaymentSecret,
5456
/// Constraints for the receiver of this payment.
5557
pub payment_constraints: PaymentConstraints,
58+
/// Context for the receiver of this payment.
59+
pub payment_context: PaymentContext,
5660
}
5761

5862
/// Data to construct a [`BlindedHop`] for sending a payment over.
@@ -97,6 +101,66 @@ pub struct PaymentConstraints {
97101
pub htlc_minimum_msat: u64,
98102
}
99103

104+
/// The context of an inbound payment, which is included in a [`BlindedPath`] via [`ReceiveTlvs`]
105+
/// and surfaced in [`PaymentPurpose`].
106+
///
107+
/// [`BlindedPath`]: crate::blinded_path::BlindedPath
108+
/// [`PaymentPurpose`]: crate::events::PaymentPurpose
109+
#[derive(Clone, Debug, Eq, PartialEq)]
110+
pub enum PaymentContext {
111+
/// The payment context was unknown.
112+
Unknown(UnknownPaymentContext),
113+
114+
/// The payment was made for an invoice requested from a BOLT 12 [`Offer`].
115+
///
116+
/// [`Offer`]: crate::offers::offer::Offer
117+
Bolt12Offer(Bolt12OfferContext),
118+
119+
/// The payment was made for an invoice sent for a BOLT 12 [`Refund`].
120+
///
121+
/// [`Refund`]: crate::offers::refund::Refund
122+
Bolt12Refund(Bolt12RefundContext),
123+
}
124+
125+
// Used when writing PaymentContext in Event::PaymentClaimable to avoid cloning.
126+
pub(crate) enum PaymentContextRef<'a> {
127+
Bolt12Offer(&'a Bolt12OfferContext),
128+
Bolt12Refund(&'a Bolt12RefundContext),
129+
}
130+
131+
/// An unknown payment context.
132+
#[derive(Clone, Debug, Eq, PartialEq)]
133+
pub struct UnknownPaymentContext(());
134+
135+
/// The context of a payment made for an invoice requested from a BOLT 12 [`Offer`].
136+
///
137+
/// [`Offer`]: crate::offers::offer::Offer
138+
#[derive(Clone, Debug, Eq, PartialEq)]
139+
pub struct Bolt12OfferContext {
140+
/// The identifier of the [`Offer`].
141+
///
142+
/// [`Offer`]: crate::offers::offer::Offer
143+
pub offer_id: OfferId,
144+
145+
/// Fields from an [`InvoiceRequest`] sent for a [`Bolt12Invoice`].
146+
///
147+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
148+
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
149+
pub invoice_request: InvoiceRequestFields,
150+
}
151+
152+
/// The context of a payment made for an invoice sent for a BOLT 12 [`Refund`].
153+
///
154+
/// [`Refund`]: crate::offers::refund::Refund
155+
#[derive(Clone, Debug, Eq, PartialEq)]
156+
pub struct Bolt12RefundContext {}
157+
158+
impl PaymentContext {
159+
pub(crate) fn unknown() -> Self {
160+
PaymentContext::Unknown(UnknownPaymentContext(()))
161+
}
162+
}
163+
100164
impl TryFrom<CounterpartyForwardingInfo> for PaymentRelay {
101165
type Error = ();
102166

@@ -137,7 +201,8 @@ impl Writeable for ReceiveTlvs {
137201
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
138202
encode_tlv_stream!(w, {
139203
(12, self.payment_constraints, required),
140-
(65536, self.payment_secret, required)
204+
(65536, self.payment_secret, required),
205+
(65537, self.payment_context, required)
141206
});
142207
Ok(())
143208
}
@@ -163,11 +228,14 @@ impl Readable for BlindedPaymentTlvs {
163228
(12, payment_constraints, required),
164229
(14, features, option),
165230
(65536, payment_secret, option),
231+
(65537, payment_context, (default_value, PaymentContext::unknown())),
166232
});
167233
let _padding: Option<utils::Padding> = _padding;
168234

169235
if let Some(short_channel_id) = scid {
170-
if payment_secret.is_some() { return Err(DecodeError::InvalidValue) }
236+
if payment_secret.is_some() {
237+
return Err(DecodeError::InvalidValue)
238+
}
171239
Ok(BlindedPaymentTlvs::Forward(ForwardTlvs {
172240
short_channel_id,
173241
payment_relay: payment_relay.ok_or(DecodeError::InvalidValue)?,
@@ -179,6 +247,7 @@ impl Readable for BlindedPaymentTlvs {
179247
Ok(BlindedPaymentTlvs::Receive(ReceiveTlvs {
180248
payment_secret: payment_secret.ok_or(DecodeError::InvalidValue)?,
181249
payment_constraints: payment_constraints.0.unwrap(),
250+
payment_context: payment_context.0.unwrap(),
182251
}))
183252
}
184253
}
@@ -309,10 +378,53 @@ impl Readable for PaymentConstraints {
309378
}
310379
}
311380

381+
impl_writeable_tlv_based_enum!(PaymentContext,
382+
;
383+
(0, Unknown),
384+
(1, Bolt12Offer),
385+
(2, Bolt12Refund),
386+
);
387+
388+
impl<'a> Writeable for PaymentContextRef<'a> {
389+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
390+
match self {
391+
PaymentContextRef::Bolt12Offer(context) => {
392+
1u8.write(w)?;
393+
context.write(w)?;
394+
},
395+
PaymentContextRef::Bolt12Refund(context) => {
396+
2u8.write(w)?;
397+
context.write(w)?;
398+
},
399+
}
400+
401+
Ok(())
402+
}
403+
}
404+
405+
impl Writeable for UnknownPaymentContext {
406+
fn write<W: Writer>(&self, _w: &mut W) -> Result<(), io::Error> {
407+
Ok(())
408+
}
409+
}
410+
411+
impl Readable for UnknownPaymentContext {
412+
fn read<R: io::Read>(_r: &mut R) -> Result<Self, DecodeError> {
413+
Ok(UnknownPaymentContext(()))
414+
}
415+
}
416+
417+
impl_writeable_tlv_based!(Bolt12OfferContext, {
418+
(0, offer_id, required),
419+
(2, invoice_request, required),
420+
});
421+
422+
impl_writeable_tlv_based!(Bolt12RefundContext, {});
423+
312424
#[cfg(test)]
313425
mod tests {
314426
use bitcoin::secp256k1::PublicKey;
315-
use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, ReceiveTlvs, PaymentConstraints, PaymentRelay};
427+
use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, ReceiveTlvs, PaymentConstraints, PaymentContext, PaymentRelay};
316428
use crate::ln::PaymentSecret;
317429
use crate::ln::features::BlindedHopFeatures;
318430
use crate::ln::functional_test_utils::TEST_FINAL_CLTV;
@@ -361,6 +473,7 @@ mod tests {
361473
max_cltv_expiry: 0,
362474
htlc_minimum_msat: 1,
363475
},
476+
payment_context: PaymentContext::unknown(),
364477
};
365478
let htlc_maximum_msat = 100_000;
366479
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat, 12).unwrap();
@@ -379,6 +492,7 @@ mod tests {
379492
max_cltv_expiry: 0,
380493
htlc_minimum_msat: 1,
381494
},
495+
payment_context: PaymentContext::unknown(),
382496
};
383497
let blinded_payinfo = super::compute_payinfo(&[], &recv_tlvs, 4242, TEST_FINAL_CLTV as u16).unwrap();
384498
assert_eq!(blinded_payinfo.fee_base_msat, 0);
@@ -432,6 +546,7 @@ mod tests {
432546
max_cltv_expiry: 0,
433547
htlc_minimum_msat: 3,
434548
},
549+
payment_context: PaymentContext::unknown(),
435550
};
436551
let htlc_maximum_msat = 100_000;
437552
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_maximum_msat, TEST_FINAL_CLTV as u16).unwrap();
@@ -482,6 +597,7 @@ mod tests {
482597
max_cltv_expiry: 0,
483598
htlc_minimum_msat: 1,
484599
},
600+
payment_context: PaymentContext::unknown(),
485601
};
486602
let htlc_minimum_msat = 3798;
487603
assert!(super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, htlc_minimum_msat - 1, TEST_FINAL_CLTV as u16).is_err());
@@ -536,6 +652,7 @@ mod tests {
536652
max_cltv_expiry: 0,
537653
htlc_minimum_msat: 1,
538654
},
655+
payment_context: PaymentContext::unknown(),
539656
};
540657

541658
let blinded_payinfo = super::compute_payinfo(&intermediate_nodes[..], &recv_tlvs, 10_000, TEST_FINAL_CLTV as u16).unwrap();

0 commit comments

Comments
 (0)