Skip to content

Commit 4b5a6cd

Browse files
committed
Add end-to-end test for HRN resolution
This commit adds an end-to-end test that asserts that HRNs are properly parsed and resolved, and sending to the corresponding offer works
1 parent 4c1a03a commit 4b5a6cd

File tree

6 files changed

+201
-34
lines changed

6 files changed

+201
-34
lines changed

src/ffi/types.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ impl std::fmt::Display for Offer {
270270
}
271271
}
272272

273+
#[derive(Eq, Hash, PartialEq)]
273274
pub struct HumanReadableName {
274275
pub(crate) inner: LdkHumanReadableName,
275276
}

src/lib.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1631,3 +1631,57 @@ pub(crate) fn total_anchor_channels_reserve_sats(
16311631
* anchor_channels_config.per_channel_reserve_sats
16321632
})
16331633
}
1634+
1635+
/// Testing utils for DNSSEC proof resolution of offers associated with the given HRN.
1636+
pub mod dnssec_testing_utils {
1637+
use lightning::offers::offer::Offer;
1638+
1639+
use std::collections::HashMap;
1640+
#[cfg(feature = "uniffi")]
1641+
use std::sync::Arc;
1642+
use std::sync::{LazyLock, Mutex};
1643+
1644+
#[cfg(not(feature = "uniffi"))]
1645+
type HumanReadableName = lightning::onion_message::dns_resolution::HumanReadableName;
1646+
#[cfg(feature = "uniffi")]
1647+
type HumanReadableName = Arc<crate::ffi::HumanReadableName>;
1648+
1649+
static OFFER_OVERRIDE_MAP: LazyLock<Mutex<HashMap<HumanReadableName, Offer>>> =
1650+
LazyLock::new(|| Mutex::new(HashMap::new()));
1651+
1652+
/// Sets a testing override for DNSSEC proof resolution of offers associated with the given HRN.
1653+
pub fn set_testing_dnssec_proof_offer_resolution_override(hrn: &str, offer: Offer) {
1654+
let hrn_key = {
1655+
#[cfg(not(feature = "uniffi"))]
1656+
{
1657+
lightning::onion_message::dns_resolution::HumanReadableName::from_encoded(hrn)
1658+
.unwrap()
1659+
}
1660+
1661+
#[cfg(feature = "uniffi")]
1662+
{
1663+
Arc::new(crate::ffi::HumanReadableName::from_encoded(hrn).unwrap())
1664+
}
1665+
};
1666+
1667+
OFFER_OVERRIDE_MAP.lock().unwrap().insert(hrn_key, offer);
1668+
}
1669+
1670+
/// Retrieves a testing override for DNSSEC proof resolution of offers associated with the given HRNs.
1671+
#[cfg(not(feature = "uniffi"))]
1672+
pub fn get_testing_offer_override(hrn: Option<HumanReadableName>) -> Option<Offer> {
1673+
OFFER_OVERRIDE_MAP.lock().unwrap().get(&hrn?).cloned()
1674+
}
1675+
1676+
/// Retrieves a testing override for DNSSEC proof resolution of offers associated with the given HRNs.
1677+
#[cfg(feature = "uniffi")]
1678+
pub fn get_testing_offer_override(hrn: Option<HumanReadableName>) -> Option<Offer> {
1679+
let offer = OFFER_OVERRIDE_MAP.lock().unwrap().get(&hrn?).cloned().unwrap();
1680+
Some(offer)
1681+
}
1682+
1683+
/// Clears all testing overrides for DNSSEC proof resolution of offers.
1684+
pub fn clear_testing_overrides() {
1685+
OFFER_OVERRIDE_MAP.lock().unwrap().clear();
1686+
}
1687+
}

src/payment/bolt12.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,22 @@ impl Bolt12Payment {
194194
return Err(Error::NotRunning);
195195
}
196196

197-
let offer = maybe_deref(offer);
197+
let hrn2 = if let Some(ref hrn) = hrn {
198+
hrn
199+
} else {
200+
return Err(Error::HrnParsingFailed);
201+
};
202+
203+
let offer =
204+
match crate::dnssec_testing_utils::get_testing_offer_override(Some(hrn2.clone())) {
205+
Some(offer) => {
206+
log_info!(self.logger, "Using test-specific Offer override.");
207+
maybe_wrap(offer)
208+
},
209+
_ => offer.clone(),
210+
};
211+
212+
let offer = maybe_deref(&offer);
198213

199214
let mut random_bytes = [0u8; 32];
200215
rand::thread_rng().fill_bytes(&mut random_bytes);

src/payment/unified.rs

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use std::vec::IntoIter;
2626

2727
use lightning::ln::channelmanager::PaymentId;
2828
use lightning::offers::offer::Offer;
29-
use lightning::onion_message::dns_resolution::HumanReadableName;
29+
use lightning::onion_message::dns_resolution::HumanReadableName as LdkHumanReadableName;
3030
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};
3131

3232
use bip21::de::ParamKind;
@@ -40,6 +40,11 @@ use tokio::time::timeout;
4040

4141
type Uri<'a> = bip21::Uri<'a, NetworkChecked, Extras>;
4242

43+
#[cfg(not(feature = "uniffi"))]
44+
type HumanReadableName = LdkHumanReadableName;
45+
#[cfg(feature = "uniffi")]
46+
type HumanReadableName = Arc<crate::ffi::HumanReadableName>;
47+
4348
#[derive(Debug, Clone)]
4449
struct Extras {
4550
bolt11_invoice: Option<Bolt11Invoice>,
@@ -162,18 +167,36 @@ impl UnifiedPayment {
162167
Error::HrnResolverNotConfigured
163168
})?;
164169

165-
println!("Parsing instructions...");
170+
const TIMEOUT_DURATION: Duration = Duration::from_secs(30);
166171

167-
let instructions =
168-
PaymentInstructions::parse(uri_str, self.config.network, resolver.as_ref(), false)
169-
.await
170-
.map_err(|e| {
171-
log_error!(self.logger, "Failed to parse payment instructions: {:?}", e);
172-
println!("Failed to parse payment instructions: {:?}", e);
173-
Error::UriParameterParsingFailed
174-
})?;
172+
let hrn =
173+
LdkHumanReadableName::from_encoded(uri_str).map_err(|_| Error::HrnParsingFailed)?;
174+
175+
let hrn: HumanReadableName = maybe_wrap(hrn.clone());
175176

176-
println!("Sending...");
177+
let target_network =
178+
match crate::dnssec_testing_utils::get_testing_offer_override(Some(hrn.clone())) {
179+
Some(_) => bitcoin::Network::Bitcoin,
180+
_ => self.config.network,
181+
};
182+
183+
let instructions = timeout(
184+
TIMEOUT_DURATION,
185+
PaymentInstructions::parse(uri_str, target_network, resolver.as_ref(), false),
186+
)
187+
.await
188+
.map_err(|_| {
189+
log_error!(
190+
self.logger,
191+
"Payment instruction parsing timed out after {:?}",
192+
TIMEOUT_DURATION
193+
);
194+
Error::TimeoutOccurred
195+
})?
196+
.map_err(|e| {
197+
log_error!(self.logger, "Failed to parse payment instructions: {:?}", e);
198+
Error::UriParameterParsingFailed
199+
})?;
177200

178201
let resolved = match instructions {
179202
PaymentInstructions::ConfigurableAmount(instr) => {
@@ -208,7 +231,7 @@ impl UnifiedPayment {
208231
{
209232
let offer = maybe_wrap(offer.clone());
210233

211-
let payment_result = if let Ok(hrn) = HumanReadableName::from_encoded(uri_str) {
234+
let payment_result = if let Ok(hrn) = LdkHumanReadableName::from_encoded(uri_str) {
212235
let hrn = maybe_wrap(hrn.clone());
213236
self.bolt12_payment.send_using_amount(&offer, amount_msat.unwrap_or(0), None, None, Some(hrn))
214237
} else if let Some(amount_msat) = amount_msat {

tests/common/mod.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ use bitcoin::{
2828
use electrsd::corepc_node::{Client as BitcoindClient, Node as BitcoinD};
2929
use electrsd::{corepc_node, ElectrsD};
3030
use electrum_client::ElectrumApi;
31-
use ldk_node::config::{AsyncPaymentsRole, Config, ElectrumSyncConfig, EsploraSyncConfig};
31+
use ldk_node::config::{
32+
AsyncPaymentsRole, Config, ElectrumSyncConfig, EsploraSyncConfig, HumanReadableNamesConfig,
33+
};
3234
use ldk_node::io::sqlite_store::SqliteStore;
3335
use ldk_node::payment::{PaymentDirection, PaymentKind, PaymentStatus};
3436
use ldk_node::{
@@ -281,14 +283,21 @@ pub(crate) use setup_builder;
281283

282284
pub(crate) fn setup_two_nodes(
283285
chain_source: &TestChainSource, allow_0conf: bool, anchor_channels: bool,
284-
anchors_trusted_no_reserve: bool,
286+
anchors_trusted_no_reserve: bool, second_node_is_hrn_resolver: bool,
285287
) -> (TestNode, TestNode) {
286288
println!("== Node A ==");
287289
let config_a = random_config(anchor_channels);
288290
let node_a = setup_node(chain_source, config_a, None);
289291

290292
println!("\n== Node B ==");
291293
let mut config_b = random_config(anchor_channels);
294+
if second_node_is_hrn_resolver {
295+
config_b.node_config.hrn_config = Some(HumanReadableNamesConfig {
296+
default_dns_resolvers: Vec::new(),
297+
is_hrn_resolver: true,
298+
dns_server_address: "8.8.8.8:53".to_string(),
299+
});
300+
}
292301
if allow_0conf {
293302
config_b.node_config.trusted_peers_0conf.push(node_a.node_id());
294303
}

0 commit comments

Comments
 (0)