Skip to content

Commit 602dd01

Browse files
committed
Add end-to-end test for HRN resolution
Introduce a comprehensive test case to verify the full lifecycle of a payment initiated via a Human Readable Name (HRN). This test ensures that the integration between HRN parsing, BIP 353 resolution, and BOLT12 offer execution is functioning correctly within the node. By asserting that an encoded URI can be successfully resolved to a valid offer and subsequently paid, we validate the reliability of the resolution pipeline and ensure that recent architectural changes to the OnionMessenger and Node configuration work in unison.
1 parent 56cf158 commit 602dd01

6 files changed

Lines changed: 119 additions & 7 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ check-cfg = [
127127
"cfg(cln_test)",
128128
"cfg(lnd_test)",
129129
"cfg(cycle_tests)",
130+
"cfg(hrn_tests)",
130131
]
131132

132133
[[bench]]

src/ffi/types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ impl std::fmt::Display for Offer {
381381
/// This struct can also be used for LN-Address recipients.
382382
///
383383
/// [Homograph Attacks]: https://en.wikipedia.org/wiki/IDN_homograph_attack
384-
#[derive(uniffi::Object)]
384+
#[derive(Eq, Hash, PartialEq, uniffi::Object)]
385385
pub struct HumanReadableName {
386386
pub(crate) inner: LdkHumanReadableName,
387387
}

src/payment/unified.rs

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ use bitcoin_payment_instructions::hrn_resolution::DummyHrnResolver;
2727
use bitcoin_payment_instructions::{PaymentInstructions, PaymentMethod};
2828
use lightning::ln::channelmanager::PaymentId;
2929
use lightning::offers::offer::Offer;
30-
use lightning::onion_message::dns_resolution::HumanReadableName;
3130
use lightning::routing::router::RouteParametersConfig;
3231
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};
3332

@@ -41,6 +40,11 @@ use crate::Config;
4140

4241
type Uri<'a> = bip21::Uri<'a, NetworkChecked, Extras>;
4342

43+
#[cfg(not(feature = "uniffi"))]
44+
type HumanReadableName = lightning::onion_message::dns_resolution::HumanReadableName;
45+
#[cfg(feature = "uniffi")]
46+
type HumanReadableName = crate::ffi::HumanReadableName;
47+
4448
#[derive(Debug, Clone)]
4549
struct Extras {
4650
bolt11_invoice: Option<Bolt11Invoice>,
@@ -164,18 +168,29 @@ impl UnifiedPayment {
164168
/// [BIP 353]: https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki
165169
pub async fn send(
166170
&self, uri_str: &str, amount_msat: Option<u64>,
167-
route_parameters: Option<RouteParametersConfig>,
171+
route_parameters: Option<RouteParametersConfig>, #[cfg(hrn_tests)] test_offer: &Offer,
168172
) -> Result<UnifiedPaymentResult, Error> {
169173
let resolver;
174+
let target_network;
170175

171176
if let Ok(_) = HumanReadableName::from_encoded(uri_str) {
172177
resolver = Arc::clone(&self.hrn_resolver);
178+
179+
#[cfg(hrn_tests)]
180+
{
181+
target_network = bitcoin::Network::Bitcoin;
182+
}
183+
#[cfg(not(hrn_tests))]
184+
{
185+
target_network = self.config.network;
186+
}
173187
} else {
174188
resolver = Arc::new(HRNResolver::Dummy(DummyHrnResolver));
189+
target_network = self.config.network;
175190
}
176191

177192
let parse_fut =
178-
PaymentInstructions::parse(uri_str, self.config.network, resolver.as_ref(), false);
193+
PaymentInstructions::parse(uri_str, target_network, resolver.as_ref(), false);
179194

180195
let instructions =
181196
tokio::time::timeout(Duration::from_secs(HRN_RESOLUTION_TIMEOUT_SECS), parse_fut)
@@ -238,8 +253,13 @@ impl UnifiedPayment {
238253

239254
for method in sorted_payment_methods {
240255
match method {
241-
PaymentMethod::LightningBolt12(offer) => {
242-
let offer = maybe_wrap(offer.clone());
256+
PaymentMethod::LightningBolt12(_offer) => {
257+
#[cfg(not(hrn_tests))]
258+
let offer = maybe_wrap(_offer.clone());
259+
260+
#[cfg(hrn_tests)]
261+
let offer = maybe_wrap(test_offer.clone());
262+
243263
let payment_result = if let Ok(hrn) = HumanReadableName::from_encoded(uri_str) {
244264
let hrn = maybe_wrap(hrn.clone());
245265
self.bolt12_payment.send_using_amount_inner(&offer, amount_msat.unwrap_or(0), None, None, route_parameters, Some(hrn))

tests/common/mod.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ use bitcoin::{
2626
use electrsd::corepc_node::{Client as BitcoindClient, Node as BitcoinD};
2727
use electrsd::{corepc_node, ElectrsD};
2828
use electrum_client::ElectrumApi;
29-
use ldk_node::config::{AsyncPaymentsRole, Config, ElectrumSyncConfig, EsploraSyncConfig};
29+
use ldk_node::config::{
30+
AsyncPaymentsRole, Config, ElectrumSyncConfig, EsploraSyncConfig, HRNResolverConfig,
31+
HumanReadableNamesConfig,
32+
};
3033
use ldk_node::entropy::{generate_entropy_mnemonic, NodeEntropy};
3134
use ldk_node::io::sqlite_store::SqliteStore;
3235
use ldk_node::payment::{PaymentDirection, PaymentKind, PaymentStatus};
@@ -401,11 +404,18 @@ pub(crate) fn setup_two_nodes_with_store(
401404
println!("== Node A ==");
402405
let mut config_a = random_config(anchor_channels);
403406
config_a.store_type = store_type;
407+
408+
if cfg!(hrn_tests) {
409+
config_a.node_config.hrn_config =
410+
HumanReadableNamesConfig { resolution_config: HRNResolverConfig::Blip32 };
411+
}
412+
404413
let node_a = setup_node(chain_source, config_a);
405414

406415
println!("\n== Node B ==");
407416
let mut config_b = random_config(anchor_channels);
408417
config_b.store_type = store_type;
418+
409419
if allow_0conf {
410420
config_b.node_config.trusted_peers_0conf.push(node_a.node_id());
411421
}

tests/integration_tests_hrn.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// This file is Copyright its original authors, visible in version control history.
2+
//
3+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5+
// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
6+
// accordance with one or both of these licenses.
7+
8+
#![cfg(hrn_tests)]
9+
10+
mod common;
11+
12+
use bitcoin::Amount;
13+
use common::{
14+
expect_channel_ready_event, expect_payment_successful_event, generate_blocks_and_wait,
15+
open_channel, premine_and_distribute_funds, setup_bitcoind_and_electrsd, setup_two_nodes,
16+
TestChainSource,
17+
};
18+
use ldk_node::payment::UnifiedPaymentResult;
19+
use ldk_node::Event;
20+
use lightning::ln::channelmanager::PaymentId;
21+
22+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
23+
async fn unified_send_to_hrn() {
24+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
25+
let chain_source = TestChainSource::Esplora(&electrsd);
26+
27+
let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false);
28+
29+
let address_a = node_a.onchain_payment().new_address().unwrap();
30+
let premined_sats = 5_000_000;
31+
32+
premine_and_distribute_funds(
33+
&bitcoind.client,
34+
&electrsd.client,
35+
vec![address_a],
36+
Amount::from_sat(premined_sats),
37+
)
38+
.await;
39+
40+
node_a.sync_wallets().unwrap();
41+
open_channel(&node_a, &node_b, 4_000_000, true, &electrsd).await;
42+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await;
43+
44+
node_a.sync_wallets().unwrap();
45+
node_b.sync_wallets().unwrap();
46+
47+
expect_channel_ready_event!(node_a, node_b.node_id());
48+
expect_channel_ready_event!(node_b, node_a.node_id());
49+
50+
// Sleep until we broadcast a node announcement.
51+
while node_b.status().latest_node_announcement_broadcast_timestamp.is_none() {
52+
std::thread::sleep(std::time::Duration::from_millis(10));
53+
}
54+
55+
let test_offer = node_b.bolt12_payment().receive(1000000, "test offer", None, None).unwrap();
56+
57+
// Sleep one more sec to make sure the node announcement propagates.
58+
std::thread::sleep(std::time::Duration::from_secs(1));
59+
60+
let hrn_str = "matt@mattcorallo.com";
61+
62+
let offer_payment_id: PaymentId =
63+
match node_a.unified_payment().send(&hrn_str, Some(1000000), None, &test_offer).await {
64+
Ok(UnifiedPaymentResult::Bolt12 { payment_id }) => {
65+
println!("\nBolt12 payment sent successfully with PaymentID: {:?}", payment_id);
66+
payment_id
67+
},
68+
Ok(UnifiedPaymentResult::Bolt11 { payment_id: _ }) => {
69+
panic!("Expected Bolt12 payment but got Bolt11");
70+
},
71+
Ok(UnifiedPaymentResult::Onchain { txid: _ }) => {
72+
panic!("Expected Bolt12 payment but got On-chain transaction");
73+
},
74+
Err(e) => {
75+
panic!("Expected Bolt12 payment but got error: {:?}", e);
76+
},
77+
};
78+
79+
expect_payment_successful_event!(node_a, Some(offer_payment_id), None);
80+
}

tests/integration_tests_rust.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ use ldk_node::payment::{
3333
ConfirmationStatus, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus,
3434
UnifiedPaymentResult,
3535
};
36+
3637
use ldk_node::{Builder, Event, NodeError};
3738
use lightning::ln::channelmanager::PaymentId;
3839
use lightning::routing::gossip::{NodeAlias, NodeId};

0 commit comments

Comments
 (0)