Skip to content

Commit e099b79

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 6a6a6c4 commit e099b79

File tree

2 files changed

+181
-82
lines changed

2 files changed

+181
-82
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ use crate::ln::types::ChannelId;
9292
use crate::offers::async_receive_offer_cache::AsyncReceiveOfferCache;
9393
use crate::offers::flow::{InvreqResponseInstructions, OffersMessageFlow};
9494
use crate::offers::invoice::{
95-
Bolt12Invoice, DerivedSigningPubkey, InvoiceBuilder, DEFAULT_RELATIVE_EXPIRY,
95+
Bolt12Invoice, DerivedSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice,
96+
DEFAULT_RELATIVE_EXPIRY,
9697
};
9798
use crate::offers::invoice_error::InvoiceError;
9899
use crate::offers::invoice_request::InvoiceRequest;
@@ -14112,16 +14113,79 @@ where
1411214113
},
1411314114
};
1411414115

14115-
let entropy = &*self.entropy_source;
14116-
let (response, context) = self.flow.create_response_for_invoice_request(
14117-
&self.node_signer, &self.router, entropy, invoice_request, amount_msats,
14118-
payment_hash, payment_secret, self.list_usable_channels()
14119-
);
14116+
let (result, context) = match invoice_request {
14117+
VerifiedInvoiceRequestEnum::WithKeys(request) => {
14118+
let result = self.flow.create_invoice_builder_from_invoice_request_with_keys(
14119+
&self.router,
14120+
&*self.entropy_source,
14121+
&request,
14122+
amount_msats,
14123+
payment_hash,
14124+
payment_secret,
14125+
self.list_usable_channels(),
14126+
);
1412014127

14121-
match context {
14122-
Some(context) => Some((response, responder.respond_with_reply_path(context))),
14123-
None => Some((response, responder.respond()))
14124-
}
14128+
match result {
14129+
Ok((builder, context)) => {
14130+
let res = builder
14131+
.build_and_sign(&self.secp_ctx)
14132+
.map_err(InvoiceError::from);
14133+
14134+
(res, context)
14135+
},
14136+
Err(error) => {
14137+
return Some((
14138+
OffersMessage::InvoiceError(InvoiceError::from(error)),
14139+
responder.respond(),
14140+
));
14141+
},
14142+
}
14143+
},
14144+
VerifiedInvoiceRequestEnum::WithoutKeys(request) => {
14145+
let result = self.flow.create_invoice_builder_from_invoice_request_without_keys(
14146+
&self.router,
14147+
&*self.entropy_source,
14148+
&request,
14149+
amount_msats,
14150+
payment_hash,
14151+
payment_secret,
14152+
self.list_usable_channels(),
14153+
);
14154+
14155+
match result {
14156+
Ok((builder, context)) => {
14157+
let res = builder.
14158+
build()
14159+
.map_err(InvoiceError::from)
14160+
.and_then(|invoice| {
14161+
#[cfg(c_bindings)]
14162+
let mut invoice = invoice;
14163+
invoice
14164+
.sign(|invoice: &UnsignedBolt12Invoice| self.node_signer.sign_bolt12_invoice(invoice))
14165+
.map_err(InvoiceError::from)
14166+
});
14167+
(res, context)
14168+
},
14169+
Err(error) => {
14170+
return Some((
14171+
OffersMessage::InvoiceError(InvoiceError::from(error)),
14172+
responder.respond(),
14173+
));
14174+
},
14175+
}
14176+
}
14177+
};
14178+
14179+
Some(match result {
14180+
Ok(invoice) => (
14181+
OffersMessage::Invoice(invoice),
14182+
responder.respond_with_reply_path(context),
14183+
),
14184+
Err(error) => (
14185+
OffersMessage::InvoiceError(error),
14186+
responder.respond(),
14187+
),
14188+
})
1412514189
},
1412614190
OffersMessage::Invoice(invoice) => {
1412714191
let payment_id = match self.flow.verify_bolt12_invoice(&invoice, context.as_ref()) {

lightning/src/offers/flow.rs

Lines changed: 107 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,11 @@ 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,
41+
};
42+
use crate::offers::invoice_request::{
43+
InvoiceRequest, InvoiceRequestBuilder, InvoiceSigningInfo, VerifiedInvoiceRequest,
4144
};
42-
use crate::offers::invoice_error::InvoiceError;
43-
use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestBuilder, InvoiceSigningInfo};
4445
use crate::offers::nonce::Nonce;
4546
use crate::offers::offer::{DerivedMetadata, Offer, OfferBuilder};
4647
use crate::offers::parse::Bolt12SemanticError;
@@ -50,7 +51,7 @@ use crate::onion_message::messenger::{Destination, MessageRouter, MessageSendIns
5051
use crate::onion_message::offers::OffersMessage;
5152
use crate::onion_message::packet::OnionMessageContents;
5253
use crate::routing::router::Router;
53-
use crate::sign::{EntropySource, NodeSigner, ReceiveAuthKey};
54+
use crate::sign::{EntropySource, ReceiveAuthKey};
5455
use crate::sync::{Mutex, RwLock};
5556
use crate::types::payment::{PaymentHash, PaymentSecret};
5657
use crate::util::ser::Writeable;
@@ -918,94 +919,126 @@ where
918919
Ok(builder.into())
919920
}
920921

921-
/// Creates a response for the provided [`InvoiceSigningInfo`].
922+
/// Creates an [`InvoiceBuilder`] with [`DerivedSigningPubkey`] for the
923+
/// provided [`VerifiedInvoiceRequest<DerivedSigningPubkey>`].
924+
///
925+
/// Returns the invoice builder along with a [`MessageContext`] that can
926+
/// later be used to respond to the counterparty.
922927
///
923-
/// A response can be either an [`OffersMessage::Invoice`] with additional [`MessageContext`],
924-
/// or an [`OffersMessage::InvoiceError`], depending on the [`InvoiceRequest`].
928+
/// Use this method when you want to inspect or modify the [`InvoiceBuilder`]
929+
/// before signing and generating the final [`Bolt12Invoice`].
925930
///
926-
/// An [`OffersMessage::InvoiceError`] will be generated if:
927-
/// - We fail to generate valid payment paths to include in the [`Bolt12Invoice`].
928-
/// - We fail to generate a valid signed [`Bolt12Invoice`] for the [`InvoiceRequest`].
929-
pub fn create_response_for_invoice_request<ES: Deref, NS: Deref, R: Deref>(
930-
&self, signer: &NS, router: &R, entropy_source: ES, invoice_request: InvoiceSigningInfo,
931-
amount_msats: u64, payment_hash: PaymentHash, payment_secret: PaymentSecret,
931+
/// # Errors
932+
///
933+
/// Returns a [`Bolt12SemanticError`] if:
934+
/// - User call the function with [`VerifiedInvoiceRequest<ExplicitSigningPubkey>`].
935+
/// - Valid blinded payment paths could not be generated for the [`Bolt12Invoice`].
936+
/// - The [`InvoiceBuilder`] could not be created from the [`InvoiceRequest`].
937+
pub fn create_invoice_builder_from_invoice_request_with_keys<'a, ES: Deref, R: Deref>(
938+
&'a self, router: &R, entropy_source: ES,
939+
invoice_request: &'a VerifiedInvoiceRequest<DerivedSigningPubkey>, amount_msats: u64,
940+
payment_hash: PaymentHash, payment_secret: PaymentSecret,
932941
usable_channels: Vec<ChannelDetails>,
933-
) -> (OffersMessage, Option<MessageContext>)
942+
) -> Result<(InvoiceBuilder<'a, DerivedSigningPubkey>, MessageContext), Bolt12SemanticError>
934943
where
935944
ES::Target: EntropySource,
936-
NS::Target: NodeSigner,
945+
937946
R::Target: Router,
938947
{
939948
let entropy = &*entropy_source;
940-
let secp_ctx = &self.secp_ctx;
949+
let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
941950

951+
let context = PaymentContext::Bolt12Offer(Bolt12OfferContext {
952+
offer_id: invoice_request.offer_id,
953+
invoice_request: invoice_request.fields(),
954+
});
955+
956+
let payment_paths = self
957+
.create_blinded_payment_paths(
958+
router,
959+
entropy,
960+
usable_channels,
961+
Some(amount_msats),
962+
payment_secret,
963+
context,
964+
relative_expiry,
965+
)
966+
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
967+
968+
#[cfg(feature = "std")]
969+
let builder = invoice_request.respond_using_derived_keys(payment_paths, payment_hash);
970+
#[cfg(not(feature = "std"))]
971+
let builder = invoice_request.respond_using_derived_keys_no_std(
972+
payment_paths,
973+
payment_hash,
974+
Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64),
975+
);
976+
let builder = builder.map(|b| InvoiceBuilder::from(b).allow_mpp())?;
977+
978+
let context = MessageContext::Offers(OffersContext::InboundPayment { payment_hash });
979+
980+
Ok((builder, context))
981+
}
982+
983+
/// Creates an [`InvoiceBuilder`] with [`ExplicitSigningPubkey`] for the
984+
/// provided [`VerifiedInvoiceRequest<ExplicitSigningPubkey>`].
985+
///
986+
/// Returns the invoice builder along with a [`MessageContext`] that can
987+
/// later be used to respond to the counterparty.
988+
///
989+
/// Use this method when you want to inspect or modify the [`InvoiceBuilder`]
990+
/// before signing and generating the final [`Bolt12Invoice`].
991+
///
992+
/// # Errors
993+
///
994+
/// Returns a [`Bolt12SemanticError`] if:
995+
/// - User call the function with [`VerifiedInvoiceRequest<DerivedSigningPubkey>`].
996+
/// - Valid blinded payment paths could not be generated for the [`Bolt12Invoice`].
997+
/// - The [`InvoiceBuilder`] could not be created from the [`InvoiceRequest`].
998+
pub fn create_invoice_builder_from_invoice_request_without_keys<'a, ES: Deref, R: Deref>(
999+
&'a self, router: &R, entropy_source: ES,
1000+
invoice_request: &'a VerifiedInvoiceRequest<ExplicitSigningPubkey>, amount_msats: u64,
1001+
payment_hash: PaymentHash, payment_secret: PaymentSecret,
1002+
usable_channels: Vec<ChannelDetails>,
1003+
) -> Result<(InvoiceBuilder<'a, ExplicitSigningPubkey>, MessageContext), Bolt12SemanticError>
1004+
where
1005+
ES::Target: EntropySource,
1006+
R::Target: Router,
1007+
{
1008+
let entropy = &*entropy_source;
9421009
let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
9431010

9441011
let context = PaymentContext::Bolt12Offer(Bolt12OfferContext {
945-
offer_id: invoice_request.offer_id(),
1012+
offer_id: invoice_request.offer_id,
9461013
invoice_request: invoice_request.fields(),
9471014
});
9481015

949-
let payment_paths = match self.create_blinded_payment_paths(
950-
router,
951-
entropy,
952-
usable_channels,
953-
Some(amount_msats),
954-
payment_secret,
955-
context,
956-
relative_expiry,
957-
) {
958-
Ok(paths) => paths,
959-
Err(_) => {
960-
let error = InvoiceError::from(Bolt12SemanticError::MissingPaths);
961-
return (OffersMessage::InvoiceError(error.into()), None);
962-
},
963-
};
1016+
let payment_paths = self
1017+
.create_blinded_payment_paths(
1018+
router,
1019+
entropy,
1020+
usable_channels,
1021+
Some(amount_msats),
1022+
payment_secret,
1023+
context,
1024+
relative_expiry,
1025+
)
1026+
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
9641027

1028+
#[cfg(feature = "std")]
1029+
let builder = invoice_request.respond_with(payment_paths, payment_hash);
9651030
#[cfg(not(feature = "std"))]
966-
let created_at = Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64);
1031+
let builder = invoice_request.respond_with_no_std(
1032+
payment_paths,
1033+
payment_hash,
1034+
Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64),
1035+
);
9671036

968-
let response = match invoice_request {
969-
InvoiceSigningInfo::DerivedKeys(request) => {
970-
#[cfg(feature = "std")]
971-
let builder = request.respond_using_derived_keys(payment_paths, payment_hash);
972-
#[cfg(not(feature = "std"))]
973-
let builder = request.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at);
974-
builder
975-
.map(InvoiceBuilder::<DerivedSigningPubkey>::from)
976-
.and_then(|builder| builder.allow_mpp().build_and_sign(secp_ctx))
977-
.map_err(InvoiceError::from)
978-
},
979-
InvoiceSigningInfo::ExplicitKeys(request) => {
980-
#[cfg(feature = "std")]
981-
let builder = request.respond_with(payment_paths, payment_hash);
982-
#[cfg(not(feature = "std"))]
983-
let builder = request.respond_with_no_std(payment_paths, payment_hash, created_at);
984-
builder
985-
.map(InvoiceBuilder::<ExplicitSigningPubkey>::from)
986-
.and_then(|builder| builder.allow_mpp().build())
987-
.map_err(InvoiceError::from)
988-
.and_then(|invoice| {
989-
#[cfg(c_bindings)]
990-
let mut invoice = invoice;
991-
invoice
992-
.sign(|invoice: &UnsignedBolt12Invoice| {
993-
signer.sign_bolt12_invoice(invoice)
994-
})
995-
.map_err(InvoiceError::from)
996-
})
997-
},
998-
};
1037+
let builder = builder.map(|b| InvoiceBuilder::from(b).allow_mpp())?;
9991038

1000-
match response {
1001-
Ok(invoice) => {
1002-
let context =
1003-
MessageContext::Offers(OffersContext::InboundPayment { payment_hash });
1039+
let context = MessageContext::Offers(OffersContext::InboundPayment { payment_hash });
10041040

1005-
(OffersMessage::Invoice(invoice), Some(context))
1006-
},
1007-
Err(error) => (OffersMessage::InvoiceError(error.into()), None),
1008-
}
1041+
Ok((builder, context))
10091042
}
10101043

10111044
/// Enqueues the created [`InvoiceRequest`] to be sent to the counterparty.
@@ -1032,6 +1065,7 @@ where
10321065
/// valid reply paths for the counterparty to send back the corresponding [`Bolt12Invoice`]
10331066
/// or [`InvoiceError`].
10341067
///
1068+
/// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError
10351069
/// [`supports_onion_messages`]: crate::types::features::Features::supports_onion_messages
10361070
pub fn enqueue_invoice_request(
10371071
&self, invoice_request: InvoiceRequest, payment_id: PaymentId, nonce: Nonce,
@@ -1077,6 +1111,7 @@ where
10771111
/// reply paths for the counterparty to send back the corresponding [`InvoiceError`] if we fail
10781112
/// to create blinded reply paths
10791113
///
1114+
/// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError
10801115
/// [`supports_onion_messages`]: crate::types::features::Features::supports_onion_messages
10811116
pub fn enqueue_invoice(
10821117
&self, invoice: Bolt12Invoice, refund: &Refund, peers: Vec<MessageForwardNode>,

0 commit comments

Comments
 (0)