Skip to content

Commit 0eb94f4

Browse files
committed
Introduce recurrence handling in InvoiceRequest flow (payee side)
This commit adds payee-side handling for recurrence-enabled `InvoiceRequest`s. The logic now: - Distinguishes between one-off requests, initial recurring requests, and successive recurring requests. - Initializes a new `RecurrenceData` session on the first recurring request (counter = 0). - Validates successive requests against stored session state (offset, expected counter, basetime). - Enforces paywindow timing when applicable. - Handles recurrence cancellation by removing the session and returning no invoice. This forms the core stateful logic required for a node to act as a BOLT12 recurrence payee. Payment-acceptance and state-update logic will follow in the next commit.
1 parent e989989 commit 0eb94f4

File tree

1 file changed

+88
-4
lines changed

1 file changed

+88
-4
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15403,7 +15403,7 @@ where
1540315403
None => return None,
1540415404
};
1540515405

15406-
let invoice_request = match self.flow.verify_invoice_request(invoice_request, context) {
15406+
let verified_invoice_request = match self.flow.verify_invoice_request(invoice_request, context) {
1540715407
Ok(InvreqResponseInstructions::SendInvoice(invoice_request)) => invoice_request,
1540815408
Ok(InvreqResponseInstructions::SendStaticInvoice { recipient_id, invoice_slot, invoice_request }) => {
1540915409
self.pending_events.lock().unwrap().push_back((Event::StaticInvoiceRequested {
@@ -15414,6 +15414,7 @@ where
1541415414
},
1541515415
Err(_) => return None,
1541615416
};
15417+
let invoice_request = verified_invoice_request.inner();
1541715418

1541815419
#[cfg(not(feature = "std"))]
1541915420
let created_at = Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64);
@@ -15422,6 +15423,82 @@ where
1542215423
.duration_since(std::time::SystemTime::UNIX_EPOCH)
1542315424
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
1542415425

15426+
// Recurrence checks
15427+
let recurrence_basetime = if let Some(recurrence_fields) = invoice_request.recurrence_fields() {
15428+
let payer_id = invoice_request.payer_signing_pubkey();
15429+
let mut sessions= self.active_recurrence_sessions.lock().unwrap();
15430+
15431+
// We first categorise the invoice request based on it's type.
15432+
let recurrence_counter = invoice_request.recurrence_counter();
15433+
let recurrence_cancel = invoice_request.recurrence_cancel();
15434+
let existing_session = sessions.get(&payer_id);
15435+
15436+
match (existing_session, recurrence_counter, recurrence_cancel) {
15437+
// This represents case where the payer, didn't support recurrence
15438+
// but we set recurrence optional so we allow payer to pay one-off
15439+
(None, None, None) => { None },
15440+
// It's the first invoice request in recurrence series
15441+
(None, Some(0), None) => {
15442+
let recurrence_basetime = recurrence_fields
15443+
.recurrence_base
15444+
.map(|base| base.basetime)
15445+
.unwrap_or(created_at.as_secs());
15446+
15447+
// Next we prepare recurrence_data to be stored in our recurrence session
15448+
let recurrence_data = RecurrenceData {
15449+
invoice_request_start: invoice_request.recurrence_start(),
15450+
next_payable_counter: 0,
15451+
recurrence_basetime,
15452+
};
15453+
// Now we store it in our active_recurrence_session
15454+
sessions.insert(payer_id, recurrence_data);
15455+
15456+
Some(recurrence_basetime)
15457+
15458+
},
15459+
// it's a successive invoice request in recurrence series
15460+
(Some(data), Some(counter), None) if counter > 0 => {
15461+
// We confirm all the data to ensure this is an expected successive invoice request
15462+
if data.invoice_request_start != invoice_request.recurrence_start()
15463+
|| data.next_payable_counter != counter
15464+
{
15465+
return None
15466+
}
15467+
15468+
// Next we ensure that the successive invoice_request is received between the period's paywindow
15469+
if let Some(window) = recurrence_fields.recurrence_paywindow {
15470+
let period_index = data.invoice_request_start.unwrap_or(0) + counter;
15471+
15472+
let period_start = data.recurrence_basetime
15473+
+ period_index as u64 * recurrence_fields.recurrence.period_length_secs().unwrap();
15474+
15475+
if created_at.as_secs() < period_start - window.seconds_before as u64
15476+
|| created_at.as_secs() >= period_start + window.seconds_after as u64
15477+
{
15478+
return None
15479+
}
15480+
}
15481+
15482+
Some(data.recurrence_basetime)
15483+
},
15484+
// it's a cancel recurrence invoice request
15485+
(Some(_data), Some(counter), Some(())) if counter > 0 => {
15486+
// Here we simply remove the data from our sessions
15487+
sessions.remove(&payer_id);
15488+
15489+
// And since cancellation invoice request are stub invoice request,
15490+
// we don't respond to this invoice request
15491+
return None
15492+
},
15493+
_ => {
15494+
debug_assert!(false, "Should be unreachable, as all the invalid cases are handled during parsing");
15495+
return None
15496+
}
15497+
}
15498+
} else {
15499+
None
15500+
};
15501+
1542515502
let get_payment_info = |amount_msats, relative_expiry| {
1542615503
self.create_inbound_payment(
1542715504
Some(amount_msats),
@@ -15430,7 +15507,7 @@ where
1543015507
).map_err(|_| Bolt12SemanticError::InvalidAmount)
1543115508
};
1543215509

15433-
let (result, context) = match invoice_request {
15510+
let (result, context) = match verified_invoice_request {
1543415511
InvoiceRequestVerifiedFromOffer::DerivedKeys(request) => {
1543515512
let result = self.flow.create_invoice_builder_from_invoice_request_with_keys(
1543615513
&self.router,
@@ -15441,7 +15518,11 @@ where
1544115518
);
1544215519

1544315520
match result {
15444-
Ok((builder, context)) => {
15521+
Ok((mut builder, context)) => {
15522+
recurrence_basetime.map(|basetime|
15523+
builder.set_invoice_recurrence_basetime(basetime)
15524+
);
15525+
1544515526
let res = builder
1544615527
.build_and_sign(&self.secp_ctx)
1544715528
.map_err(InvoiceError::from);
@@ -15466,7 +15547,10 @@ where
1546615547
);
1546715548

1546815549
match result {
15469-
Ok((builder, context)) => {
15550+
Ok((mut builder, context)) => {
15551+
recurrence_basetime.map(|basetime|
15552+
builder.set_invoice_recurrence_basetime(basetime)
15553+
);
1547015554
let res = builder
1547115555
.build()
1547215556
.map_err(InvoiceError::from)

0 commit comments

Comments
 (0)