Skip to content

Commit aa94c32

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 7146763 commit aa94c32

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
@@ -3878,7 +3878,8 @@ where
38783878
let flow = OffersMessageFlow::new(
38793879
ChainHash::using_genesis_block(params.network), params.best_block,
38803880
our_network_pubkey, current_timestamp, expanded_inbound_key,
3881-
node_signer.get_receive_auth_key(), secp_ctx.clone(), message_router, logger.clone(),
3881+
node_signer.get_receive_auth_key(), secp_ctx.clone(), message_router, false,
3882+
logger.clone(),
38823883
);
38833884

38843885
ChannelManager {
@@ -14703,7 +14704,7 @@ where
1470314704
None => return None,
1470414705
};
1470514706

14706-
let invoice_request = match self.flow.verify_invoice_request(invoice_request, context) {
14707+
let invoice_request = match self.flow.verify_invoice_request(invoice_request, context, responder.clone()) {
1470714708
Ok(InvreqResponseInstructions::SendInvoice(invoice_request)) => invoice_request,
1470814709
Ok(InvreqResponseInstructions::SendStaticInvoice { recipient_id, invoice_slot, invoice_request }) => {
1470914710
self.pending_events.lock().unwrap().push_back((Event::StaticInvoiceRequested {
@@ -14712,6 +14713,7 @@ where
1471214713

1471314714
return None
1471414715
},
14716+
Ok(InvreqResponseInstructions::AsynchronouslyHandleResponse) => return None,
1471514717
Err(_) => return None,
1471614718
};
1471714719

@@ -17456,6 +17458,7 @@ where
1745617458
args.node_signer.get_receive_auth_key(),
1745717459
secp_ctx.clone(),
1745817460
args.message_router,
17461+
false,
1745917462
args.logger.clone(),
1746017463
)
1746117464
.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.
@@ -421,6 +467,8 @@ pub enum InvreqResponseInstructions {
421467
/// [`OffersMessageFlow::enqueue_invoice_request_to_forward`].
422468
invoice_request: InvoiceRequest,
423469
},
470+
/// We are recipient of this payment, and should handle the response asynchronously.
471+
AsynchronouslyHandleResponse,
424472
}
425473

426474
impl<MR: Deref, L: Deref> OffersMessageFlow<MR, L>
@@ -429,6 +477,7 @@ where
429477
L::Target: Logger,
430478
{
431479
/// Verifies an [`InvoiceRequest`] using the provided [`OffersContext`] or the [`InvoiceRequest::metadata`].
480+
/// It also helps determine the response instructions, corresponding to the verified invoice request must be taken.
432481
///
433482
/// - If an [`OffersContext::InvoiceRequest`] with a `nonce` is provided, verification is performed using recipient context data.
434483
/// - If no context is provided but the [`InvoiceRequest`] contains [`Offer`] metadata, verification is performed using that metadata.
@@ -441,6 +490,7 @@ where
441490
/// - The verification process (via recipient context data or metadata) fails.
442491
pub fn verify_invoice_request(
443492
&self, invoice_request: InvoiceRequest, context: Option<OffersContext>,
493+
responder: Responder,
444494
) -> Result<InvreqResponseInstructions, ()> {
445495
let secp_ctx = &self.secp_ctx;
446496
let expanded_key = &self.inbound_payment_key;
@@ -474,7 +524,18 @@ where
474524
None => invoice_request.verify_using_metadata(expanded_key, secp_ctx),
475525
}?;
476526

477-
Ok(InvreqResponseInstructions::SendInvoice(invoice_request))
527+
if self.enable_events {
528+
self.pending_flow_events.lock().unwrap().push(
529+
OfferMessageFlowEvent::InvoiceRequestReceived {
530+
invoice_request,
531+
reply_path: responder.into_blinded_path(),
532+
},
533+
);
534+
535+
Ok(InvreqResponseInstructions::AsynchronouslyHandleResponse)
536+
} else {
537+
Ok(InvreqResponseInstructions::SendInvoice(invoice_request))
538+
}
478539
}
479540

480541
/// Verifies a [`Bolt12Invoice`] using the provided [`OffersContext`] or the invoice's payer metadata,
@@ -1352,6 +1413,11 @@ where
13521413
Ok(())
13531414
}
13541415

1416+
/// Enqueues the generated [`OfferMessageFlowEvent`] to be processed.
1417+
pub fn enqueue_flow_event(&self, flow_event: OfferMessageFlowEvent) {
1418+
self.pending_flow_events.lock().unwrap().push(flow_event);
1419+
}
1420+
13551421
/// Gets the enqueued [`OffersMessage`] with their corresponding [`MessageSendInstructions`].
13561422
pub fn release_pending_offers_messages(&self) -> Vec<(OffersMessage, MessageSendInstructions)> {
13571423
core::mem::take(&mut self.pending_offers_messages.lock().unwrap())
@@ -1364,6 +1430,11 @@ where
13641430
core::mem::take(&mut self.pending_async_payments_messages.lock().unwrap())
13651431
}
13661432

1433+
/// Gets the enqueued [`OfferMessageFlowEvent`] to be processed.
1434+
pub fn release_pending_flow_events(&self) -> Vec<OfferMessageFlowEvent> {
1435+
core::mem::take(&mut self.pending_flow_events.lock().unwrap())
1436+
}
1437+
13671438
/// Gets the enqueued [`DNSResolverMessage`] with their corresponding [`MessageSendInstructions`].
13681439
#[cfg(feature = "dnssec")]
13691440
pub fn release_pending_dns_messages(

0 commit comments

Comments
 (0)