Skip to content

Commit 9d53077

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 682b727 commit 9d53077

File tree

2 files changed

+78
-4
lines changed

2 files changed

+78
-4
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3947,7 +3947,8 @@ where
39473947
let flow = OffersMessageFlow::new(
39483948
ChainHash::using_genesis_block(params.network), params.best_block,
39493949
our_network_pubkey, current_timestamp, expanded_inbound_key,
3950-
node_signer.get_receive_auth_key(), secp_ctx.clone(), message_router, logger.clone(),
3950+
node_signer.get_receive_auth_key(), secp_ctx.clone(), message_router, false,
3951+
logger.clone(),
39513952
);
39523953

39533954
ChannelManager {
@@ -15319,7 +15320,7 @@ where
1531915320
None => return None,
1532015321
};
1532115322

15322-
let invoice_request = match self.flow.verify_invoice_request(invoice_request, context) {
15323+
let invoice_request = match self.flow.verify_invoice_request(invoice_request, context, responder.clone()) {
1532315324
Ok(InvreqResponseInstructions::SendInvoice(invoice_request)) => invoice_request,
1532415325
Ok(InvreqResponseInstructions::SendStaticInvoice { recipient_id, invoice_slot, invoice_request }) => {
1532515326
self.pending_events.lock().unwrap().push_back((Event::StaticInvoiceRequested {
@@ -15328,6 +15329,7 @@ where
1532815329

1532915330
return None
1533015331
},
15332+
Ok(InvreqResponseInstructions::AsynchronouslyHandleResponse) => return None,
1533115333
Err(_) => return None,
1533215334
};
1533315335

@@ -18126,6 +18128,7 @@ where
1812618128
args.node_signer.get_receive_auth_key(),
1812718129
secp_ctx.clone(),
1812818130
args.message_router,
18131+
false,
1812918132
args.logger.clone(),
1813018133
)
1813118134
.with_async_payments_offers_cache(async_receive_offer_cache);

lightning/src/offers/flow.rs

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,32 @@ use {
7171
crate::onion_message::dns_resolution::{DNSResolverMessage, DNSSECQuery, OMNameResolver},
7272
};
7373

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

122+
pub(crate) enable_events: bool,
123+
96124
#[cfg(not(any(test, feature = "_test_utils")))]
97125
pending_offers_messages: Mutex<Vec<(OffersMessage, MessageSendInstructions)>>,
98126
#[cfg(any(test, feature = "_test_utils"))]
@@ -106,6 +134,8 @@ where
106134
#[cfg(feature = "dnssec")]
107135
pending_dns_onion_messages: Mutex<Vec<(DNSResolverMessage, MessageSendInstructions)>>,
108136

137+
pending_flow_events: Mutex<Vec<OfferMessageFlowEvent>>,
138+
109139
logger: L,
110140
}
111141

@@ -119,7 +149,7 @@ where
119149
chain_hash: ChainHash, best_block: BestBlock, our_network_pubkey: PublicKey,
120150
current_timestamp: u32, inbound_payment_key: inbound_payment::ExpandedKey,
121151
receive_auth_key: ReceiveAuthKey, secp_ctx: Secp256k1<secp256k1::All>, message_router: MR,
122-
logger: L,
152+
enable_events: bool, logger: L,
123153
) -> Self {
124154
Self {
125155
chain_hash,
@@ -134,6 +164,8 @@ where
134164
secp_ctx,
135165
message_router,
136166

167+
enable_events,
168+
137169
pending_offers_messages: Mutex::new(Vec::new()),
138170
pending_async_payments_messages: Mutex::new(Vec::new()),
139171

@@ -144,6 +176,8 @@ where
144176

145177
async_receive_offer_cache: Mutex::new(AsyncReceiveOfferCache::new()),
146178

179+
pending_flow_events: Mutex::new(Vec::new()),
180+
147181
logger,
148182
}
149183
}
@@ -160,6 +194,18 @@ where
160194
self
161195
}
162196

197+
/// Enables [`OfferMessageFlowEvent`] for this flow.
198+
///
199+
/// By default, events are not emitted when processing offers messages. Calling this method
200+
/// sets the internal `enable_events` flag to `true`, allowing you to receive [`OfferMessageFlowEvent`]
201+
/// such as [`OfferMessageFlowEvent::InvoiceRequestReceived`].
202+
///
203+
/// This is useful when you want to manually inspect, handle, or respond to incoming
204+
/// offers messages rather than having them processed automatically.
205+
pub fn enable_events(&mut self) {
206+
self.enable_events = true;
207+
}
208+
163209
/// Sets the [`BlindedMessagePath`]s that we will use as an async recipient to interactively build
164210
/// [`Offer`]s with a static invoice server, so the server can serve [`StaticInvoice`]s to payers
165211
/// on our behalf when we're offline.
@@ -416,6 +462,8 @@ pub enum InvreqResponseInstructions {
416462
/// [`OffersMessageFlow::enqueue_invoice_request_to_forward`].
417463
invoice_request: InvoiceRequest,
418464
},
465+
/// We are recipient of this payment, and should handle the response asynchronously.
466+
AsynchronouslyHandleResponse,
419467
}
420468

421469
/// Parameters for the reply path to a [`HeldHtlcAvailable`] onion message.
@@ -444,6 +492,7 @@ where
444492
L::Target: Logger,
445493
{
446494
/// Verifies an [`InvoiceRequest`] using the provided [`OffersContext`] or the [`InvoiceRequest::metadata`].
495+
/// It also helps determine the response instructions, corresponding to the verified invoice request must be taken.
447496
///
448497
/// - If an [`OffersContext::InvoiceRequest`] with a `nonce` is provided, verification is performed using recipient context data.
449498
/// - If no context is provided but the [`InvoiceRequest`] contains [`Offer`] metadata, verification is performed using that metadata.
@@ -456,6 +505,7 @@ where
456505
/// - The verification process (via recipient context data or metadata) fails.
457506
pub fn verify_invoice_request(
458507
&self, invoice_request: InvoiceRequest, context: Option<OffersContext>,
508+
responder: Responder,
459509
) -> Result<InvreqResponseInstructions, ()> {
460510
let secp_ctx = &self.secp_ctx;
461511
let expanded_key = &self.inbound_payment_key;
@@ -489,7 +539,18 @@ where
489539
None => invoice_request.verify_using_metadata(expanded_key, secp_ctx),
490540
}?;
491541

492-
Ok(InvreqResponseInstructions::SendInvoice(invoice_request))
542+
if self.enable_events {
543+
self.pending_flow_events.lock().unwrap().push(
544+
OfferMessageFlowEvent::InvoiceRequestReceived {
545+
invoice_request,
546+
reply_path: responder.into_blinded_path(),
547+
},
548+
);
549+
550+
Ok(InvreqResponseInstructions::AsynchronouslyHandleResponse)
551+
} else {
552+
Ok(InvreqResponseInstructions::SendInvoice(invoice_request))
553+
}
493554
}
494555

495556
/// Verifies a [`Bolt12Invoice`] using the provided [`OffersContext`] or the invoice's payer metadata,
@@ -1384,6 +1445,11 @@ where
13841445
Ok(())
13851446
}
13861447

1448+
/// Enqueues the generated [`OfferMessageFlowEvent`] to be processed.
1449+
pub fn enqueue_flow_event(&self, flow_event: OfferMessageFlowEvent) {
1450+
self.pending_flow_events.lock().unwrap().push(flow_event);
1451+
}
1452+
13871453
/// Gets the enqueued [`OffersMessage`] with their corresponding [`MessageSendInstructions`].
13881454
pub fn release_pending_offers_messages(&self) -> Vec<(OffersMessage, MessageSendInstructions)> {
13891455
core::mem::take(&mut self.pending_offers_messages.lock().unwrap())
@@ -1396,6 +1462,11 @@ where
13961462
core::mem::take(&mut self.pending_async_payments_messages.lock().unwrap())
13971463
}
13981464

1465+
/// Gets the enqueued [`OfferMessageFlowEvent`] to be processed.
1466+
pub fn release_pending_flow_events(&self) -> Vec<OfferMessageFlowEvent> {
1467+
core::mem::take(&mut self.pending_flow_events.lock().unwrap())
1468+
}
1469+
13991470
/// Gets the enqueued [`DNSResolverMessage`] with their corresponding [`MessageSendInstructions`].
14001471
#[cfg(feature = "dnssec")]
14011472
pub fn release_pending_dns_messages(

0 commit comments

Comments
 (0)