@@ -25,8 +25,7 @@ use bitcoin::{Amount, Txid};
2525use bitcoin_payment_instructions:: amount:: Amount as BPIAmount ;
2626use bitcoin_payment_instructions:: { PaymentInstructions , PaymentMethod } ;
2727use lightning:: ln:: channelmanager:: PaymentId ;
28- use lightning:: offers:: offer:: Offer ;
29- use lightning:: onion_message:: dns_resolution:: HumanReadableName ;
28+ use lightning:: offers:: offer:: Offer as LdkOffer ;
3029use lightning:: routing:: router:: RouteParametersConfig ;
3130use lightning_invoice:: { Bolt11Invoice , Bolt11InvoiceDescription , Description } ;
3231
@@ -40,6 +39,16 @@ use crate::Config;
4039
4140type Uri < ' a > = bip21:: Uri < ' a , NetworkChecked , Extras > ;
4241
42+ #[ cfg( not( feature = "uniffi" ) ) ]
43+ type HumanReadableName = lightning:: onion_message:: dns_resolution:: HumanReadableName ;
44+ #[ cfg( feature = "uniffi" ) ]
45+ type HumanReadableName = crate :: ffi:: HumanReadableName ;
46+
47+ #[ cfg( not( feature = "uniffi" ) ) ]
48+ type Offer = LdkOffer ;
49+ #[ cfg( feature = "uniffi" ) ]
50+ type Offer = Arc < crate :: ffi:: Offer > ;
51+
4352#[ derive( Debug , Clone ) ]
4453struct Extras {
4554 bolt11_invoice : Option < Bolt11Invoice > ,
@@ -66,6 +75,8 @@ pub struct UnifiedPayment {
6675 config : Arc < Config > ,
6776 logger : Arc < Logger > ,
6877 hrn_resolver : Arc < HRNResolver > ,
78+ #[ cfg( hrn_tests) ]
79+ test_offer : std:: sync:: Mutex < Option < Offer > > ,
6980}
7081
7182impl UnifiedPayment {
@@ -74,7 +85,16 @@ impl UnifiedPayment {
7485 bolt12_payment : Arc < Bolt12Payment > , config : Arc < Config > , logger : Arc < Logger > ,
7586 hrn_resolver : Arc < HRNResolver > ,
7687 ) -> Self {
77- Self { onchain_payment, bolt11_invoice, bolt12_payment, config, logger, hrn_resolver }
88+ Self {
89+ onchain_payment,
90+ bolt11_invoice,
91+ bolt12_payment,
92+ config,
93+ logger,
94+ hrn_resolver,
95+ #[ cfg( hrn_tests) ]
96+ test_offer : std:: sync:: Mutex :: new ( None ) ,
97+ }
7898 }
7999}
80100
@@ -115,7 +135,7 @@ impl UnifiedPayment {
115135
116136 let bolt12_offer =
117137 match self . bolt12_payment . receive_inner ( amount_msats, description, None , None ) {
118- Ok ( offer) => Some ( offer) ,
138+ Ok ( offer) => Some ( maybe_wrap ( offer) ) ,
119139 Err ( e) => {
120140 log_error ! ( self . logger, "Failed to create offer: {}" , e) ;
121141 None
@@ -165,12 +185,19 @@ impl UnifiedPayment {
165185 & self , uri_str : & str , amount_msat : Option < u64 > ,
166186 route_parameters : Option < RouteParametersConfig > ,
167187 ) -> Result < UnifiedPaymentResult , Error > {
168- let parse_fut = PaymentInstructions :: parse (
169- uri_str,
170- self . config . network ,
171- self . hrn_resolver . as_ref ( ) ,
172- false ,
173- ) ;
188+ let target_network;
189+
190+ #[ cfg( hrn_tests) ]
191+ {
192+ target_network = bitcoin:: Network :: Bitcoin ;
193+ }
194+ #[ cfg( not( hrn_tests) ) ]
195+ {
196+ target_network = self . config . network ;
197+ }
198+
199+ let parse_fut =
200+ PaymentInstructions :: parse ( uri_str, target_network, self . hrn_resolver . as_ref ( ) , false ) ;
174201
175202 let instructions =
176203 tokio:: time:: timeout ( Duration :: from_secs ( HRN_RESOLUTION_TIMEOUT_SECS ) , parse_fut)
@@ -233,8 +260,30 @@ impl UnifiedPayment {
233260
234261 for method in sorted_payment_methods {
235262 match method {
236- PaymentMethod :: LightningBolt12 ( offer) => {
237- let offer = maybe_wrap ( offer. clone ( ) ) ;
263+ PaymentMethod :: LightningBolt12 ( _offer) => {
264+ #[ cfg( not( hrn_tests) ) ]
265+ let offer = maybe_wrap ( _offer. clone ( ) ) ;
266+
267+ #[ cfg( hrn_tests) ]
268+ // We inject a test-only offer here because full DNSSEC validation is
269+ // currently infeasible in regtest environments. This allows us to
270+ // bypass the validation requirements that would otherwise fail
271+ // without a functional global DNSSEC root in the test runner.
272+ let offer = {
273+ let test_offer_guard = self . test_offer . lock ( ) . map_err ( |e| {
274+ log_error ! (
275+ self . logger,
276+ "Failed to lock test_offer due to poisoning: {:?}" ,
277+ e
278+ ) ;
279+ Error :: PaymentSendingFailed
280+ } ) ?;
281+
282+ match & * test_offer_guard {
283+ Some ( o) => o. clone ( ) ,
284+ None => maybe_wrap ( _offer. clone ( ) ) ,
285+ }
286+ } ;
238287
239288 let payment_result = if let Ok ( hrn) = HumanReadableName :: from_encoded ( uri_str) {
240289 let hrn = maybe_wrap ( hrn. clone ( ) ) ;
@@ -288,6 +337,21 @@ impl UnifiedPayment {
288337 log_error ! ( self . logger, "Payable methods not found in URI" ) ;
289338 Err ( Error :: PaymentSendingFailed )
290339 }
340+
341+ /// Sets a test offer to be used in the `send` method when the `hrn_tests` config flag is enabled.
342+ ///
343+ /// This is necessary for Bolt12 payments in HRN tests because we typically resolve offers
344+ /// via [BIP 353] DNS addresses. Since full DNSSEC validation is infeasible in regtest
345+ /// environments, the automated resolution of an offer from a URI will fail. Injected
346+ /// offers allow us to bypass this resolution step and test the subsequent payment flow.
347+ ///
348+ /// [BIP 353]: https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki
349+ #[ cfg( hrn_tests) ]
350+ pub fn set_test_offer ( & self , _offer : Offer ) {
351+ let _ = self . test_offer . lock ( ) . map ( |mut guard| * guard = Some ( _offer) ) . map_err ( |e| {
352+ log_error ! ( self . logger, "Failed to set test offer due to poisoned lock: {:?}" , e)
353+ } ) ;
354+ }
291355}
292356
293357/// Represents the result of a payment made using a [BIP 21] URI or a [BIP 353] Human-Readable Name.
@@ -395,9 +459,10 @@ impl<'a> bip21::de::DeserializationState<'a> for DeserializationState {
395459 "lno" => {
396460 let bolt12_value =
397461 String :: try_from ( value) . map_err ( |_| Error :: UriParameterParsingFailed ) ?;
398- let offer =
399- bolt12_value. parse :: < Offer > ( ) . map_err ( |_| Error :: UriParameterParsingFailed ) ?;
400- self . bolt12_offer = Some ( offer) ;
462+ let offer = bolt12_value
463+ . parse :: < LdkOffer > ( )
464+ . map_err ( |_| Error :: UriParameterParsingFailed ) ?;
465+ self . bolt12_offer = Some ( maybe_wrap ( offer) ) ;
401466 Ok ( bip21:: de:: ParamKind :: Known )
402467 } ,
403468 _ => Ok ( bip21:: de:: ParamKind :: Unknown ) ,
@@ -420,7 +485,7 @@ mod tests {
420485 use bitcoin:: address:: NetworkUnchecked ;
421486 use bitcoin:: { Address , Network } ;
422487
423- use super :: { Amount , Bolt11Invoice , Extras , Offer } ;
488+ use super :: { maybe_wrap , Amount , Bolt11Invoice , Extras , LdkOffer } ;
424489
425490 #[ test]
426491 fn parse_uri ( ) {
@@ -474,7 +539,7 @@ mod tests {
474539 }
475540
476541 if let Some ( offer) = parsed_uri_with_offer. extras . bolt12_offer {
477- assert_eq ! ( offer, Offer :: from_str( expected_bolt12_offer_2) . unwrap( ) ) ;
542+ assert_eq ! ( offer, maybe_wrap ( LdkOffer :: from_str( expected_bolt12_offer_2) . unwrap( ) ) ) ;
478543 } else {
479544 panic ! ( "No offer found." ) ;
480545 }
0 commit comments