Skip to content

Commit 6851001

Browse files
committed
Introduce specific InvoiceBuilders in OffersMessageFlow
This change improves type safety and architectural clarity by introducing dedicated `InvoiceBuilder` methods tied to each variant of `VerifiedInvoiceRequestEnum`. With this change, users are now required to match on the enum variant before calling the corresponding builder method. This pushes the responsibility of selecting the correct builder to the user and ensures that invalid builder usage is caught at compile time, rather than relying on runtime checks. The signing logic has also been moved from the builder to the `ChannelManager`. This shift simplifies the builder's role and aligns it with the rest of the API, where builder methods return a configurable object that can be extended before signing. The result is a more consistent and predictable interface that separates concerns cleanly and makes future maintenance easier.
1 parent eeb5d24 commit 6851001

File tree

2 files changed

+178
-85
lines changed

2 files changed

+178
-85
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 75 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,11 @@ use crate::ln::types::ChannelId;
9393
use crate::offers::async_receive_offer_cache::AsyncReceiveOfferCache;
9494
use crate::offers::flow::{HeldHtlcReplyPath, InvreqResponseInstructions, OffersMessageFlow};
9595
use crate::offers::invoice::{
96-
Bolt12Invoice, DerivedSigningPubkey, InvoiceBuilder, DEFAULT_RELATIVE_EXPIRY,
96+
Bolt12Invoice, DerivedSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice,
97+
DEFAULT_RELATIVE_EXPIRY,
9798
};
9899
use crate::offers::invoice_error::InvoiceError;
99-
use crate::offers::invoice_request::InvoiceRequest;
100+
use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestVerifiedFromOffer};
100101
use crate::offers::nonce::Nonce;
101102
use crate::offers::offer::{Offer, OfferFromHrn};
102103
use crate::offers::parse::Bolt12SemanticError;
@@ -14893,16 +14894,79 @@ where
1489314894
},
1489414895
};
1489514896

14896-
let entropy = &*self.entropy_source;
14897-
let (response, context) = self.flow.create_response_for_invoice_request(
14898-
&self.node_signer, &self.router, entropy, invoice_request, amount_msats,
14899-
payment_hash, payment_secret, self.list_usable_channels()
14900-
);
14897+
let (result, context) = match invoice_request {
14898+
InvoiceRequestVerifiedFromOffer::DerivedKeys(request) => {
14899+
let result = self.flow.create_invoice_builder_from_invoice_request_with_keys(
14900+
&self.router,
14901+
&*self.entropy_source,
14902+
&request,
14903+
amount_msats,
14904+
payment_hash,
14905+
payment_secret,
14906+
self.list_usable_channels(),
14907+
);
1490114908

14902-
match context {
14903-
Some(context) => Some((response, responder.respond_with_reply_path(context))),
14904-
None => Some((response, responder.respond()))
14905-
}
14909+
match result {
14910+
Ok((builder, context)) => {
14911+
let res = builder
14912+
.build_and_sign(&self.secp_ctx)
14913+
.map_err(InvoiceError::from);
14914+
14915+
(res, context)
14916+
},
14917+
Err(error) => {
14918+
return Some((
14919+
OffersMessage::InvoiceError(InvoiceError::from(error)),
14920+
responder.respond(),
14921+
));
14922+
},
14923+
}
14924+
},
14925+
InvoiceRequestVerifiedFromOffer::ExplicitKeys(request) => {
14926+
let result = self.flow.create_invoice_builder_from_invoice_request_without_keys(
14927+
&self.router,
14928+
&*self.entropy_source,
14929+
&request,
14930+
amount_msats,
14931+
payment_hash,
14932+
payment_secret,
14933+
self.list_usable_channels(),
14934+
);
14935+
14936+
match result {
14937+
Ok((builder, context)) => {
14938+
let res = builder
14939+
.build()
14940+
.map_err(InvoiceError::from)
14941+
.and_then(|invoice| {
14942+
#[cfg(c_bindings)]
14943+
let mut invoice = invoice;
14944+
invoice
14945+
.sign(|invoice: &UnsignedBolt12Invoice| self.node_signer.sign_bolt12_invoice(invoice))
14946+
.map_err(InvoiceError::from)
14947+
});
14948+
(res, context)
14949+
},
14950+
Err(error) => {
14951+
return Some((
14952+
OffersMessage::InvoiceError(InvoiceError::from(error)),
14953+
responder.respond(),
14954+
));
14955+
},
14956+
}
14957+
}
14958+
};
14959+
14960+
Some(match result {
14961+
Ok(invoice) => (
14962+
OffersMessage::Invoice(invoice),
14963+
responder.respond_with_reply_path(context),
14964+
),
14965+
Err(error) => (
14966+
OffersMessage::InvoiceError(error),
14967+
responder.respond(),
14968+
),
14969+
})
1490614970
},
1490714971
OffersMessage::Invoice(invoice) => {
1490814972
let payment_id = match self.flow.verify_bolt12_invoice(&invoice, context.as_ref()) {

lightning/src/offers/flow.rs

Lines changed: 103 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,16 @@ use crate::ln::inbound_payment;
3737
use crate::offers::async_receive_offer_cache::AsyncReceiveOfferCache;
3838
use crate::offers::invoice::{
3939
Bolt12Invoice, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder,
40-
UnsignedBolt12Invoice, DEFAULT_RELATIVE_EXPIRY,
40+
DEFAULT_RELATIVE_EXPIRY,
4141
};
42-
use crate::offers::invoice_error::InvoiceError;
4342
use crate::offers::invoice_request::{
44-
InvoiceRequest, InvoiceRequestBuilder, InvoiceRequestVerifiedFromOffer,
43+
InvoiceRequest, InvoiceRequestBuilder, InvoiceRequestVerifiedFromOffer, VerifiedInvoiceRequest,
4544
};
4645
use crate::offers::nonce::Nonce;
4746
use crate::offers::offer::{Amount, DerivedMetadata, Offer, OfferBuilder};
4847
use crate::offers::parse::Bolt12SemanticError;
4948
use crate::offers::refund::{Refund, RefundBuilder};
49+
use crate::offers::static_invoice::{StaticInvoice, StaticInvoiceBuilder};
5050
use crate::onion_message::async_payments::{
5151
AsyncPaymentsMessage, HeldHtlcAvailable, OfferPaths, OfferPathsRequest, ServeStaticInvoice,
5252
StaticInvoicePersisted,
@@ -57,9 +57,7 @@ use crate::onion_message::messenger::{
5757
use crate::onion_message::offers::OffersMessage;
5858
use crate::onion_message::packet::OnionMessageContents;
5959
use crate::routing::router::Router;
60-
use crate::sign::{EntropySource, NodeSigner, ReceiveAuthKey};
61-
62-
use crate::offers::static_invoice::{StaticInvoice, StaticInvoiceBuilder};
60+
use crate::sign::{EntropySource, ReceiveAuthKey};
6361
use crate::sync::{Mutex, RwLock};
6462
use crate::types::payment::{PaymentHash, PaymentSecret};
6563
use crate::util::logger::Logger;
@@ -939,95 +937,124 @@ where
939937
Ok(builder.into())
940938
}
941939

942-
/// Creates a response for the provided [`InvoiceRequestVerifiedFromOffer`].
940+
/// Creates an [`InvoiceBuilder<DerivedSigningPubkey>`] for the
941+
/// provided [`VerifiedInvoiceRequest<DerivedSigningPubkey>`].
942+
///
943+
/// Returns the invoice builder along with a [`MessageContext`] that can
944+
/// later be used to respond to the counterparty.
945+
///
946+
/// Use this method when you want to inspect or modify the [`InvoiceBuilder`]
947+
/// before signing and generating the final [`Bolt12Invoice`].
943948
///
944-
/// A response can be either an [`OffersMessage::Invoice`] with additional [`MessageContext`],
945-
/// or an [`OffersMessage::InvoiceError`], depending on the [`InvoiceRequest`].
949+
/// # Errors
946950
///
947-
/// An [`OffersMessage::InvoiceError`] will be generated if:
948-
/// - We fail to generate valid payment paths to include in the [`Bolt12Invoice`].
949-
/// - We fail to generate a valid signed [`Bolt12Invoice`] for the [`InvoiceRequest`].
950-
pub fn create_response_for_invoice_request<ES: Deref, NS: Deref, R: Deref>(
951-
&self, signer: &NS, router: &R, entropy_source: ES,
952-
invoice_request: InvoiceRequestVerifiedFromOffer, amount_msats: u64,
951+
/// Returns a [`Bolt12SemanticError`] if:
952+
/// - Valid blinded payment paths could not be generated for the [`Bolt12Invoice`].
953+
/// - The [`InvoiceBuilder`] could not be created from the [`InvoiceRequest`].
954+
pub fn create_invoice_builder_from_invoice_request_with_keys<'a, ES: Deref, R: Deref>(
955+
&self, router: &R, entropy_source: ES,
956+
invoice_request: &'a VerifiedInvoiceRequest<DerivedSigningPubkey>, amount_msats: u64,
953957
payment_hash: PaymentHash, payment_secret: PaymentSecret,
954958
usable_channels: Vec<ChannelDetails>,
955-
) -> (OffersMessage, Option<MessageContext>)
959+
) -> Result<(InvoiceBuilder<'a, DerivedSigningPubkey>, MessageContext), Bolt12SemanticError>
956960
where
957961
ES::Target: EntropySource,
958-
NS::Target: NodeSigner,
962+
959963
R::Target: Router,
960964
{
961965
let entropy = &*entropy_source;
962-
let secp_ctx = &self.secp_ctx;
966+
let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
967+
968+
let context = PaymentContext::Bolt12Offer(Bolt12OfferContext {
969+
offer_id: invoice_request.offer_id,
970+
invoice_request: invoice_request.fields(),
971+
});
972+
973+
let payment_paths = self
974+
.create_blinded_payment_paths(
975+
router,
976+
entropy,
977+
usable_channels,
978+
Some(amount_msats),
979+
payment_secret,
980+
context,
981+
relative_expiry,
982+
)
983+
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
984+
985+
#[cfg(feature = "std")]
986+
let builder = invoice_request.respond_using_derived_keys(payment_paths, payment_hash);
987+
#[cfg(not(feature = "std"))]
988+
let builder = invoice_request.respond_using_derived_keys_no_std(
989+
payment_paths,
990+
payment_hash,
991+
Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64),
992+
);
993+
let builder = builder.map(|b| InvoiceBuilder::from(b).allow_mpp())?;
963994

995+
let context = MessageContext::Offers(OffersContext::InboundPayment { payment_hash });
996+
997+
Ok((builder, context))
998+
}
999+
1000+
/// Creates an [`InvoiceBuilder<ExplicitSigningPubkey>`] for the
1001+
/// provided [`VerifiedInvoiceRequest<ExplicitSigningPubkey>`].
1002+
///
1003+
/// Returns the invoice builder along with a [`MessageContext`] that can
1004+
/// later be used to respond to the counterparty.
1005+
///
1006+
/// Use this method when you want to inspect or modify the [`InvoiceBuilder`]
1007+
/// before signing and generating the final [`Bolt12Invoice`].
1008+
///
1009+
/// # Errors
1010+
///
1011+
/// Returns a [`Bolt12SemanticError`] if:
1012+
/// - Valid blinded payment paths could not be generated for the [`Bolt12Invoice`].
1013+
/// - The [`InvoiceBuilder`] could not be created from the [`InvoiceRequest`].
1014+
pub fn create_invoice_builder_from_invoice_request_without_keys<'a, ES: Deref, R: Deref>(
1015+
&self, router: &R, entropy_source: ES,
1016+
invoice_request: &'a VerifiedInvoiceRequest<ExplicitSigningPubkey>, amount_msats: u64,
1017+
payment_hash: PaymentHash, payment_secret: PaymentSecret,
1018+
usable_channels: Vec<ChannelDetails>,
1019+
) -> Result<(InvoiceBuilder<'a, ExplicitSigningPubkey>, MessageContext), Bolt12SemanticError>
1020+
where
1021+
ES::Target: EntropySource,
1022+
R::Target: Router,
1023+
{
1024+
let entropy = &*entropy_source;
9641025
let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
9651026

9661027
let context = PaymentContext::Bolt12Offer(Bolt12OfferContext {
967-
offer_id: invoice_request.offer_id(),
1028+
offer_id: invoice_request.offer_id,
9681029
invoice_request: invoice_request.fields(),
9691030
});
9701031

971-
let payment_paths = match self.create_blinded_payment_paths(
972-
router,
973-
entropy,
974-
usable_channels,
975-
Some(amount_msats),
976-
payment_secret,
977-
context,
978-
relative_expiry,
979-
) {
980-
Ok(paths) => paths,
981-
Err(_) => {
982-
let error = InvoiceError::from(Bolt12SemanticError::MissingPaths);
983-
return (OffersMessage::InvoiceError(error.into()), None);
984-
},
985-
};
1032+
let payment_paths = self
1033+
.create_blinded_payment_paths(
1034+
router,
1035+
entropy,
1036+
usable_channels,
1037+
Some(amount_msats),
1038+
payment_secret,
1039+
context,
1040+
relative_expiry,
1041+
)
1042+
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
9861043

1044+
#[cfg(feature = "std")]
1045+
let builder = invoice_request.respond_with(payment_paths, payment_hash);
9871046
#[cfg(not(feature = "std"))]
988-
let created_at = Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64);
1047+
let builder = invoice_request.respond_with_no_std(
1048+
payment_paths,
1049+
payment_hash,
1050+
Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64),
1051+
);
9891052

990-
let response = match invoice_request {
991-
InvoiceRequestVerifiedFromOffer::DerivedKeys(request) => {
992-
#[cfg(feature = "std")]
993-
let builder = request.respond_using_derived_keys(payment_paths, payment_hash);
994-
#[cfg(not(feature = "std"))]
995-
let builder = request.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at);
996-
builder
997-
.map(InvoiceBuilder::<DerivedSigningPubkey>::from)
998-
.and_then(|builder| builder.allow_mpp().build_and_sign(secp_ctx))
999-
.map_err(InvoiceError::from)
1000-
},
1001-
InvoiceRequestVerifiedFromOffer::ExplicitKeys(request) => {
1002-
#[cfg(feature = "std")]
1003-
let builder = request.respond_with(payment_paths, payment_hash);
1004-
#[cfg(not(feature = "std"))]
1005-
let builder = request.respond_with_no_std(payment_paths, payment_hash, created_at);
1006-
builder
1007-
.map(InvoiceBuilder::<ExplicitSigningPubkey>::from)
1008-
.and_then(|builder| builder.allow_mpp().build())
1009-
.map_err(InvoiceError::from)
1010-
.and_then(|invoice| {
1011-
#[cfg(c_bindings)]
1012-
let mut invoice = invoice;
1013-
invoice
1014-
.sign(|invoice: &UnsignedBolt12Invoice| {
1015-
signer.sign_bolt12_invoice(invoice)
1016-
})
1017-
.map_err(InvoiceError::from)
1018-
})
1019-
},
1020-
};
1053+
let builder = builder.map(|b| InvoiceBuilder::from(b).allow_mpp())?;
10211054

1022-
match response {
1023-
Ok(invoice) => {
1024-
let context =
1025-
MessageContext::Offers(OffersContext::InboundPayment { payment_hash });
1055+
let context = MessageContext::Offers(OffersContext::InboundPayment { payment_hash });
10261056

1027-
(OffersMessage::Invoice(invoice), Some(context))
1028-
},
1029-
Err(error) => (OffersMessage::InvoiceError(error.into()), None),
1030-
}
1057+
Ok((builder, context))
10311058
}
10321059

10331060
/// Enqueues the created [`InvoiceRequest`] to be sent to the counterparty.
@@ -1054,6 +1081,7 @@ where
10541081
/// valid reply paths for the counterparty to send back the corresponding [`Bolt12Invoice`]
10551082
/// or [`InvoiceError`].
10561083
///
1084+
/// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError
10571085
/// [`supports_onion_messages`]: crate::types::features::Features::supports_onion_messages
10581086
pub fn enqueue_invoice_request(
10591087
&self, invoice_request: InvoiceRequest, payment_id: PaymentId, nonce: Nonce,
@@ -1099,6 +1127,7 @@ where
10991127
/// reply paths for the counterparty to send back the corresponding [`InvoiceError`] if we fail
11001128
/// to create blinded reply paths
11011129
///
1130+
/// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError
11021131
/// [`supports_onion_messages`]: crate::types::features::Features::supports_onion_messages
11031132
pub fn enqueue_invoice(
11041133
&self, invoice: Bolt12Invoice, refund: &Refund, peers: Vec<MessageForwardNode>,

0 commit comments

Comments
 (0)