Skip to content

Commit 55382db

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 13d21ca commit 55382db

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
@@ -3985,7 +3985,8 @@ where
39853985
let flow = OffersMessageFlow::new(
39863986
ChainHash::using_genesis_block(params.network), params.best_block,
39873987
our_network_pubkey, current_timestamp, expanded_inbound_key,
3988-
node_signer.get_receive_auth_key(), secp_ctx.clone(), message_router, logger.clone(),
3988+
node_signer.get_receive_auth_key(), secp_ctx.clone(), message_router, false,
3989+
logger.clone(),
39893990
);
39903991

39913992
ChannelManager {
@@ -15271,7 +15272,7 @@ where
1527115272
None => return None,
1527215273
};
1527315274

15274-
let invoice_request = match self.flow.verify_invoice_request(invoice_request, context) {
15275+
let invoice_request = match self.flow.verify_invoice_request(invoice_request, context, responder.clone()) {
1527515276
Ok(InvreqResponseInstructions::SendInvoice(invoice_request)) => invoice_request,
1527615277
Ok(InvreqResponseInstructions::SendStaticInvoice { recipient_id, invoice_slot, invoice_request }) => {
1527715278
self.pending_events.lock().unwrap().push_back((Event::StaticInvoiceRequested {
@@ -15280,6 +15281,7 @@ where
1528015281

1528115282
return None
1528215283
},
15284+
Ok(InvreqResponseInstructions::AsynchronouslyHandleResponse) => return None,
1528315285
Err(_) => return None,
1528415286
};
1528515287

@@ -18081,6 +18083,7 @@ where
1808118083
args.node_signer.get_receive_auth_key(),
1808218084
secp_ctx.clone(),
1808318085
args.message_router,
18086+
false,
1808418087
args.logger.clone(),
1808518088
)
1808618089
.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
/// Parameters for the reply path to a [`HeldHtlcAvailable`] onion message.
@@ -449,6 +497,7 @@ where
449497
L::Target: Logger,
450498
{
451499
/// Verifies an [`InvoiceRequest`] using the provided [`OffersContext`] or the [`InvoiceRequest::metadata`].
500+
/// It also helps determine the response instructions, corresponding to the verified invoice request must be taken.
452501
///
453502
/// - If an [`OffersContext::InvoiceRequest`] with a `nonce` is provided, verification is performed using recipient context data.
454503
/// - If no context is provided but the [`InvoiceRequest`] contains [`Offer`] metadata, verification is performed using that metadata.
@@ -461,6 +510,7 @@ where
461510
/// - The verification process (via recipient context data or metadata) fails.
462511
pub fn verify_invoice_request(
463512
&self, invoice_request: InvoiceRequest, context: Option<OffersContext>,
513+
responder: Responder,
464514
) -> Result<InvreqResponseInstructions, ()> {
465515
let secp_ctx = &self.secp_ctx;
466516
let expanded_key = &self.inbound_payment_key;
@@ -494,7 +544,18 @@ where
494544
None => invoice_request.verify_using_metadata(expanded_key, secp_ctx),
495545
}?;
496546

497-
Ok(InvreqResponseInstructions::SendInvoice(invoice_request))
547+
if self.enable_events {
548+
self.pending_flow_events.lock().unwrap().push(
549+
OfferMessageFlowEvent::InvoiceRequestReceived {
550+
invoice_request,
551+
reply_path: responder.into_blinded_path(),
552+
},
553+
);
554+
555+
Ok(InvreqResponseInstructions::AsynchronouslyHandleResponse)
556+
} else {
557+
Ok(InvreqResponseInstructions::SendInvoice(invoice_request))
558+
}
498559
}
499560

500561
/// Verifies a [`Bolt12Invoice`] using the provided [`OffersContext`] or the invoice's payer metadata,
@@ -1401,6 +1462,11 @@ where
14011462
Ok(())
14021463
}
14031464

1465+
/// Enqueues the generated [`OfferMessageFlowEvent`] to be processed.
1466+
pub fn enqueue_flow_event(&self, flow_event: OfferMessageFlowEvent) {
1467+
self.pending_flow_events.lock().unwrap().push(flow_event);
1468+
}
1469+
14041470
/// Gets the enqueued [`OffersMessage`] with their corresponding [`MessageSendInstructions`].
14051471
pub fn release_pending_offers_messages(&self) -> Vec<(OffersMessage, MessageSendInstructions)> {
14061472
core::mem::take(&mut self.pending_offers_messages.lock().unwrap())
@@ -1413,6 +1479,11 @@ where
14131479
core::mem::take(&mut self.pending_async_payments_messages.lock().unwrap())
14141480
}
14151481

1482+
/// Gets the enqueued [`OfferMessageFlowEvent`] to be processed.
1483+
pub fn release_pending_flow_events(&self) -> Vec<OfferMessageFlowEvent> {
1484+
core::mem::take(&mut self.pending_flow_events.lock().unwrap())
1485+
}
1486+
14161487
/// Gets the enqueued [`DNSResolverMessage`] with their corresponding [`MessageSendInstructions`].
14171488
#[cfg(feature = "dnssec")]
14181489
pub fn release_pending_dns_messages(

0 commit comments

Comments
 (0)