Skip to content

Commit 83863de

Browse files
shaavancodex
andcommitted
[test] Cover invoice recurrence parsing
Add targeted coverage for the invoice recurrence fields introduced by the preceding feature commit. The new tests prove that recurring invoice TLVs round-trip on offer invoices, that incomplete recurrence TLVs are rejected, and that refund/static invoice parsers still reject unexpected recurrence data. Co-Authored-By: OpenAI Codex <codex@openai.com>
1 parent 1dfc5b9 commit 83863de

2 files changed

Lines changed: 233 additions & 3 deletions

File tree

lightning/src/offers/invoice.rs

Lines changed: 162 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,8 @@ macro_rules! invoice_builder_methods {
419419
pub(crate) fn recurrence_fields(
420420
_invoice_request: &InvoiceRequest, _recurrence_basetime: Option<u64>,
421421
) -> Result<Option<InvoiceRecurrence>, Bolt12SemanticError> {
422-
todo!("Future commits will introduce the Recurrence Token creation logic")
422+
//TODO: Future commits will introduce the recurrence token creation logic
423+
return Ok(None)
423424
}
424425

425426
#[cfg_attr(c_bindings, allow(dead_code))]
@@ -1880,8 +1881,9 @@ pub(super) fn check_invoice_signing_pubkey(
18801881
mod tests {
18811882
use super::{
18821883
Bolt12Invoice, ExperimentalInvoiceTlvStreamRef, FallbackAddress, FullInvoiceTlvStreamRef,
1883-
InvoiceTlvStreamRef, UnsignedBolt12Invoice, DEFAULT_RELATIVE_EXPIRY,
1884-
EXPERIMENTAL_INVOICE_TYPES, INVOICE_TYPES, SIGNATURE_TAG,
1884+
InvoiceContents, InvoiceFields, InvoiceRecurrence, InvoiceTlvStreamRef,
1885+
UnsignedBolt12Invoice, DEFAULT_RELATIVE_EXPIRY, EXPERIMENTAL_INVOICE_TYPES, INVOICE_TYPES,
1886+
SIGNATURE_TAG,
18851887
};
18861888

18871889
use bitcoin::address::Address;
@@ -3452,6 +3454,163 @@ mod tests {
34523454
}
34533455
}
34543456

3457+
#[test]
3458+
fn parses_invoice_with_recurrence() {
3459+
let expanded_key = ExpandedKey::new([42; 32]);
3460+
let entropy = FixedEntropy {};
3461+
let nonce = Nonce::from_entropy_source(&entropy);
3462+
let secp_ctx = Secp256k1::new();
3463+
let payment_id = PaymentId([1; 32]);
3464+
let recurrence_basetime = 123_456;
3465+
let recurrence_token = vec![1, 2, 3];
3466+
3467+
let offer = OfferBuilder::new(recipient_pubkey()).amount_msats(1000).build().unwrap();
3468+
let invoice_request = offer
3469+
.request_invoice(&expanded_key, nonce, &secp_ctx, payment_id)
3470+
.unwrap()
3471+
.build_and_sign()
3472+
.unwrap();
3473+
3474+
let contents = InvoiceContents::ForOffer {
3475+
invoice_request: invoice_request.contents.clone(),
3476+
fields: InvoiceFields {
3477+
payment_paths: payment_paths(),
3478+
created_at: now(),
3479+
relative_expiry: None,
3480+
payment_hash: payment_hash(),
3481+
amount_msats: 1000,
3482+
fallbacks: None,
3483+
features: Bolt12InvoiceFeatures::empty(),
3484+
signing_pubkey: recipient_pubkey(),
3485+
#[cfg(test)]
3486+
experimental_baz: None,
3487+
invoice_recurrence: Some(InvoiceRecurrence {
3488+
recurrence_basetime,
3489+
recurrence_token: recurrence_token.clone(),
3490+
}),
3491+
},
3492+
};
3493+
3494+
let invoice = UnsignedBolt12Invoice::new(invoice_request.bytes(), contents)
3495+
.sign(recipient_sign)
3496+
.unwrap();
3497+
3498+
let mut buffer = Vec::new();
3499+
invoice.write(&mut buffer).unwrap();
3500+
3501+
match Bolt12Invoice::try_from(buffer) {
3502+
Ok(invoice) => {
3503+
match &invoice.contents {
3504+
InvoiceContents::ForOffer { fields, .. } => {
3505+
assert_eq!(
3506+
fields.invoice_recurrence,
3507+
Some(InvoiceRecurrence {
3508+
recurrence_basetime,
3509+
recurrence_token: recurrence_token.clone(),
3510+
})
3511+
);
3512+
},
3513+
InvoiceContents::ForRefund { .. } => panic!("expected offer invoice"),
3514+
}
3515+
3516+
let tlv_stream = invoice.as_tlv_stream();
3517+
assert_eq!(tlv_stream.3.invoice_recurrence_basetime, Some(recurrence_basetime));
3518+
assert_eq!(tlv_stream.3.invoice_recurrence_token, Some(&recurrence_token));
3519+
},
3520+
Err(e) => panic!("error parsing invoice: {:?}", e),
3521+
}
3522+
}
3523+
3524+
#[test]
3525+
fn fails_parsing_invoice_with_invalid_recurrence() {
3526+
let expanded_key = ExpandedKey::new([42; 32]);
3527+
let entropy = FixedEntropy {};
3528+
let nonce = Nonce::from_entropy_source(&entropy);
3529+
let secp_ctx = Secp256k1::new();
3530+
let payment_id = PaymentId([1; 32]);
3531+
let recurrence_token = vec![1, 2, 3];
3532+
3533+
let offer = OfferBuilder::new(recipient_pubkey()).amount_msats(1000).build().unwrap();
3534+
let invoice_request = offer
3535+
.request_invoice(&expanded_key, nonce, &secp_ctx, payment_id)
3536+
.unwrap()
3537+
.build_and_sign()
3538+
.unwrap();
3539+
3540+
let contents = InvoiceContents::ForOffer {
3541+
invoice_request: invoice_request.contents.clone(),
3542+
fields: InvoiceFields {
3543+
payment_paths: payment_paths(),
3544+
created_at: now(),
3545+
relative_expiry: None,
3546+
payment_hash: payment_hash(),
3547+
amount_msats: 1000,
3548+
fallbacks: None,
3549+
features: Bolt12InvoiceFeatures::empty(),
3550+
signing_pubkey: recipient_pubkey(),
3551+
#[cfg(test)]
3552+
experimental_baz: None,
3553+
invoice_recurrence: Some(InvoiceRecurrence {
3554+
recurrence_basetime: 123_456,
3555+
recurrence_token: recurrence_token.clone(),
3556+
}),
3557+
},
3558+
};
3559+
3560+
let invoice = UnsignedBolt12Invoice::new(invoice_request.bytes(), contents)
3561+
.sign(recipient_sign)
3562+
.unwrap();
3563+
3564+
let mut missing_token = invoice.as_tlv_stream();
3565+
missing_token.3.invoice_recurrence_token = None;
3566+
3567+
match Bolt12Invoice::try_from(missing_token.to_bytes()) {
3568+
Ok(_) => panic!("expected error"),
3569+
Err(e) => assert_eq!(
3570+
e,
3571+
Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidRecurrence)
3572+
),
3573+
}
3574+
3575+
let mut missing_basetime = invoice.as_tlv_stream();
3576+
missing_basetime.3.invoice_recurrence_basetime = None;
3577+
3578+
match Bolt12Invoice::try_from(missing_basetime.to_bytes()) {
3579+
Ok(_) => panic!("expected error"),
3580+
Err(e) => assert_eq!(
3581+
e,
3582+
Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidRecurrence)
3583+
),
3584+
}
3585+
}
3586+
3587+
#[test]
3588+
fn fails_parsing_refund_invoice_with_recurrence() {
3589+
let refund =
3590+
RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap().build().unwrap();
3591+
3592+
let invoice = refund
3593+
.respond_with_no_std(payment_paths(), payment_hash(), recipient_pubkey(), now())
3594+
.unwrap()
3595+
.build()
3596+
.unwrap()
3597+
.sign(recipient_sign)
3598+
.unwrap();
3599+
3600+
let recurrence_token = vec![1, 2, 3];
3601+
let mut tlv_stream = invoice.as_tlv_stream();
3602+
tlv_stream.3.invoice_recurrence_basetime = Some(123_456);
3603+
tlv_stream.3.invoice_recurrence_token = Some(&recurrence_token);
3604+
3605+
match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
3606+
Ok(_) => panic!("expected error"),
3607+
Err(e) => assert_eq!(
3608+
e,
3609+
Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnexpectedRecurrence)
3610+
),
3611+
}
3612+
}
3613+
34553614
#[test]
34563615
fn parses_invoice_with_experimental_tlv_records() {
34573616
let expanded_key = ExpandedKey::new([42; 32]);

lightning/src/offers/static_invoice.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,7 @@ mod tests {
768768
use crate::offers::nonce::Nonce;
769769
use crate::offers::offer::{
770770
ExperimentalOfferTlvStreamRef, Offer, OfferBuilder, OfferTlvStreamRef, Quantity,
771+
RecurrencePeriod,
771772
};
772773
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
773774
use crate::offers::static_invoice::{
@@ -1677,6 +1678,39 @@ mod tests {
16771678
}
16781679
}
16791680

1681+
#[test]
1682+
fn fails_parsing_invoice_with_recurrence() {
1683+
let invoice = invoice();
1684+
let recurrence_token = vec![1, 2, 3];
1685+
let mut tlv_stream = invoice.as_tlv_stream();
1686+
tlv_stream.1.invoice_recurrence_basetime = Some(123_456);
1687+
tlv_stream.1.invoice_recurrence_token = Some(&recurrence_token);
1688+
1689+
match StaticInvoice::try_from(tlv_stream_to_bytes(&tlv_stream)) {
1690+
Ok(_) => panic!("expected error"),
1691+
Err(e) => assert_eq!(
1692+
e,
1693+
Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnexpectedRecurrence)
1694+
),
1695+
}
1696+
}
1697+
1698+
#[test]
1699+
fn fails_parsing_invoice_with_offer_recurrence() {
1700+
let invoice = invoice();
1701+
let recurrence_period = RecurrencePeriod::Days(14);
1702+
let mut tlv_stream = invoice.as_tlv_stream();
1703+
tlv_stream.0.recurrence_optional = Some(&recurrence_period);
1704+
1705+
match StaticInvoice::try_from(tlv_stream_to_bytes(&tlv_stream)) {
1706+
Ok(_) => panic!("expected error"),
1707+
Err(e) => assert_eq!(
1708+
e,
1709+
Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnexpectedRecurrence)
1710+
),
1711+
}
1712+
}
1713+
16801714
#[test]
16811715
fn fails_parsing_invoice_with_invalid_offer_fields() {
16821716
// Error if the offer is missing paths.
@@ -1744,4 +1778,41 @@ mod tests {
17441778

17451779
assert_eq!(invoice.offer_id(), offer_id);
17461780
}
1781+
1782+
#[test]
1783+
fn fails_building_invoice_from_offer_with_recurrence() {
1784+
let node_id = recipient_pubkey();
1785+
let now = now();
1786+
let expanded_key = ExpandedKey::new([42; 32]);
1787+
let entropy = FixedEntropy {};
1788+
let nonce = Nonce::from_entropy_source(&entropy);
1789+
let secp_ctx = Secp256k1::new();
1790+
1791+
let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx)
1792+
.path(blinded_path())
1793+
.build()
1794+
.unwrap();
1795+
1796+
let recurrence_period = RecurrencePeriod::Days(14);
1797+
let mut tlv_stream = offer.as_tlv_stream();
1798+
tlv_stream.0.recurrence_optional = Some(&recurrence_period);
1799+
1800+
let mut offer_bytes = Vec::new();
1801+
tlv_stream.0.write(&mut offer_bytes).unwrap();
1802+
tlv_stream.1.write(&mut offer_bytes).unwrap();
1803+
let offer = Offer::try_from(offer_bytes).unwrap();
1804+
1805+
match StaticInvoiceBuilder::for_offer_using_derived_keys(
1806+
&offer,
1807+
payment_paths(),
1808+
vec![blinded_path()],
1809+
now,
1810+
&expanded_key,
1811+
nonce,
1812+
&secp_ctx,
1813+
) {
1814+
Ok(_) => panic!("expected error"),
1815+
Err(e) => assert_eq!(e, Bolt12SemanticError::UnexpectedRecurrence),
1816+
}
1817+
}
17471818
}

0 commit comments

Comments
 (0)