Skip to content

Commit ab61bcf

Browse files
committed
Support LSPS2 metadata in BOLT12 payment context
Encode LSPS2 parameters in a shared payment metadata type so both BOLT11 invoices and BOLT12 payment contexts can carry the fee limits needed when handling intercepted payments. Co-Authored-By: HAL 9000
1 parent 12f6e13 commit ab61bcf

4 files changed

Lines changed: 175 additions & 54 deletions

File tree

src/event.rs

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
use core::future::Future;
99
use core::task::{Poll, Waker};
10-
use std::collections::VecDeque;
10+
use std::collections::{BTreeMap, VecDeque};
1111
use std::ops::Deref;
1212
use std::sync::{Arc, Mutex};
1313

@@ -50,7 +50,7 @@ use crate::payment::asynchronous::static_invoice_store::StaticInvoiceStore;
5050
use crate::payment::store::{
5151
PaymentDetails, PaymentDetailsUpdate, PaymentDirection, PaymentKind, PaymentStatus,
5252
};
53-
use crate::payment::PaymentMetadata;
53+
use crate::payment::{PaymentMetadata, LDK_NODE_BOLT12_PAYMENT_METADATA_KEY};
5454
use crate::runtime::Runtime;
5555
use crate::types::{
5656
CustomTlvRecord, DynStore, KeysManager, OnionMessenger, PaymentStore, Sweeper, Wallet,
@@ -600,8 +600,9 @@ where
600600
}
601601
}
602602

603-
fn lsps2_max_total_opening_fee_msat(payment_metadata: &[u8], amount_msat: u64) -> Option<u64> {
604-
let metadata = PaymentMetadata::read(&mut &payment_metadata[..]).ok()?;
603+
fn lsps2_max_total_opening_fee_msat_from_metadata(
604+
metadata: PaymentMetadata, amount_msat: u64,
605+
) -> Option<u64> {
605606
let lsps2_parameters = metadata.lsps2_parameters?;
606607
lsps2_parameters.max_total_opening_fee_msat.or_else(|| {
607608
lsps2_parameters.max_proportional_opening_fee_ppm_msat.and_then(|max_prop_fee| {
@@ -611,6 +612,19 @@ where
611612
})
612613
}
613614

615+
fn lsps2_max_total_opening_fee_msat(payment_metadata: &[u8], amount_msat: u64) -> Option<u64> {
616+
let metadata = PaymentMetadata::read(&mut &payment_metadata[..]).ok()?;
617+
Self::lsps2_max_total_opening_fee_msat_from_metadata(metadata, amount_msat)
618+
}
619+
620+
fn lsps2_max_total_opening_fee_msat_from_bolt12_metadata(
621+
payment_metadata: Option<&BTreeMap<u64, Vec<u8>>>, amount_msat: u64,
622+
) -> Option<u64> {
623+
let encoded_metadata = payment_metadata?.get(&LDK_NODE_BOLT12_PAYMENT_METADATA_KEY)?;
624+
let metadata = PaymentMetadata::read(&mut &encoded_metadata[..]).ok()?;
625+
Self::lsps2_max_total_opening_fee_msat_from_metadata(metadata, amount_msat)
626+
}
627+
614628
pub async fn handle_event(&self, event: LdkEvent) -> Result<(), ReplayEvent> {
615629
match event {
616630
LdkEvent::FundingGenerationReady {
@@ -799,13 +813,19 @@ where
799813
.and_then(|metadata| {
800814
Self::lsps2_max_total_opening_fee_msat(metadata, amount_msat)
801815
}),
816+
PaymentPurpose::Bolt12OfferPayment { payment_context, .. } => {
817+
Self::lsps2_max_total_opening_fee_msat_from_bolt12_metadata(
818+
payment_context.payment_metadata.as_ref(),
819+
amount_msat,
820+
)
821+
},
802822
_ => None,
803823
};
804824

805825
let Some(max_total_opening_fee_msat) = max_total_opening_fee_msat else {
806826
log_info!(
807827
self.logger,
808-
"Refusing inbound payment with hash {} as the counterparty withheld {}msat without valid BOLT11 LSPS2 payment metadata",
828+
"Refusing inbound payment with hash {} as the counterparty withheld {}msat without valid LSPS2 payment metadata",
809829
hex_utils::to_string(&payment_hash.0),
810830
counterparty_skimmed_fee_msat,
811831
);
@@ -829,18 +849,24 @@ where
829849
match &info.kind {
830850
PaymentKind::Bolt11 { .. } => {
831851
let update = PaymentDetailsUpdate {
832-
counterparty_skimmed_fee_msat: Some(Some(counterparty_skimmed_fee_msat)),
852+
counterparty_skimmed_fee_msat: Some(Some(
853+
counterparty_skimmed_fee_msat,
854+
)),
833855
..PaymentDetailsUpdate::new(payment_id)
834856
};
835857
match self.payment_store.update(update).await {
836858
Ok(_) => (),
837859
Err(e) => {
838-
log_error!(self.logger, "Failed to access payment store: {}", e);
860+
log_error!(
861+
self.logger,
862+
"Failed to access payment store: {}",
863+
e
864+
);
839865
return Err(ReplayEvent());
840866
},
841867
};
842868
},
843-
_ => debug_assert!(false, "We only expect the counterparty to get away with withholding fees for BOLT11 payments."),
869+
_ => {},
844870
}
845871
}
846872
}
@@ -1936,6 +1962,7 @@ mod tests {
19361962
max_total_opening_fee_msat: Some(42_000),
19371963
max_proportional_opening_fee_ppm_msat: None,
19381964
}),
1965+
lsps2_bolt12_invoice_parameters: None,
19391966
};
19401967

19411968
assert_eq!(
@@ -1947,14 +1974,37 @@ mod tests {
19471974
);
19481975
}
19491976

1977+
#[test]
1978+
fn lsps2_bolt12_payment_metadata_decodes_total_fee_limit() {
1979+
let metadata = PaymentMetadata {
1980+
lsps2_parameters: Some(LSPS2Parameters {
1981+
max_total_opening_fee_msat: None,
1982+
max_proportional_opening_fee_ppm_msat: Some(10_000),
1983+
}),
1984+
lsps2_bolt12_invoice_parameters: None,
1985+
}
1986+
.encode_as_bolt12_payment_metadata();
1987+
1988+
assert_eq!(
1989+
EventHandler::<Arc<TestLogger>>::lsps2_max_total_opening_fee_msat_from_bolt12_metadata(
1990+
Some(&metadata),
1991+
100_000
1992+
),
1993+
Some(1_000)
1994+
);
1995+
}
1996+
19501997
#[test]
19511998
fn lsps2_payment_metadata_missing_or_malformed_limit_is_rejected() {
1952-
let empty_metadata = PaymentMetadata { lsps2_parameters: None }.encode();
1999+
let empty_metadata =
2000+
PaymentMetadata { lsps2_parameters: None, lsps2_bolt12_invoice_parameters: None }
2001+
.encode();
19532002
let metadata_without_fee_limit = PaymentMetadata {
19542003
lsps2_parameters: Some(LSPS2Parameters {
19552004
max_total_opening_fee_msat: None,
19562005
max_proportional_opening_fee_ppm_msat: None,
19572006
}),
2007+
lsps2_bolt12_invoice_parameters: None,
19582008
}
19592009
.encode();
19602010

src/payment/bolt11.rs

Lines changed: 1 addition & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ use std::sync::{Arc, RwLock};
1313

1414
use bitcoin::hashes::sha256::Hash as Sha256;
1515
use bitcoin::hashes::Hash;
16-
use lightning::impl_ser_tlv_based;
1716
use lightning::ln::channelmanager::{
1817
Bolt11InvoiceParameters, OptionalBolt11PaymentParams, PaymentId,
1918
};
@@ -32,8 +31,7 @@ use crate::ffi::{maybe_deref, maybe_try_convert_enum, maybe_wrap};
3231
use crate::liquidity::LiquiditySource;
3332
use crate::logger::{log_error, log_info, LdkLogger, Logger};
3433
use crate::payment::store::{
35-
LSPS2Parameters, PaymentDetails, PaymentDetailsUpdate, PaymentDirection, PaymentKind,
36-
PaymentStatus,
34+
PaymentDetails, PaymentDetailsUpdate, PaymentDirection, PaymentKind, PaymentStatus,
3735
};
3836
use crate::peer_store::{PeerInfo, PeerStore};
3937
use crate::runtime::Runtime;
@@ -49,16 +47,6 @@ type Bolt11InvoiceDescription = LdkBolt11InvoiceDescription;
4947
#[cfg(feature = "uniffi")]
5048
type Bolt11InvoiceDescription = crate::ffi::Bolt11InvoiceDescription;
5149

52-
/// Metadata carried in BOLT11 invoice `payment_metadata`.
53-
#[derive(Clone, Debug, PartialEq, Eq)]
54-
pub(crate) struct PaymentMetadata {
55-
pub(crate) lsps2_parameters: Option<LSPS2Parameters>,
56-
}
57-
58-
impl_ser_tlv_based!(PaymentMetadata, {
59-
(0, lsps2_parameters, option),
60-
});
61-
6250
/// A payment handler allowing to create and pay [BOLT 11] invoices.
6351
///
6452
/// Should be retrieved by calling [`Node::bolt11_payment`].
@@ -248,37 +236,6 @@ impl Bolt11Payment {
248236
}
249237
}
250238

251-
#[cfg(test)]
252-
mod tests {
253-
use lightning::util::ser::{Readable, Writeable};
254-
255-
use super::*;
256-
257-
#[test]
258-
fn empty_metadata_roundtrips() {
259-
let metadata = PaymentMetadata { lsps2_parameters: None };
260-
261-
let encoded = metadata.encode();
262-
let decoded = PaymentMetadata::read(&mut &*encoded).unwrap();
263-
264-
assert_eq!(metadata, decoded);
265-
}
266-
267-
#[test]
268-
fn lsps2_parameters_roundtrip() {
269-
let lsps2_parameters = LSPS2Parameters {
270-
max_total_opening_fee_msat: Some(42_000),
271-
max_proportional_opening_fee_ppm_msat: Some(17_000),
272-
};
273-
let metadata = PaymentMetadata { lsps2_parameters: Some(lsps2_parameters) };
274-
275-
let encoded = metadata.encode();
276-
let decoded = PaymentMetadata::read(&mut &*encoded).unwrap();
277-
278-
assert_eq!(metadata, decoded);
279-
}
280-
}
281-
282239
#[cfg_attr(feature = "uniffi", uniffi::export)]
283240
impl Bolt11Payment {
284241
/// Send a payment given an invoice.

src/payment/metadata.rs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
use std::collections::BTreeMap;
2+
3+
use lightning::impl_ser_tlv_based;
4+
use lightning::util::ser::{Readable, Writeable};
5+
use lightning_liquidity::lsps2::router::{
6+
LSPS2Bolt12InvoiceParameters, LSPS2Bolt12PaymentMetadataDecoder,
7+
};
8+
9+
use crate::payment::store::LSPS2Parameters;
10+
11+
pub(crate) const LDK_NODE_BOLT12_PAYMENT_METADATA_KEY: u64 = 0;
12+
13+
/// Metadata carried in BOLT11 invoice `payment_metadata` or BOLT12 payment metadata maps.
14+
#[derive(Clone, Debug, PartialEq, Eq)]
15+
pub(crate) struct PaymentMetadata {
16+
pub(crate) lsps2_parameters: Option<LSPS2Parameters>,
17+
pub(crate) lsps2_bolt12_invoice_parameters: Option<LSPS2Bolt12InvoiceParameters>,
18+
}
19+
20+
impl PaymentMetadata {
21+
pub(crate) fn encode_as_bolt12_payment_metadata(&self) -> BTreeMap<u64, Vec<u8>> {
22+
let mut metadata = BTreeMap::new();
23+
metadata.insert(LDK_NODE_BOLT12_PAYMENT_METADATA_KEY, self.encode());
24+
metadata
25+
}
26+
}
27+
28+
impl_ser_tlv_based!(PaymentMetadata, {
29+
(0, lsps2_parameters, option),
30+
(2, lsps2_bolt12_invoice_parameters, option),
31+
});
32+
33+
#[derive(Clone, Copy)]
34+
pub(crate) struct LdkNodeLSPS2Bolt12PaymentMetadataDecoder;
35+
36+
impl LSPS2Bolt12PaymentMetadataDecoder for LdkNodeLSPS2Bolt12PaymentMetadataDecoder {
37+
fn decode_lsps2_invoice_parameters(
38+
&self, payment_metadata: &BTreeMap<u64, Vec<u8>>,
39+
) -> Vec<LSPS2Bolt12InvoiceParameters> {
40+
payment_metadata
41+
.get(&LDK_NODE_BOLT12_PAYMENT_METADATA_KEY)
42+
.and_then(|encoded| PaymentMetadata::read(&mut &encoded[..]).ok())
43+
.and_then(|metadata| metadata.lsps2_bolt12_invoice_parameters)
44+
.into_iter()
45+
.collect()
46+
}
47+
}
48+
49+
#[cfg(test)]
50+
mod tests {
51+
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
52+
use lightning::util::ser::{Readable, Writeable};
53+
54+
use super::*;
55+
56+
fn pubkey(byte: u8) -> PublicKey {
57+
PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&[byte; 32]).unwrap())
58+
}
59+
60+
#[test]
61+
fn empty_metadata_roundtrips() {
62+
let metadata =
63+
PaymentMetadata { lsps2_parameters: None, lsps2_bolt12_invoice_parameters: None };
64+
65+
let encoded = metadata.encode();
66+
let decoded = PaymentMetadata::read(&mut &*encoded).unwrap();
67+
68+
assert_eq!(metadata, decoded);
69+
}
70+
71+
#[test]
72+
fn lsps2_parameters_roundtrip() {
73+
let lsps2_parameters = LSPS2Parameters {
74+
max_total_opening_fee_msat: Some(42_000),
75+
max_proportional_opening_fee_ppm_msat: Some(17_000),
76+
};
77+
let lsps2_bolt12_invoice_parameters = LSPS2Bolt12InvoiceParameters {
78+
counterparty_node_id: pubkey(2),
79+
intercept_scid: 42,
80+
cltv_expiry_delta: 144,
81+
};
82+
let metadata = PaymentMetadata {
83+
lsps2_parameters: Some(lsps2_parameters),
84+
lsps2_bolt12_invoice_parameters: Some(lsps2_bolt12_invoice_parameters),
85+
};
86+
87+
let encoded = metadata.encode();
88+
let decoded = PaymentMetadata::read(&mut &*encoded).unwrap();
89+
90+
assert_eq!(metadata, decoded);
91+
}
92+
93+
#[test]
94+
fn bolt12_metadata_decoder_extracts_invoice_parameters() {
95+
let lsps2_bolt12_invoice_parameters = LSPS2Bolt12InvoiceParameters {
96+
counterparty_node_id: pubkey(3),
97+
intercept_scid: 43,
98+
cltv_expiry_delta: 72,
99+
};
100+
let metadata = PaymentMetadata {
101+
lsps2_parameters: None,
102+
lsps2_bolt12_invoice_parameters: Some(lsps2_bolt12_invoice_parameters),
103+
}
104+
.encode_as_bolt12_payment_metadata();
105+
106+
let decoded =
107+
LdkNodeLSPS2Bolt12PaymentMetadataDecoder.decode_lsps2_invoice_parameters(&metadata);
108+
109+
assert_eq!(decoded, vec![lsps2_bolt12_invoice_parameters]);
110+
}
111+
}

src/payment/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,18 @@
1010
pub(crate) mod asynchronous;
1111
mod bolt11;
1212
mod bolt12;
13+
mod metadata;
1314
mod onchain;
1415
pub(crate) mod pending_payment_store;
1516
mod spontaneous;
1617
pub(crate) mod store;
1718
mod unified;
1819

1920
pub use bolt11::Bolt11Payment;
20-
pub(crate) use bolt11::PaymentMetadata;
2121
pub use bolt12::Bolt12Payment;
22+
pub(crate) use metadata::{
23+
LdkNodeLSPS2Bolt12PaymentMetadataDecoder, PaymentMetadata, LDK_NODE_BOLT12_PAYMENT_METADATA_KEY,
24+
};
2225
pub use onchain::OnchainPayment;
2326
pub use pending_payment_store::PendingPaymentDetails;
2427
pub use spontaneous::SpontaneousPayment;

0 commit comments

Comments
 (0)