Skip to content

Commit 236f588

Browse files
nepetdaywalker90
authored andcommitted
lsps-types: check the payment size against the params' limits in lsps2.buy
bLIP-52, "2. Request JIT Channel": The LSP MUST validate that the `payment_size_msat` is within the previous `min_payment_size_msat` and `max_payment_size_msat` parameters from the LSP. The service did not: a `payment_size_msat` outside the limits was accepted as long as the opening fee was below it. Its tests `buy_rejects_payment_below_min` and `buy_rejects_payment_above_max` passed for other reasons (fee above the payment size, and a mock that fails every buy with an internal error). `OpeningFeeParams::check_payment_size()`, which `validate()` runs on `lsps2.buy`, now rejects sizes outside the inclusive limits before computing the fee. The spec names no error code for this; below the minimum maps to `payment_size_too_small` (202) and above the maximum to `payment_size_too_large` (203), as in #8948. The service tests now check the codes and the limits themselves. Behavior change for the service only; the client already checked the limits before calling `lsps2.buy`. Changelog-Fixed: lsps-plugin: the LSPS2 service now rejects `lsps2.buy` with a `payment_size_msat` outside the offered `min_payment_size_msat`/`max_payment_size_msat`.
1 parent 2d18738 commit 236f588

2 files changed

Lines changed: 131 additions & 8 deletions

File tree

‎lsps-types/src/lsps2/params.rs‎

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ pub enum ValidationError {
2525
MinAboveMax { min: Msat, max: Msat },
2626
/// `valid_until` is in the past.
2727
Expired { valid_until: DateTime },
28+
/// `payment_size` is below `min_payment_size_msat`.
29+
BelowMinPaymentSize { payment_size: Msat, min: Msat },
30+
/// `payment_size` is above `max_payment_size_msat`.
31+
AboveMaxPaymentSize { payment_size: Msat, max: Msat },
2832
/// Computing the opening fee for `payment_size` overflowed.
2933
FeeOverflow { payment_size: Msat },
3034
/// The opening fee is not strictly less than `payment_size`.
@@ -45,6 +49,16 @@ impl core::fmt::Display for ValidationError {
4549
ValidationError::Expired { valid_until } => {
4650
write!(f, "opening fee params expired at {}", valid_until)
4751
}
52+
ValidationError::BelowMinPaymentSize { payment_size, min } => write!(
53+
f,
54+
"payment size {} is below min_payment_size_msat {}",
55+
payment_size, min
56+
),
57+
ValidationError::AboveMaxPaymentSize { payment_size, max } => write!(
58+
f,
59+
"payment size {} is above max_payment_size_msat {}",
60+
payment_size, max
61+
),
4862
ValidationError::FeeOverflow { payment_size } => write!(
4963
f,
5064
"opening fee computation overflowed for payment size {}",
@@ -65,14 +79,21 @@ impl core::fmt::Display for ValidationError {
6579
impl core::error::Error for ValidationError {}
6680

6781
/// Maps a validation failure to the `lsps2.buy` error an LSP returns for it.
82+
///
83+
/// bLIP-52 names no error for a `payment_size_msat` outside the params'
84+
/// limits; below the minimum maps to `payment_size_too_small` (202) and
85+
/// above the maximum to `payment_size_too_large` (203).
6886
impl From<ValidationError> for Error {
6987
fn from(error: ValidationError) -> Self {
7088
match error {
7189
ValidationError::MinAboveMax { .. } | ValidationError::Expired { .. } => {
7290
Error::InvalidOpeningFeeParams
7391
}
74-
ValidationError::FeeOverflow { .. } => Error::PaymentSizeTooLarge,
75-
ValidationError::FeeNotBelowPaymentSize { .. } => Error::PaymentSizeTooSmall,
92+
ValidationError::BelowMinPaymentSize { .. }
93+
| ValidationError::FeeNotBelowPaymentSize { .. } => Error::PaymentSizeTooSmall,
94+
ValidationError::AboveMaxPaymentSize { .. } | ValidationError::FeeOverflow { .. } => {
95+
Error::PaymentSizeTooLarge
96+
}
7697
}
7798
}
7899
}
@@ -106,16 +127,32 @@ impl OpeningFeeParams {
106127
*now > self.valid_until
107128
}
108129

109-
/// Computes the opening fee for `payment_size` and checks it as the LSP
130+
/// Checks `payment_size` and computes the opening fee for it as the LSP
110131
/// must when `payment_size_msat` is specified in `lsps2.buy`:
111132
///
133+
/// > The LSP MUST validate that the `payment_size_msat` is within the
134+
/// > previous `min_payment_size_msat` and `max_payment_size_msat`
135+
/// > parameters from the LSP.
136+
///
112137
/// > MUST compute the `opening_fee` and check that the computation did
113138
/// > not hit an overflow failure.
114139
/// > MUST check that the resulting `opening_fee` is strictly less than
115140
/// > the `payment_size_msat`.
116141
///
117142
/// Returns the opening fee.
118143
pub fn check_payment_size(&self, payment_size: Msat) -> Result<Msat, ValidationError> {
144+
if payment_size < self.min_payment_size_msat {
145+
return Err(ValidationError::BelowMinPaymentSize {
146+
payment_size,
147+
min: self.min_payment_size_msat,
148+
});
149+
}
150+
if payment_size > self.max_payment_size_msat {
151+
return Err(ValidationError::AboveMaxPaymentSize {
152+
payment_size,
153+
max: self.max_payment_size_msat,
154+
});
155+
}
119156
let opening_fee = self
120157
.opening_fee(payment_size)
121158
.ok_or(ValidationError::FeeOverflow { payment_size })?;
@@ -216,6 +253,7 @@ mod tests {
216253
fn test_validate_payment_size_overflow() {
217254
let p = OpeningFeeParams {
218255
proportional: Ppm::from_ppm(u32::MAX),
256+
max_payment_size_msat: Msat::from_msat(u64::MAX),
219257
..params()
220258
};
221259
let err = p
@@ -241,9 +279,11 @@ mod tests {
241279
#[test]
242280
fn test_validate_opening_fee_greater_than_payment_size() {
243281
// Less than min_fee_msat.
244-
let err = params()
245-
.validate(&now(), Some(Msat::from_msat(500)))
246-
.unwrap_err();
282+
let p = OpeningFeeParams {
283+
min_payment_size_msat: Msat::from_msat(1),
284+
..params()
285+
};
286+
let err = p.validate(&now(), Some(Msat::from_msat(500))).unwrap_err();
247287
assert!(matches!(
248288
err,
249289
ValidationError::FeeNotBelowPaymentSize { .. }
@@ -271,6 +311,42 @@ mod tests {
271311
assert_eq!(p.validate(&now(), None), Ok(None));
272312
}
273313

314+
#[test]
315+
fn test_validate_payment_size_limits_are_inclusive() {
316+
let p = OpeningFeeParams {
317+
min_fee_msat: Msat::from_msat(0),
318+
min_payment_size_msat: Msat::from_msat(5_000),
319+
max_payment_size_msat: Msat::from_msat(10_000),
320+
..params()
321+
};
322+
assert!(p.validate(&now(), Some(Msat::from_msat(5_000))).is_ok());
323+
assert!(p.validate(&now(), Some(Msat::from_msat(10_000))).is_ok());
324+
325+
let err = p
326+
.validate(&now(), Some(Msat::from_msat(4_999)))
327+
.unwrap_err();
328+
assert_eq!(
329+
err,
330+
ValidationError::BelowMinPaymentSize {
331+
payment_size: Msat::from_msat(4_999),
332+
min: Msat::from_msat(5_000),
333+
}
334+
);
335+
assert_eq!(Error::from(err), Error::PaymentSizeTooSmall);
336+
337+
let err = p
338+
.validate(&now(), Some(Msat::from_msat(10_001)))
339+
.unwrap_err();
340+
assert_eq!(
341+
err,
342+
ValidationError::AboveMaxPaymentSize {
343+
payment_size: Msat::from_msat(10_001),
344+
max: Msat::from_msat(10_000),
345+
}
346+
);
347+
assert_eq!(Error::from(err), Error::PaymentSizeTooLarge);
348+
}
349+
274350
#[test]
275351
fn test_check_limits() {
276352
assert_eq!(params().check_limits(), Ok(()));

‎plugins/lsps-plugin/src/core/lsps2/service.rs‎

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,39 @@ mod tests {
502502

503503
let result = h.handle_buy(test_peer_id(), request).await;
504504

505-
assert!(result.is_err());
505+
assert_eq!(result.unwrap_err().code, 202);
506+
}
507+
508+
#[tokio::test]
509+
async fn buy_rejects_payment_just_below_min() {
510+
let api = MockApi::new();
511+
let h = handler(api);
512+
513+
// The opening fee (10_000) is well below this payment size, only the
514+
// limit rejects it.
515+
let request = Lsps2BuyRequest {
516+
opening_fee_params: test_opening_fee_params(&test_secret()),
517+
payment_size_msat: Some(Msat(999_999)),
518+
};
519+
520+
let result = h.handle_buy(test_peer_id(), request).await;
521+
522+
assert_eq!(result.unwrap_err().code, 202);
523+
}
524+
525+
#[tokio::test]
526+
async fn buy_accepts_payment_at_limits() {
527+
for payment_size in [1_000_000, 100_000_000] {
528+
let api = MockApi::new()
529+
.with_blockheight(800_000)
530+
.with_store_result(true);
531+
let h = handler(api);
532+
let request = Lsps2BuyRequest {
533+
opening_fee_params: test_opening_fee_params(&test_secret()),
534+
payment_size_msat: Some(Msat(payment_size)),
535+
};
536+
assert!(h.handle_buy(test_peer_id(), request).await.is_ok());
537+
}
506538
}
507539

508540
#[tokio::test]
@@ -517,7 +549,22 @@ mod tests {
517549

518550
let result = h.handle_buy(test_peer_id(), request).await;
519551

520-
assert!(result.is_err());
552+
assert_eq!(result.unwrap_err().code, 203);
553+
}
554+
555+
#[tokio::test]
556+
async fn buy_rejects_payment_just_above_max() {
557+
let api = MockApi::new();
558+
let h = handler(api);
559+
560+
let request = Lsps2BuyRequest {
561+
opening_fee_params: test_opening_fee_params(&test_secret()),
562+
payment_size_msat: Some(Msat(100_000_001)),
563+
};
564+
565+
let result = h.handle_buy(test_peer_id(), request).await;
566+
567+
assert_eq!(result.unwrap_err().code, 203);
521568
}
522569

523570
#[tokio::test]

0 commit comments

Comments
 (0)