Skip to content

Commit 21eea8c

Browse files
authored
Merge pull request lightningdevkit#630 from chuksys/add-support-for-resolving-hrns
Add support for resolving BIP 353 Human-Readable Names
2 parents c754e2f + 07654fa commit 21eea8c

11 files changed

Lines changed: 437 additions & 38 deletions

File tree

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: CI Checks - HRN Integration Tests
2+
3+
on: [push, pull_request]
4+
5+
concurrency:
6+
group: ${{ github.workflow }}-${{ github.ref }}
7+
cancel-in-progress: true
8+
9+
jobs:
10+
build-and-test:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout source code
15+
uses: actions/checkout@v3
16+
- name: Install Rust stable toolchain
17+
run: |
18+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain stable
19+
- name: Enable caching for bitcoind
20+
id: cache-bitcoind
21+
uses: actions/cache@v4
22+
with:
23+
path: bin/bitcoind-${{ runner.os }}-${{ runner.arch }}
24+
key: bitcoind-29.0-${{ runner.os }}-${{ runner.arch }}
25+
- name: Enable caching for electrs
26+
id: cache-electrs
27+
uses: actions/cache@v4
28+
with:
29+
path: bin/electrs-${{ runner.os }}-${{ runner.arch }}
30+
key: electrs-${{ runner.os }}-${{ runner.arch }}
31+
- name: Download bitcoind/electrs
32+
if: "steps.cache-bitcoind.outputs.cache-hit != 'true' || steps.cache-electrs.outputs.cache-hit != 'true'"
33+
run: |
34+
source ./scripts/download_bitcoind_electrs.sh
35+
mkdir -p bin
36+
mv "$BITCOIND_EXE" bin/bitcoind-${{ runner.os }}-${{ runner.arch }}
37+
mv "$ELECTRS_EXE" bin/electrs-${{ runner.os }}-${{ runner.arch }}
38+
- name: Set bitcoind/electrs environment variables
39+
run: |
40+
echo "BITCOIND_EXE=$( pwd )/bin/bitcoind-${{ runner.os }}-${{ runner.arch }}" >> "$GITHUB_ENV"
41+
echo "ELECTRS_EXE=$( pwd )/bin/electrs-${{ runner.os }}-${{ runner.arch }}" >> "$GITHUB_ENV"
42+
- name: Run HRN Integration Tests
43+
run: |
44+
RUSTFLAGS="--cfg no_download --cfg hrn_tests $RUSTFLAGS" cargo test --test integration_tests_hrn
45+
RUSTFLAGS="--cfg no_download --cfg hrn_tests $RUSTFLAGS" cargo test --test integration_tests_hrn --features uniffi

.github/workflows/rust.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,4 @@ jobs:
114114
- uses: actions/checkout@v6
115115
- uses: dtolnay/rust-toolchain@nightly
116116
- uses: dtolnay/install@cargo-docs-rs
117-
- run: cargo docs-rs
117+
- run: cargo docs-rs

Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ default = []
3838
#lightning-transaction-sync = { version = "0.2.0", features = ["esplora-async-https", "time", "electrum-rustls-ring"] }
3939
#lightning-liquidity = { version = "0.2.0", features = ["std"] }
4040
#lightning-macros = { version = "0.2.0" }
41+
#lightning-dns-resolver = { version = "0.3.0" }
4142

4243
lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "38a62c32454d3eac22578144c479dbf9a6d9bff6", features = ["std"] }
4344
lightning-types = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "38a62c32454d3eac22578144c479dbf9a6d9bff6" }
@@ -50,6 +51,7 @@ lightning-block-sync = { git = "https://github.com/lightningdevkit/rust-lightnin
5051
lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "38a62c32454d3eac22578144c479dbf9a6d9bff6", features = ["esplora-async-https", "time", "electrum-rustls-ring"] }
5152
lightning-liquidity = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "38a62c32454d3eac22578144c479dbf9a6d9bff6", features = ["std"] }
5253
lightning-macros = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "38a62c32454d3eac22578144c479dbf9a6d9bff6" }
54+
lightning-dns-resolver = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "38a62c32454d3eac22578144c479dbf9a6d9bff6" }
5355

5456
bdk_chain = { version = "0.23.0", default-features = false, features = ["std"] }
5557
bdk_esplora = { version = "0.22.0", default-features = false, features = ["async-https-rustls", "tokio"]}
@@ -126,6 +128,7 @@ check-cfg = [
126128
"cfg(cln_test)",
127129
"cfg(lnd_test)",
128130
"cfg(cycle_tests)",
131+
"cfg(hrn_tests)",
129132
]
130133

131134
[[bench]]
@@ -144,6 +147,7 @@ harness = false
144147
#lightning-transaction-sync = { path = "../rust-lightning/lightning-transaction-sync" }
145148
#lightning-liquidity = { path = "../rust-lightning/lightning-liquidity" }
146149
#lightning-macros = { path = "../rust-lightning/lightning-macros" }
150+
#lightning-dns-resolver = { path = "../rust-lightning/lightning-dns-resolver" }
147151

148152
#lightning = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main" }
149153
#lightning-types = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main" }
@@ -156,6 +160,7 @@ harness = false
156160
#lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main" }
157161
#lightning-liquidity = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main" }
158162
#lightning-macros = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main" }
163+
#lightning-dns-resolver = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main" }
159164

160165
#lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "21e9a9c0ef80021d0669f2a366f55d08ba8d9b03" }
161166
#lightning-types = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "21e9a9c0ef80021d0669f2a366f55d08ba8d9b03" }
@@ -168,6 +173,7 @@ harness = false
168173
#lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "21e9a9c0ef80021d0669f2a366f55d08ba8d9b03" }
169174
#lightning-liquidity = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "21e9a9c0ef80021d0669f2a366f55d08ba8d9b03" }
170175
#lightning-macros = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "21e9a9c0ef80021d0669f2a366f55d08ba8d9b03" }
176+
#lightning-dns-resolver = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "21e9a9c0ef80021d0669f2a366f55d08ba8d9b03" }
171177

172178
#vss-client-ng = { path = "../vss-client" }
173179
#vss-client-ng = { git = "https://github.com/lightningdevkit/vss-client", branch = "main" }
@@ -184,3 +190,4 @@ harness = false
184190
#lightning-transaction-sync = { path = "../rust-lightning/lightning-transaction-sync" }
185191
#lightning-liquidity = { path = "../rust-lightning/lightning-liquidity" }
186192
#lightning-macros = { path = "../rust-lightning/lightning-macros" }
193+
#lightning-dns-resolver = { path = "../rust-lightning/lightning-dns-resolver" }

bindings/ldk_node.udl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,3 +416,7 @@ typedef string LSPSDateTime;
416416
typedef string ScriptBuf;
417417

418418
typedef enum Event;
419+
420+
typedef interface HRNResolverConfig;
421+
422+
typedef dictionary HumanReadableNamesConfig;

src/builder.rs

Lines changed: 85 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
use std::collections::HashMap;
99
use std::convert::TryInto;
1010
use std::default::Default;
11+
use std::net::ToSocketAddrs;
1112
use std::path::PathBuf;
12-
use std::sync::{Arc, Mutex, Once, RwLock};
13+
use std::sync::{Arc, Mutex, Once, RwLock, Weak};
1314
use std::time::SystemTime;
1415
use std::{fmt, fs};
1516

@@ -19,12 +20,14 @@ use bitcoin::bip32::{ChildNumber, Xpriv};
1920
use bitcoin::key::Secp256k1;
2021
use bitcoin::secp256k1::PublicKey;
2122
use bitcoin::{BlockHash, Network};
23+
use bitcoin_payment_instructions::dns_resolver::DNSHrnResolver;
2224
use bitcoin_payment_instructions::onion_message_resolver::LDKOnionMessageDNSSECHrnResolver;
2325
use lightning::chain::{chainmonitor, BestBlock};
2426
use lightning::ln::channelmanager::{self, ChainParameters, ChannelManagerReadArgs};
2527
use lightning::ln::msgs::{RoutingMessageHandler, SocketAddress};
2628
use lightning::ln::peer_handler::{IgnoringMessageHandler, MessageHandler};
2729
use lightning::log_trace;
30+
use lightning::onion_message::dns_resolution::DNSResolverMessageHandler;
2831
use lightning::routing::gossip::NodeAlias;
2932
use lightning::routing::router::DefaultRouter;
3033
use lightning::routing::scoring::{
@@ -39,14 +42,15 @@ use lightning::util::persist::{
3942
};
4043
use lightning::util::ser::ReadableArgs;
4144
use lightning::util::sweep::OutputSweeper;
45+
use lightning_dns_resolver::OMDomainResolver;
4246
use lightning_persister::fs_store::v1::FilesystemStore;
4347
use vss_client::headers::VssHeaderProvider;
4448

4549
use crate::chain::ChainSource;
4650
use crate::config::{
4751
default_user_config, may_announce_channel, AnnounceError, AsyncPaymentsRole,
48-
BitcoindRestClientConfig, Config, ElectrumSyncConfig, EsploraSyncConfig, TorConfig,
49-
DEFAULT_ESPLORA_SERVER_URL, DEFAULT_LOG_FILENAME, DEFAULT_LOG_LEVEL,
52+
BitcoindRestClientConfig, Config, ElectrumSyncConfig, EsploraSyncConfig, HRNResolverConfig,
53+
TorConfig, DEFAULT_ESPLORA_SERVER_URL, DEFAULT_LOG_FILENAME, DEFAULT_LOG_LEVEL,
5054
};
5155
use crate::connection::ConnectionManager;
5256
use crate::entropy::NodeEntropy;
@@ -77,8 +81,8 @@ use crate::runtime::{Runtime, RuntimeSpawner};
7781
use crate::tx_broadcaster::TransactionBroadcaster;
7882
use crate::types::{
7983
AsyncPersister, ChainMonitor, ChannelManager, DynStore, DynStoreRef, DynStoreWrapper,
80-
GossipSync, Graph, KeysManager, MessageRouter, OnionMessenger, PaymentStore, PeerManager,
81-
PendingPaymentStore, SyncAndAsyncKVStore,
84+
GossipSync, Graph, HRNResolver, KeysManager, MessageRouter, OnionMessenger, PaymentStore,
85+
PeerManager, PendingPaymentStore, SyncAndAsyncKVStore,
8286
};
8387
use crate::wallet::persist::KVStoreWalletPersister;
8488
use crate::wallet::Wallet;
@@ -195,6 +199,8 @@ pub enum BuildError {
195199
NetworkMismatch,
196200
/// The role of the node in an asynchronous payments context is not compatible with the current configuration.
197201
AsyncPaymentsConfigMismatch,
202+
/// An attempt to setup a DNS Resolver failed.
203+
DNSResolverSetupFailed,
198204
}
199205

200206
impl fmt::Display for BuildError {
@@ -229,6 +235,9 @@ impl fmt::Display for BuildError {
229235
"The async payments role is not compatible with the current configuration."
230236
)
231237
},
238+
Self::DNSResolverSetupFailed => {
239+
write!(f, "An attempt to setup a DNS resolver has failed.")
240+
},
232241
}
233242
}
234243
}
@@ -1726,7 +1735,71 @@ fn build_with_store_internal(
17261735
})?;
17271736
}
17281737

1729-
let hrn_resolver = Arc::new(LDKOnionMessageDNSSECHrnResolver::new(Arc::clone(&network_graph)));
1738+
// This hook resolves a circular dependency:
1739+
// 1. PeerManager requires OnionMessenger (via MessageHandler).
1740+
// 2. OnionMessenger (via HRN resolver) needs to call PeerManager::process_events.
1741+
//
1742+
// We provide the resolver with a Weak pointer via this Mutex-protected "hook."
1743+
// This allows us to initialize the resolver before the PeerManager exists,
1744+
// and prevents a reference cycle (memory leak).
1745+
let peer_manager_hook: Arc<Mutex<Option<Weak<PeerManager>>>> = Arc::new(Mutex::new(None));
1746+
let hrn_resolver;
1747+
1748+
let runtime_handle = runtime.handle();
1749+
1750+
let om_resolver: Arc<dyn DNSResolverMessageHandler + Send + Sync> = match &config
1751+
.hrn_config
1752+
.resolution_config
1753+
{
1754+
HRNResolverConfig::Blip32 => {
1755+
let hrn_res =
1756+
Arc::new(LDKOnionMessageDNSSECHrnResolver::new(Arc::clone(&network_graph)));
1757+
hrn_resolver = HRNResolver::Onion(Arc::clone(&hrn_res));
1758+
1759+
// We clone the hook because it's moved into a Send + Sync closure that outlives this scope.
1760+
let pm_hook_clone = Arc::clone(&peer_manager_hook);
1761+
hrn_res.register_post_queue_action(Box::new(move || {
1762+
if let Ok(guard) = pm_hook_clone.lock() {
1763+
if let Some(pm) = guard.as_ref().and_then(|weak| weak.upgrade()) {
1764+
pm.process_events();
1765+
}
1766+
}
1767+
}));
1768+
hrn_res as Arc<dyn DNSResolverMessageHandler + Send + Sync>
1769+
},
1770+
HRNResolverConfig::Dns { dns_server_address, enable_hrn_resolution_service, .. } => {
1771+
let addr = dns_server_address
1772+
.to_socket_addrs()
1773+
.map_err(|_| BuildError::DNSResolverSetupFailed)?
1774+
.next()
1775+
.ok_or({
1776+
log_error!(logger, "No valid address found for: {}", dns_server_address);
1777+
BuildError::DNSResolverSetupFailed
1778+
})?;
1779+
let hrn_res = Arc::new(DNSHrnResolver(addr));
1780+
hrn_resolver = HRNResolver::Local(hrn_res);
1781+
1782+
if *enable_hrn_resolution_service {
1783+
if let Err(_) = may_announce_channel(&config) {
1784+
log_error!(
1785+
logger,
1786+
"HRN resolution service enabled, but node is not announceable."
1787+
);
1788+
return Err(BuildError::DNSResolverSetupFailed);
1789+
}
1790+
1791+
Arc::new(OMDomainResolver::<IgnoringMessageHandler>::with_runtime(
1792+
addr,
1793+
None,
1794+
Some(runtime_handle.clone()),
1795+
)) as Arc<dyn DNSResolverMessageHandler + Send + Sync>
1796+
} else {
1797+
// The user wants to use DNS to pay others, but NOT provide a service to others.
1798+
Arc::new(IgnoringMessageHandler {})
1799+
as Arc<dyn DNSResolverMessageHandler + Send + Sync>
1800+
}
1801+
},
1802+
};
17301803

17311804
// Initialize the PeerManager
17321805
let onion_messenger: Arc<OnionMessenger> =
@@ -1739,7 +1812,7 @@ fn build_with_store_internal(
17391812
message_router,
17401813
Arc::clone(&channel_manager),
17411814
Arc::clone(&channel_manager),
1742-
Arc::clone(&hrn_resolver),
1815+
Arc::clone(&om_resolver),
17431816
IgnoringMessageHandler {},
17441817
))
17451818
} else {
@@ -1751,7 +1824,7 @@ fn build_with_store_internal(
17511824
message_router,
17521825
Arc::clone(&channel_manager),
17531826
Arc::clone(&channel_manager),
1754-
Arc::clone(&hrn_resolver),
1827+
Arc::clone(&om_resolver),
17551828
IgnoringMessageHandler {},
17561829
))
17571830
};
@@ -1882,12 +1955,9 @@ fn build_with_store_internal(
18821955
Arc::clone(&keys_manager),
18831956
));
18841957

1885-
let peer_manager_clone = Arc::downgrade(&peer_manager);
1886-
hrn_resolver.register_post_queue_action(Box::new(move || {
1887-
if let Some(upgraded_pointer) = peer_manager_clone.upgrade() {
1888-
upgraded_pointer.process_events();
1889-
}
1890-
}));
1958+
if let Ok(mut guard) = peer_manager_hook.lock() {
1959+
*guard = Some(Arc::downgrade(&peer_manager));
1960+
}
18911961

18921962
liquidity_source.as_ref().map(|l| l.set_peer_manager(Arc::downgrade(&peer_manager)));
18931963

@@ -2001,7 +2071,7 @@ fn build_with_store_internal(
20012071
node_metrics,
20022072
om_mailbox,
20032073
async_payments_role,
2004-
hrn_resolver,
2074+
hrn_resolver: Arc::new(hrn_resolver),
20052075
#[cfg(cycle_tests)]
20062076
_leak_checker,
20072077
})

src/config.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
//! Objects for configuring the node.
99
1010
use std::fmt;
11+
use std::str::FromStr;
1112
use std::time::Duration;
1213

1314
use bitcoin::secp256k1::PublicKey;
@@ -128,6 +129,7 @@ pub(crate) const LNURL_AUTH_TIMEOUT_SECS: u64 = 15;
128129
/// | `anchor_channels_config` | Some(..) |
129130
/// | `route_parameters` | None |
130131
/// | `tor_config` | None |
132+
/// | `hrn_config` | HumanReadableNamesConfig::default() |
131133
///
132134
/// See [`AnchorChannelsConfig`] and [`RouteParametersConfig`] for more information regarding their
133135
/// respective default values.
@@ -199,6 +201,10 @@ pub struct Config {
199201
///
200202
/// **Note**: If unset, connecting to peer OnionV3 addresses will fail.
201203
pub tor_config: Option<TorConfig>,
204+
/// Configuration options for Human-Readable Names ([BIP 353]).
205+
///
206+
/// [BIP 353]: https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki
207+
pub hrn_config: HumanReadableNamesConfig,
202208
}
203209

204210
impl Default for Config {
@@ -214,6 +220,62 @@ impl Default for Config {
214220
tor_config: None,
215221
route_parameters: None,
216222
node_alias: None,
223+
hrn_config: HumanReadableNamesConfig::default(),
224+
}
225+
}
226+
}
227+
228+
/// Configuration options for how our node resolves Human-Readable Names (BIP 353).
229+
///
230+
/// [BIP 353]: https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki
231+
#[derive(Debug, Clone)]
232+
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
233+
pub enum HRNResolverConfig {
234+
/// Use [bLIP-32] to ask other nodes to resolve names for us.
235+
///
236+
/// [bLIP-32]: https://github.com/lightning/blips/blob/master/blip-0032.md
237+
Blip32,
238+
/// Resolve names locally using a specific DNS server.
239+
Dns {
240+
/// The IP and port of the DNS server.
241+
///
242+
/// **Default:** `8.8.8.8:53` (Google Public DNS)
243+
dns_server_address: SocketAddress,
244+
/// If set to true, this allows others to use our node for HRN resolutions.
245+
///
246+
/// **Default:** `false`
247+
///
248+
/// **Note:** Enabling `enable_hrn_resolution_service` allows your node to act
249+
/// as a resolver for the rest of the network. For this to work, your node must
250+
/// be announceable (publicly visible in the network graph) so that other nodes
251+
/// can route resolution requests to you via Onion Messages. This does not affect
252+
/// your node's ability to resolve names for its own outgoing payments.
253+
enable_hrn_resolution_service: bool,
254+
},
255+
}
256+
257+
/// Configuration options for Human-Readable Names ([BIP 353]).
258+
///
259+
/// [BIP 353]: https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki
260+
#[derive(Debug, Clone)]
261+
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
262+
pub struct HumanReadableNamesConfig {
263+
/// This sets how our node resolves names when we want to send a payment.
264+
///
265+
/// By default, this uses the `Dns` variant with the following settings:
266+
/// * **DNS Server**: `8.8.8.8:53` (Google Public DNS)
267+
/// * **Resolution Service**: Enabled (`false`)
268+
pub resolution_config: HRNResolverConfig,
269+
}
270+
271+
impl Default for HumanReadableNamesConfig {
272+
fn default() -> Self {
273+
HumanReadableNamesConfig {
274+
resolution_config: HRNResolverConfig::Dns {
275+
dns_server_address: SocketAddress::from_str("8.8.8.8:53")
276+
.expect("Socket address conversion failed."),
277+
enable_hrn_resolution_service: false,
278+
},
217279
}
218280
}
219281
}

0 commit comments

Comments
 (0)