Skip to content

Commit 0765ba0

Browse files
committed
Introduce FlowEvents for manual handling of offers messages
Until now, offers messages were processed internally without exposing intermediate steps. This made it harder for callers to intercept or analyse offer messages before deciding how to respond to them. `FlowEvents` provide an optional mechanism to surface these events back to the user. With events enabled, the caller can manually inspect an incoming message, choose to construct and sign an invoice, or send back an InvoiceError. This shifts control to the user where needed, while keeping the default automatic flow unchanged.
1 parent c86da43 commit 0765ba0

File tree

2 files changed

+88
-18
lines changed

2 files changed

+88
-18
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3737,7 +3737,7 @@ where
37373737
let flow = OffersMessageFlow::new(
37383738
ChainHash::using_genesis_block(params.network), params.best_block,
37393739
our_network_pubkey, current_timestamp, expanded_inbound_key,
3740-
node_signer.get_receive_auth_key(), secp_ctx.clone(), message_router
3740+
node_signer.get_receive_auth_key(), secp_ctx.clone(), message_router, false,
37413741
);
37423742

37433743
ChannelManager {
@@ -14325,7 +14325,7 @@ where
1432514325
None => return None,
1432614326
};
1432714327

14328-
let invoice_request = match self.flow.verify_invoice_request(invoice_request, context) {
14328+
let invoice_request = match self.flow.verify_invoice_request(invoice_request, context, responder.clone()) {
1432914329
Ok(InvreqResponseInstructions::SendInvoice(invoice_request)) => invoice_request,
1433014330
Ok(InvreqResponseInstructions::SendStaticInvoice {
1433114331
recipient_id: _recipient_id, invoice_id: _invoice_id
@@ -14336,6 +14336,7 @@ where
1433614336

1433714337
return None
1433814338
},
14339+
Ok(InvreqResponseInstructions::AsynchronouslyHandleResponse) => return None,
1433914340
Err(_) => return None,
1434014341
};
1434114342

@@ -16959,6 +16960,7 @@ where
1695916960
args.node_signer.get_receive_auth_key(),
1696016961
secp_ctx.clone(),
1696116962
args.message_router,
16963+
false,
1696216964
)
1696316965
.with_async_payments_offers_cache(async_receive_offer_cache);
1696416966

lightning/src/offers/flow.rs

Lines changed: 84 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,32 @@ use {
7070
crate::onion_message::dns_resolution::{DNSResolverMessage, DNSSECQuery, OMNameResolver},
7171
};
7272

73+
/// Defines the events that can be optionally triggered when processing offers messages.
74+
///
75+
/// Once generated, these events are stored in the [`OffersMessageFlow`], where they can be
76+
/// manually inspected and responded to.
77+
pub enum FlowEvents {
78+
/// Notifies that an [`InvoiceRequest`] has been received.
79+
///
80+
/// To respond to this message:
81+
/// - Based on the variant of [`InvoiceRequestVerifiedFromOffer`], create the appropriate invoice builder:
82+
/// - [`InvoiceRequestVerifiedFromOffer::DerivedKeys`] → use
83+
/// [`OffersMessageFlow::create_invoice_builder_from_invoice_request_with_keys`]
84+
/// - [`InvoiceRequestVerifiedFromOffer::ExplicitKeys`] → use
85+
/// [`OffersMessageFlow::create_invoice_builder_from_invoice_request_without_keys`]
86+
/// - After building the invoice, sign it and send it back using the provided reply path via
87+
/// [`OffersMessageFlow::enqueue_invoice_using_reply_paths`].
88+
///
89+
/// If the invoice request is invalid, respond with an [`InvoiceError`] using
90+
/// [`OffersMessageFlow::enqueue_invoice_error`].
91+
InvoiceRequestReceived {
92+
/// The received, verified invoice request.
93+
invoice_request: InvoiceRequestVerifiedFromOffer,
94+
/// The reply path to use when responding to the invoice request.
95+
reply_path: BlindedMessagePath,
96+
},
97+
}
98+
7399
/// A BOLT12 offers code and flow utility provider, which facilitates
74100
/// BOLT12 builder generation and onion message handling.
75101
///
@@ -91,6 +117,8 @@ where
91117
secp_ctx: Secp256k1<secp256k1::All>,
92118
message_router: MR,
93119

120+
pub(crate) enable_events: bool,
121+
94122
#[cfg(not(any(test, feature = "_test_utils")))]
95123
pending_offers_messages: Mutex<Vec<(OffersMessage, MessageSendInstructions)>>,
96124
#[cfg(any(test, feature = "_test_utils"))]
@@ -103,6 +131,8 @@ where
103131
pub(crate) hrn_resolver: OMNameResolver,
104132
#[cfg(feature = "dnssec")]
105133
pending_dns_onion_messages: Mutex<Vec<(DNSResolverMessage, MessageSendInstructions)>>,
134+
135+
pending_flow_events: Mutex<Vec<FlowEvents>>,
106136
}
107137

108138
impl<MR: Deref> OffersMessageFlow<MR>
@@ -114,6 +144,7 @@ where
114144
chain_hash: ChainHash, best_block: BestBlock, our_network_pubkey: PublicKey,
115145
current_timestamp: u32, inbound_payment_key: inbound_payment::ExpandedKey,
116146
receive_auth_key: ReceiveAuthKey, secp_ctx: Secp256k1<secp256k1::All>, message_router: MR,
147+
enable_events: bool,
117148
) -> Self {
118149
Self {
119150
chain_hash,
@@ -128,6 +159,8 @@ where
128159
secp_ctx,
129160
message_router,
130161

162+
enable_events,
163+
131164
pending_offers_messages: Mutex::new(Vec::new()),
132165
pending_async_payments_messages: Mutex::new(Vec::new()),
133166

@@ -137,6 +170,8 @@ where
137170
pending_dns_onion_messages: Mutex::new(Vec::new()),
138171

139172
async_receive_offer_cache: Mutex::new(AsyncReceiveOfferCache::new()),
173+
174+
pending_flow_events: Mutex::new(Vec::new()),
140175
}
141176
}
142177

@@ -152,6 +187,18 @@ where
152187
self
153188
}
154189

190+
/// Enables [`FlowEvents`] for this flow.
191+
///
192+
/// By default, events are not emitted when processing offers messages. Calling this method
193+
/// sets the internal `enable_events` flag to `true`, allowing you to receive [`FlowEvents`]
194+
/// such as [`FlowEvents::InvoiceRequestReceived`].
195+
///
196+
/// This is useful when you want to manually inspect, handle, or respond to incoming
197+
/// offers messages rather than having them processed automatically.
198+
pub fn enable_events(&mut self) {
199+
self.enable_events = true;
200+
}
201+
155202
/// Sets the [`BlindedMessagePath`]s that we will use as an async recipient to interactively build
156203
/// [`Offer`]s with a static invoice server, so the server can serve [`StaticInvoice`]s to payers
157204
/// on our behalf when we're offline.
@@ -407,13 +454,16 @@ pub enum InvreqResponseInstructions {
407454
/// An identifier for the specific invoice being requested by the payer.
408455
invoice_id: u128,
409456
},
457+
/// We are recipient of this payment, and should handle the response asynchronously.
458+
AsynchronouslyHandleResponse,
410459
}
411460

412461
impl<MR: Deref> OffersMessageFlow<MR>
413462
where
414463
MR::Target: MessageRouter,
415464
{
416465
/// Verifies an [`InvoiceRequest`] using the provided [`OffersContext`] or the [`InvoiceRequest::metadata`].
466+
/// It also helps determine the response instructions, corresponding to the verified invoice request must be taken.
417467
///
418468
/// - If an [`OffersContext::InvoiceRequest`] with a `nonce` is provided, verification is performed using recipient context data.
419469
/// - If no context is provided but the [`InvoiceRequest`] contains [`Offer`] metadata, verification is performed using that metadata.
@@ -426,27 +476,26 @@ where
426476
/// - The verification process (via recipient context data or metadata) fails.
427477
pub fn verify_invoice_request(
428478
&self, invoice_request: InvoiceRequest, context: Option<OffersContext>,
479+
responder: Responder,
429480
) -> Result<InvreqResponseInstructions, ()> {
430481
let secp_ctx = &self.secp_ctx;
431482
let expanded_key = &self.inbound_payment_key;
432483

484+
if let Some(OffersContext::StaticInvoiceRequested {
485+
recipient_id,
486+
invoice_id,
487+
path_absolute_expiry,
488+
}) = context
489+
{
490+
if path_absolute_expiry < self.duration_since_epoch() {
491+
return Err(());
492+
}
493+
return Ok(InvreqResponseInstructions::SendStaticInvoice { recipient_id, invoice_id });
494+
}
495+
433496
let nonce = match context {
434-
None if invoice_request.metadata().is_some() => None,
435497
Some(OffersContext::InvoiceRequest { nonce }) => Some(nonce),
436-
Some(OffersContext::StaticInvoiceRequested {
437-
recipient_id,
438-
invoice_id,
439-
path_absolute_expiry,
440-
}) => {
441-
if path_absolute_expiry < self.duration_since_epoch() {
442-
return Err(());
443-
}
444-
445-
return Ok(InvreqResponseInstructions::SendStaticInvoice {
446-
recipient_id,
447-
invoice_id,
448-
});
449-
},
498+
None if invoice_request.metadata().is_some() => None,
450499
_ => return Err(()),
451500
};
452501

@@ -457,7 +506,16 @@ where
457506
None => invoice_request.verify_using_metadata(expanded_key, secp_ctx),
458507
}?;
459508

460-
Ok(InvreqResponseInstructions::SendInvoice(invoice_request))
509+
if self.enable_events {
510+
self.pending_flow_events.lock().unwrap().push(FlowEvents::InvoiceRequestReceived {
511+
invoice_request,
512+
reply_path: responder.into_blinded_path(),
513+
});
514+
515+
Ok(InvreqResponseInstructions::AsynchronouslyHandleResponse)
516+
} else {
517+
Ok(InvreqResponseInstructions::SendInvoice(invoice_request))
518+
}
461519
}
462520

463521
/// Verifies a [`Bolt12Invoice`] using the provided [`OffersContext`] or the invoice's payer metadata,
@@ -1288,6 +1346,11 @@ where
12881346
Ok(())
12891347
}
12901348

1349+
/// Enqueues the generated [`FlowEvents`] to be processed.
1350+
pub fn enqueue_flow_event(&self, flow_event: FlowEvents) {
1351+
self.pending_flow_events.lock().unwrap().push(flow_event);
1352+
}
1353+
12911354
/// Gets the enqueued [`OffersMessage`] with their corresponding [`MessageSendInstructions`].
12921355
pub fn release_pending_offers_messages(&self) -> Vec<(OffersMessage, MessageSendInstructions)> {
12931356
core::mem::take(&mut self.pending_offers_messages.lock().unwrap())
@@ -1300,6 +1363,11 @@ where
13001363
core::mem::take(&mut self.pending_async_payments_messages.lock().unwrap())
13011364
}
13021365

1366+
/// Gets the enqueued [`FlowEvents`] to be processed.
1367+
pub fn release_pending_flow_events(&self) -> Vec<FlowEvents> {
1368+
core::mem::take(&mut self.pending_flow_events.lock().unwrap())
1369+
}
1370+
13031371
/// Gets the enqueued [`DNSResolverMessage`] with their corresponding [`MessageSendInstructions`].
13041372
#[cfg(feature = "dnssec")]
13051373
pub fn release_pending_dns_messages(

0 commit comments

Comments
 (0)