Skip to content

Introduce Event Model for Offers Flow #3833

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
135 changes: 119 additions & 16 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,10 @@ use crate::ln::outbound_payment::{
SendAlongPathArgs, StaleExpiration,
};
use crate::ln::types::ChannelId;
use crate::offers::flow::OffersMessageFlow;
use crate::offers::flow::{FlowConfigs, OffersMessageFlow};
use crate::offers::invoice::{
Bolt12Invoice, DerivedSigningPubkey, InvoiceBuilder, DEFAULT_RELATIVE_EXPIRY,
Bolt12Invoice, DerivedSigningPubkey, InvoiceBuilder, InvoiceBuilderVariant,
UnsignedBolt12Invoice, DEFAULT_RELATIVE_EXPIRY,
};
use crate::offers::invoice_error::InvoiceError;
use crate::offers::invoice_request::InvoiceRequest;
Expand Down Expand Up @@ -155,7 +156,6 @@ use {
};
#[cfg(not(c_bindings))]
use {
crate::offers::offer::{DerivedMetadata, OfferBuilder},
crate::offers::refund::RefundBuilder,
crate::onion_message::messenger::DefaultMessageRouter,
crate::routing::gossip::NetworkGraph,
Expand All @@ -164,6 +164,9 @@ use {
crate::sign::KeysManager,
};

#[cfg(any(not(c_bindings), async_payments))]
use crate::offers::offer::{DerivedMetadata, OfferBuilder};

use lightning_invoice::{
Bolt11Invoice, Bolt11InvoiceDescription, CreationError, Currency, Description,
InvoiceBuilder as Bolt11InvoiceBuilder, SignOrCreationError, DEFAULT_EXPIRY_TIME,
Expand Down Expand Up @@ -3702,9 +3705,51 @@ where
let flow = OffersMessageFlow::new(
ChainHash::using_genesis_block(params.network), params.best_block,
our_network_pubkey, current_timestamp, expanded_inbound_key,
secp_ctx.clone(), message_router
secp_ctx.clone(), message_router, FlowConfigs::new()
);

Self::new_inner(
secp_ctx, fee_est, chain_monitor, tx_broadcaster, router, flow,
logger, entropy_source, node_signer, signer_provider, config, params,
current_timestamp
)

}

/// Similar to [`ChannelManager::new`], but allows providing a custom [`OffersMessageFlow`] implementation.
///
/// This is useful if you want more control over how BOLT12 offer-related messages are handled,
/// including support for custom [`FlowConfigs`] to conditionally trigger [`OfferEvents`] that you
/// can handle asynchronously via your own logic.
///
/// Use this method when:
/// - You want to initialize [`ChannelManager`] with a non-default [`OffersMessageFlow`] implementation.
/// - You need fine-grained control over BOLT12 event generation or message flow behavior.
///
/// [`FlowConfigs`]: crate::offers::flow::FlowConfigs
/// [`OfferEvents`]: crate::offers::flow::OfferEvents
#[rustfmt::skip]
pub fn new_with_flow(
fee_est: F, chain_monitor: M, tx_broadcaster: T, router: R, flow: OffersMessageFlow<MR>,
logger: L, entropy_source: ES, node_signer: NS, signer_provider: SP, config: UserConfig,
params: ChainParameters, current_timestamp: u32,
) -> Self {
let mut secp_ctx = Secp256k1::new();
secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes());

Self::new_inner(
secp_ctx, fee_est, chain_monitor, tx_broadcaster, router, flow,
logger, entropy_source, node_signer, signer_provider, config, params,
current_timestamp
)
}

#[rustfmt::skip]
fn new_inner(
secp_ctx: Secp256k1<secp256k1::All>, fee_est: F, chain_monitor: M, tx_broadcaster: T, router: R,
flow: OffersMessageFlow<MR>, logger: L, entropy_source: ES, node_signer: NS,
signer_provider: SP, config: UserConfig, params: ChainParameters, current_timestamp: u32
) -> Self {
ChannelManager {
default_configuration: config.clone(),
chain_hash: ChainHash::using_genesis_block(params.network),
Expand All @@ -3724,10 +3769,10 @@ where
pending_intercepted_htlcs: Mutex::new(new_hash_map()),
short_to_chan_info: FairRwLock::new(new_hash_map()),

our_network_pubkey,
our_network_pubkey: node_signer.get_node_id(Recipient::Node).unwrap(),
secp_ctx,

inbound_payment_key: expanded_inbound_key,
inbound_payment_key: node_signer.get_inbound_payment_key(),
fake_scid_rand_bytes: entropy_source.get_secure_random_bytes(),

probing_cookie_secret: entropy_source.get_secure_random_bytes(),
Expand Down Expand Up @@ -12680,8 +12725,17 @@ where
Err(_) => return None,
};

let invoice_request = match self.flow.determine_invoice_request_handling(invoice_request) {
Ok(Some(ir)) => ir,
Ok(None) => return None,
Err(_) => {
log_trace!(self.logger, "Failed to handle invoice request");
return None;
}
};
Comment on lines +12728 to +12735
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... so the user may want to handle this asynchronously because they just need to verify that an amount (or supply that is) is sufficient for the offer's currency denomination. In that case, we should really have some function that continues the code below (i.e. creating a payment hash / secret, building the invoice, and enqueuing it to be sent).

Another reason is they want to supply their own payment hash. Similarly, they would need to build the invoice and enqueue it for sending. They may be even want to customize the invoice in some other ways using the builder.

I'm not quite sure how we want to do this. For the first case, it seems we should make it easy for them, which means they would need to call something on ChannelManager to continue the flow. Whereas, the second case is more about calling methods on OffersMessageFlow. But it would be weird for the former since the events' docs would need to reference ChannelManager.


let amount_msats = match InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(
&invoice_request.inner
&invoice_request.inner, None
) {
Ok(amount_msats) => amount_msats,
Err(error) => return Some((OffersMessage::InvoiceError(error.into()), responder.respond())),
Expand All @@ -12699,15 +12753,55 @@ where
};

let entropy = &*self.entropy_source;
let (response, context) = self.flow.create_response_for_invoice_request(
&self.node_signer, &self.router, entropy, invoice_request, amount_msats,
payment_hash, payment_secret, self.list_usable_channels()
);

match context {
Some(context) => Some((response, responder.respond_with_reply_path(context))),
None => Some((response, responder.respond()))
}
let (builder_var, context) = match self.flow.create_invoice_builder_from_invoice_request(
&self.router,
entropy,
&invoice_request,
amount_msats,
payment_hash,
payment_secret,
self.list_usable_channels(),
) {
Ok(result) => result,
Err(error) => {
return Some((
OffersMessage::InvoiceError(InvoiceError::from(error)),
responder.respond(),
));
}
};

let result = match builder_var {
InvoiceBuilderVariant::Derived(builder) => {
builder
.build_and_sign(&self.secp_ctx)
.map_err(InvoiceError::from)
},
InvoiceBuilderVariant::Explicit(builder) => {
builder
.build()
.map_err(InvoiceError::from)
.and_then(|invoice| {
#[cfg(c_bindings)]
let mut invoice = invoice;
invoice
.sign(|invoice: &UnsignedBolt12Invoice| self.node_signer.sign_bolt12_invoice(invoice))
.map_err(InvoiceError::from)
})
}
};

Some(match result {
Ok(invoice) => (
OffersMessage::Invoice(invoice),
responder.respond_with_reply_path(context),
),
Err(error) => (
OffersMessage::InvoiceError(error),
responder.respond(),
),
})
},
OffersMessage::Invoice(invoice) => {
let payment_id = match self.flow.verify_bolt12_invoice(&invoice, context.as_ref()) {
Expand All @@ -12719,6 +12813,15 @@ where
&self.logger, None, None, Some(invoice.payment_hash()),
);

let invoice = match self.flow.determine_invoice_handling(invoice, payment_id) {
Ok(Some(invoice)) => invoice,
Ok(None) => return None,
Err(_) => {
log_trace!(logger, "Failed to handle invoice");
return None
},
};

if self.default_configuration.manually_handle_bolt12_invoices {
// Update the corresponding entry in `PendingOutboundPayment` for this invoice.
// This ensures that event generation remains idempotent in case we receive
Expand Down Expand Up @@ -14924,7 +15027,7 @@ where
let flow = OffersMessageFlow::new(
chain_hash, best_block, our_network_pubkey,
highest_seen_timestamp, expanded_inbound_key,
secp_ctx.clone(), args.message_router
secp_ctx.clone(), args.message_router, FlowConfigs::new(),
);

let channel_manager = ChannelManager {
Expand Down
2 changes: 1 addition & 1 deletion lightning/src/ln/offers_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2231,7 +2231,7 @@ fn fails_paying_invoice_with_unknown_required_features() {
let created_at = alice.node.duration_since_epoch();
let invoice = invoice_request
.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).unwrap()
.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at).unwrap()
.respond_using_derived_keys_no_std(None, payment_paths, payment_hash, created_at).unwrap()
.features_unchecked(Bolt12InvoiceFeatures::unknown())
.build_and_sign(&secp_ctx).unwrap();

Expand Down
8 changes: 4 additions & 4 deletions lightning/src/ln/outbound_payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1114,7 +1114,7 @@ impl OutboundPayments {
return Err(Bolt12PaymentError::SendingFailed(RetryableSendFailure::PaymentExpired))
}

let amount_msat = match InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(invreq) {
let amount_msat = match InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(invreq, None) {
Ok(amt) => amt,
Err(_) => {
// We check this during invoice request parsing, when constructing the invreq's
Expand Down Expand Up @@ -2975,7 +2975,7 @@ mod tests {
.build().unwrap()
.request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build_and_sign().unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), created_at).unwrap()
.respond_with_no_std(None, payment_paths(), payment_hash(), created_at).unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();

Expand Down Expand Up @@ -3021,7 +3021,7 @@ mod tests {
.build().unwrap()
.request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build_and_sign().unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.respond_with_no_std(None, payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();

Expand Down Expand Up @@ -3083,7 +3083,7 @@ mod tests {
.build().unwrap()
.request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
.build_and_sign().unwrap()
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
.respond_with_no_std(None, payment_paths(), payment_hash(), now()).unwrap()
.build().unwrap()
.sign(recipient_sign).unwrap();

Expand Down
Loading
Loading