Skip to content

Commit 1660024

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 570600b commit 1660024

File tree

2 files changed

+178
-82
lines changed

2 files changed

+178
-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: 104 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,10 @@ 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::{DerivedMetadata, Offer, OfferBuilder};
@@ -52,7 +51,7 @@ use crate::onion_message::messenger::{Destination, MessageRouter, MessageSendIns
5251
use crate::onion_message::offers::OffersMessage;
5352
use crate::onion_message::packet::OnionMessageContents;
5453
use crate::routing::router::Router;
55-
use crate::sign::{EntropySource, NodeSigner, ReceiveAuthKey};
54+
use crate::sign::{EntropySource, ReceiveAuthKey};
5655
use crate::sync::{Mutex, RwLock};
5756
use crate::types::payment::{PaymentHash, PaymentSecret};
5857
use crate::util::ser::Writeable;
@@ -920,95 +919,126 @@ where
920919
Ok(builder.into())
921920
}
922921

923-
/// Creates a response for the provided [`InvoiceRequestVerifiedFromOffer`].
922+
/// Creates an [`InvoiceBuilder`] with [`DerivedSigningPubkey`] for the
923+
/// provided [`VerifiedInvoiceRequest<DerivedSigningPubkey>`].
924924
///
925-
/// A response can be either an [`OffersMessage::Invoice`] with additional [`MessageContext`],
926-
/// or an [`OffersMessage::InvoiceError`], depending on the [`InvoiceRequest`].
925+
/// Returns the invoice builder along with a [`MessageContext`] that can
926+
/// later be used to respond to the counterparty.
927927
///
928-
/// An [`OffersMessage::InvoiceError`] will be generated if:
929-
/// - We fail to generate valid payment paths to include in the [`Bolt12Invoice`].
930-
/// - We fail to generate a valid signed [`Bolt12Invoice`] for the [`InvoiceRequest`].
931-
pub fn create_response_for_invoice_request<ES: Deref, NS: Deref, R: Deref>(
932-
&self, signer: &NS, router: &R, entropy_source: ES,
933-
invoice_request: InvoiceRequestVerifiedFromOffer, amount_msats: u64,
928+
/// Use this method when you want to inspect or modify the [`InvoiceBuilder`]
929+
/// before signing and generating the final [`Bolt12Invoice`].
930+
///
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,
934940
payment_hash: PaymentHash, payment_secret: PaymentSecret,
935941
usable_channels: Vec<ChannelDetails>,
936-
) -> (OffersMessage, Option<MessageContext>)
942+
) -> Result<(InvoiceBuilder<'a, DerivedSigningPubkey>, MessageContext), Bolt12SemanticError>
937943
where
938944
ES::Target: EntropySource,
939-
NS::Target: NodeSigner,
945+
940946
R::Target: Router,
941947
{
942948
let entropy = &*entropy_source;
943-
let secp_ctx = &self.secp_ctx;
949+
let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
950+
951+
let context = PaymentContext::Bolt12Offer(Bolt12OfferContext {
952+
offer_id: invoice_request.offer_id,
953+
invoice_request: invoice_request.fields(),
954+
});
944955

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;
9451009
let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
9461010

9471011
let context = PaymentContext::Bolt12Offer(Bolt12OfferContext {
948-
offer_id: invoice_request.offer_id(),
1012+
offer_id: invoice_request.offer_id,
9491013
invoice_request: invoice_request.fields(),
9501014
});
9511015

952-
let payment_paths = match self.create_blinded_payment_paths(
953-
router,
954-
entropy,
955-
usable_channels,
956-
Some(amount_msats),
957-
payment_secret,
958-
context,
959-
relative_expiry,
960-
) {
961-
Ok(paths) => paths,
962-
Err(_) => {
963-
let error = InvoiceError::from(Bolt12SemanticError::MissingPaths);
964-
return (OffersMessage::InvoiceError(error.into()), None);
965-
},
966-
};
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)?;
9671027

1028+
#[cfg(feature = "std")]
1029+
let builder = invoice_request.respond_with(payment_paths, payment_hash);
9681030
#[cfg(not(feature = "std"))]
969-
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+
);
9701036

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

1003-
match response {
1004-
Ok(invoice) => {
1005-
let context =
1006-
MessageContext::Offers(OffersContext::InboundPayment { payment_hash });
1039+
let context = MessageContext::Offers(OffersContext::InboundPayment { payment_hash });
10071040

1008-
(OffersMessage::Invoice(invoice), Some(context))
1009-
},
1010-
Err(error) => (OffersMessage::InvoiceError(error.into()), None),
1011-
}
1041+
Ok((builder, context))
10121042
}
10131043

10141044
/// Enqueues the created [`InvoiceRequest`] to be sent to the counterparty.
@@ -1035,6 +1065,7 @@ where
10351065
/// valid reply paths for the counterparty to send back the corresponding [`Bolt12Invoice`]
10361066
/// or [`InvoiceError`].
10371067
///
1068+
/// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError
10381069
/// [`supports_onion_messages`]: crate::types::features::Features::supports_onion_messages
10391070
pub fn enqueue_invoice_request(
10401071
&self, invoice_request: InvoiceRequest, payment_id: PaymentId, nonce: Nonce,
@@ -1080,6 +1111,7 @@ where
10801111
/// reply paths for the counterparty to send back the corresponding [`InvoiceError`] if we fail
10811112
/// to create blinded reply paths
10821113
///
1114+
/// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError
10831115
/// [`supports_onion_messages`]: crate::types::features::Features::supports_onion_messages
10841116
pub fn enqueue_invoice(
10851117
&self, invoice: Bolt12Invoice, refund: &Refund, peers: Vec<MessageForwardNode>,

0 commit comments

Comments
 (0)