Skip to content

Commit bc95f12

Browse files
committed
Update create_refund_builder to use create_blinded_paths
This change mirrors the previous update to `create_offer_builder`, applying the **“One `MessageRouter`, one `BlindedPath` type”** principle to refund creation. Now, `create_refund_builder` uses the `create_blinded_paths` method of the `MessageRouter` associated with the `ChannelManager` or `OffersMessageFlow`. For non-default path behavior, users can call `create_refund_builder_using_router` and pass a custom `MessageRouter`. See previous commit for detailed reasoning.
1 parent 985676e commit bc95f12

File tree

3 files changed

+156
-32
lines changed

3 files changed

+156
-32
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11013,6 +11013,55 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => {
1101311013

1101411014
Ok(builder.into())
1101511015
}
11016+
11017+
/// Same as [`Self::create_refund_builder`], but allows specifying a custom [`MessageRouter`]
11018+
/// instead of using the one provided during [`ChannelManager`] construction for
11019+
/// [`BlindedMessagePath`] creation.
11020+
///
11021+
/// This gives users full control over how the [`BlindedMessagePath`] is constructed for the
11022+
/// refund, including the option to omit it entirely. This is useful for testing or when
11023+
/// alternative privacy strategies are needed.
11024+
///
11025+
/// See [`Self::create_refund_builder`] for:
11026+
/// - refund recognition by [`ChannelManager`] via [`Bolt12Invoice`] handling,
11027+
/// - `payment_id` rules and expiration behavior,
11028+
/// - invoice revocation and refund failure handling,
11029+
/// - defaulting behavior for `max_total_routing_fee_msat`,
11030+
/// - and detailed payment and privacy semantics.
11031+
///
11032+
/// # Errors
11033+
///
11034+
/// In addition to the errors in [`Self::create_refund_builder`], this returns an error if
11035+
/// the provided [`MessageRouter`] fails to construct a valid [`BlindedMessagePath`] for the refund.
11036+
///
11037+
/// [`Refund`]: crate::offers::refund::Refund
11038+
/// [`BlindedMessagePath`]: crate::blinded_path::message::BlindedMessagePath
11039+
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
11040+
pub fn create_refund_builder_using_router<ME: Deref>(
11041+
&$self, router: ME, amount_msats: u64, absolute_expiry: Duration, payment_id: PaymentId,
11042+
retry_strategy: Retry, route_params_config: RouteParametersConfig
11043+
) -> Result<$builder, Bolt12SemanticError>
11044+
where
11045+
ME::Target: MessageRouter,
11046+
{
11047+
let entropy = &*$self.entropy_source;
11048+
11049+
let builder = $self.flow.create_refund_builder_using_router(
11050+
router, entropy, amount_msats, absolute_expiry,
11051+
payment_id, $self.get_peers_for_blinded_path()
11052+
)?;
11053+
11054+
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop($self);
11055+
11056+
let expiration = StaleExpiration::AbsoluteTimeout(absolute_expiry);
11057+
$self.pending_outbound_payments
11058+
.add_new_awaiting_invoice(
11059+
payment_id, expiration, retry_strategy, route_params_config, None,
11060+
)
11061+
.map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;
11062+
11063+
Ok(builder.into())
11064+
}
1101611065
} }
1101711066

1101811067
impl<

lightning/src/ln/offers_tests.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ fn creates_short_lived_refund() {
476476
#[test]
477477
fn creates_long_lived_refund() {
478478
let chanmon_cfgs = create_chanmon_cfgs(2);
479-
let node_cfgs = create_node_cfgs_with_node_id_message_router(2, &chanmon_cfgs);
479+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
480480
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
481481
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
482482

@@ -488,8 +488,10 @@ fn creates_long_lived_refund() {
488488
let absolute_expiry = bob.node.duration_since_epoch() + MAX_SHORT_LIVED_RELATIVE_EXPIRY
489489
+ Duration::from_secs(1);
490490
let payment_id = PaymentId([1; 32]);
491+
492+
let router = NodeIdMessageRouter::new(bob.network_graph, bob.keys_manager);
491493
let refund = bob.node
492-
.create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), RouteParametersConfig::default())
494+
.create_refund_builder_using_router(&router, 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), RouteParametersConfig::default())
493495
.unwrap()
494496
.build().unwrap();
495497
assert_eq!(refund.absolute_expiry(), Some(absolute_expiry));

lightning/src/offers/flow.rs

Lines changed: 103 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -611,28 +611,71 @@ where
611611
})
612612
}
613613

614+
fn create_refund_builder_intern<ES: Deref, PF, I>(
615+
&self, entropy_source: ES, make_paths: PF, amount_msats: u64, absolute_expiry: Duration,
616+
payment_id: PaymentId,
617+
) -> Result<RefundBuilder<secp256k1::All>, Bolt12SemanticError>
618+
where
619+
ES::Target: EntropySource,
620+
PF: FnOnce(
621+
PublicKey,
622+
MessageContext,
623+
&secp256k1::Secp256k1<secp256k1::All>,
624+
) -> Result<I, Bolt12SemanticError>,
625+
I: IntoIterator<Item = BlindedMessagePath>,
626+
{
627+
let node_id = self.get_our_node_id();
628+
let expanded_key = &self.inbound_payment_key;
629+
let entropy = &*entropy_source;
630+
let secp_ctx = &self.secp_ctx;
631+
632+
let nonce = Nonce::from_entropy_source(entropy);
633+
let context = MessageContext::Offers(OffersContext::OutboundPayment {
634+
payment_id,
635+
nonce,
636+
hmac: None,
637+
});
638+
639+
// Create the base builder with common properties
640+
let mut builder = RefundBuilder::deriving_signing_pubkey(
641+
node_id,
642+
expanded_key,
643+
nonce,
644+
secp_ctx,
645+
amount_msats,
646+
payment_id,
647+
)?
648+
.chain_hash(self.chain_hash)
649+
.absolute_expiry(absolute_expiry);
650+
651+
for path in make_paths(node_id, context, secp_ctx)? {
652+
builder = builder.path(path);
653+
}
654+
655+
Ok(builder.into())
656+
}
657+
614658
/// Creates a [`RefundBuilder`] such that the [`Refund`] it builds is recognized by the
615659
/// [`OffersMessageFlow`], and any corresponding [`Bolt12Invoice`] received for the refund
616660
/// can be verified using [`Self::verify_bolt12_invoice`].
617661
///
662+
/// # Privacy
663+
///
664+
/// Uses the [`OffersMessageFlow`]'s [`MessageRouter`] to construct a [`BlindedMessagePath`]
665+
/// for the offer. See those docs for privacy implications.
666+
///
618667
/// The builder will have the provided expiration set. Any changes to the expiration on the
619668
/// returned builder will not be honored by [`OffersMessageFlow`]. For non-`std`, the highest seen
620669
/// block time minus two hours is used for the current time when determining if the refund has
621670
/// expired.
622671
///
623-
/// To refund can be revoked by the user prior to receiving the invoice.
672+
/// The refund can be revoked by the user prior to receiving the invoice.
624673
/// If abandoned, or if an invoice is not received before expiration, the payment will fail
625674
/// with an [`Event::PaymentFailed`].
626675
///
627676
/// If `max_total_routing_fee_msat` is not specified, the default from
628677
/// [`RouteParameters::from_payment_params_and_value`] is applied.
629678
///
630-
/// # Privacy
631-
///
632-
/// Uses [`MessageRouter`] to construct a [`BlindedMessagePath`] for the refund based on the given
633-
/// `absolute_expiry` according to [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. See those docs for
634-
/// privacy implications.
635-
///
636679
/// Also uses a derived payer id in the refund for payer privacy.
637680
///
638681
/// # Errors
@@ -651,32 +694,62 @@ where
651694
where
652695
ES::Target: EntropySource,
653696
{
654-
let node_id = self.get_our_node_id();
655-
let expanded_key = &self.inbound_payment_key;
656-
let entropy = &*entropy_source;
657-
let secp_ctx = &self.secp_ctx;
658-
659-
let nonce = Nonce::from_entropy_source(entropy);
660-
let context = OffersContext::OutboundPayment { payment_id, nonce, hmac: None };
661-
662-
let path = self
663-
.create_blinded_paths_using_absolute_expiry(context, Some(absolute_expiry), peers)
664-
.and_then(|paths| paths.into_iter().next().ok_or(()))
665-
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
666-
667-
let builder = RefundBuilder::deriving_signing_pubkey(
668-
node_id,
669-
expanded_key,
670-
nonce,
671-
secp_ctx,
697+
self.create_refund_builder_intern(
698+
&*entropy_source,
699+
|_, context, _| {
700+
self.create_blinded_paths(peers, context)
701+
.map(|paths| paths.into_iter().take(1))
702+
.map_err(|_| Bolt12SemanticError::MissingPaths)
703+
},
672704
amount_msats,
705+
absolute_expiry,
673706
payment_id,
674-
)?
675-
.chain_hash(self.chain_hash)
676-
.absolute_expiry(absolute_expiry)
677-
.path(path);
707+
)
708+
}
678709

679-
Ok(builder)
710+
/// Same as [`Self::create_refund_builder`] but allows specifying a custom [`MessageRouter`]
711+
/// instead of using the one provided via the [`OffersMessageFlow`] parameterization.
712+
///
713+
/// This gives users full control over how the [`BlindedMessagePath`] is constructed,
714+
/// including the option to omit it entirely.
715+
///
716+
/// See [`Self::create_refund_builder`] for:
717+
/// - how the resulting [`Refund`] is recognized by [`OffersMessageFlow`] and verified via [`Self::verify_bolt12_invoice`],
718+
/// - refund expiration handling,
719+
/// - rules around revocation and [`Event::PaymentFailed`] behavior,
720+
/// - and defaulting logic for `max_total_routing_fee_msat`.
721+
///
722+
/// # Errors
723+
///
724+
/// In addition to the errors documented in [`Self::create_refund_builder`], this method will
725+
/// return an error if the provided [`MessageRouter`] fails to construct a valid
726+
/// [`BlindedMessagePath`] for the refund.
727+
///
728+
/// [`Refund`]: crate::offers::refund::Refund
729+
/// [`BlindedMessagePath`]: crate::blinded_path::message::BlindedMessagePath
730+
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
731+
/// [`Event::PaymentFailed`]: crate::events::Event::PaymentFailed
732+
/// [`RouteParameters::from_payment_params_and_value`]: crate::routing::router::RouteParameters::from_payment_params_and_value
733+
pub fn create_refund_builder_using_router<ES: Deref, ME: Deref>(
734+
&self, router: ME, entropy_source: ES, amount_msats: u64, absolute_expiry: Duration,
735+
payment_id: PaymentId, peers: Vec<MessageForwardNode>,
736+
) -> Result<RefundBuilder<secp256k1::All>, Bolt12SemanticError>
737+
where
738+
ME::Target: MessageRouter,
739+
ES::Target: EntropySource,
740+
{
741+
self.create_refund_builder_intern(
742+
&*entropy_source,
743+
|node_id, context, secp_ctx| {
744+
router
745+
.create_blinded_paths(node_id, context, peers, secp_ctx)
746+
.map(|paths| paths.into_iter().take(1))
747+
.map_err(|_| Bolt12SemanticError::MissingPaths)
748+
},
749+
amount_msats,
750+
absolute_expiry,
751+
payment_id,
752+
)
680753
}
681754

682755
/// Creates an [`InvoiceRequestBuilder`] such that the [`InvoiceRequest`] it builds is recognized

0 commit comments

Comments
 (0)