Skip to content

Commit f008439

Browse files
committed
Allow configuring Human-Readable Name resolution
Exposes LDK Node's new `HumanReadableNamesConfig` via a new `[hrn]` section in `config.toml`. Users can pick between resolving BIP 353 names locally over DNS (`mode = "dns"`, the default) or via bLIP-32 (`mode = "blip32"`), and, when using DNS, optionally run as an HRN resolution service for the rest of the network. Defaults match LDK Node's: resolve via DNS against `8.8.8.8:53` with the resolution service disabled (enabling it requires the node to be announceable so others can route resolution requests to us). Co-Authored-By: HAL 9000
1 parent 2869459 commit f008439

4 files changed

Lines changed: 168 additions & 0 deletions

File tree

contrib/ldk-server-config.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,15 @@ poll_metrics_interval = 60 # The polling interval for metrics in seconds.
103103
[tor]
104104
# Only connections to OnionV3 peers will be made via this proxy; other connections (IPv4 peers, Electrum server) will not be routed over Tor.
105105
#proxy_address = "127.0.0.1:9050" # Tor daemon SOCKS proxy address.
106+
107+
# Human-Readable Names (BIP 353) resolution
108+
[hrn]
109+
# Resolution method: "dns" (resolve locally via a DNS server) or "blip32" (ask other
110+
# nodes to resolve for us via bLIP-32). Defaults to "dns".
111+
#mode = "dns"
112+
# DNS server used when `mode = "dns"`. Defaults to 8.8.8.8:53 (Google Public DNS).
113+
#dns_server_address = "8.8.8.8:53"
114+
# When set to true (and `mode = "dns"`), also offer HRN resolution to the rest of the
115+
# network over Onion Messages. Requires the node to be announceable so resolution
116+
# requests can be routed to us. Defaults to false.
117+
#enable_resolution_service = false

docs/configuration.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,21 @@ are not proxied. This does not set up inbound connections, to make your node rea
123123
hidden service, you need to configure Tor separately. See the [Tor guide](tor.md) for the
124124
full setup.
125125

126+
### `[hrn]`
127+
128+
Configures how the node resolves [BIP 353](https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki)
129+
Human-Readable Names (e.g., `₿alice@example.com`) to Lightning payment destinations.
130+
131+
Two resolution methods are supported via the `mode` field:
132+
133+
- **`"dns"`** (default) - Resolve names locally using a DNS server. The server is set via
134+
`dns_server_address` (default: `8.8.8.8:53`, Google Public DNS). When
135+
`enable_resolution_service = true`, the node additionally offers HRN resolution to the
136+
rest of the network over Onion Messages. This requires the node to be announceable so
137+
resolution requests can be routed to it, and is therefore disabled by default.
138+
- **`"blip32"`** - Ask other nodes to resolve names on our behalf via
139+
[bLIP-32](https://github.com/lightning/blips/blob/master/blip-0032.md).
140+
126141
## Storage Layout
127142

128143
```

ldk-server/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ fn main() {
141141
ldk_node_config.listening_addresses = config_file.listening_addrs;
142142
ldk_node_config.announcement_addresses = config_file.announcement_addrs;
143143
ldk_node_config.network = config_file.network;
144+
ldk_node_config.hrn_config = config_file.hrn_config;
144145

145146
let mut builder = Builder::from_config(ldk_node_config);
146147
builder.set_log_facade_logger();

ldk-server/src/util/config.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use std::{fs, io};
1515
use clap::Parser;
1616
use ldk_node::bitcoin::secp256k1::PublicKey;
1717
use ldk_node::bitcoin::Network;
18+
use ldk_node::config::{HRNResolverConfig, HumanReadableNamesConfig};
1819
use ldk_node::lightning::ln::msgs::SocketAddress;
1920
use ldk_node::lightning::routing::gossip::NodeAlias;
2021
use ldk_node::liquidity::LSPS2ServiceConfig;
@@ -61,6 +62,7 @@ pub struct Config {
6162
pub metrics_username: Option<String>,
6263
pub metrics_password: Option<String>,
6364
pub tor_config: Option<TorConfig>,
65+
pub hrn_config: HumanReadableNamesConfig,
6466
}
6567

6668
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -114,6 +116,7 @@ struct ConfigBuilder {
114116
metrics_username: Option<String>,
115117
metrics_password: Option<String>,
116118
tor_proxy_address: Option<String>,
119+
hrn: Option<HrnTomlConfig>,
117120
}
118121

119122
impl ConfigBuilder {
@@ -180,6 +183,10 @@ impl ConfigBuilder {
180183
if let Some(tor) = toml.tor {
181184
self.tor_proxy_address = Some(tor.proxy_address)
182185
}
186+
187+
if let Some(hrn) = toml.hrn {
188+
self.hrn = Some(hrn);
189+
}
183190
}
184191

185192
fn merge_args(&mut self, args: &ArgsConfig) {
@@ -402,6 +409,11 @@ impl ConfigBuilder {
402409
})
403410
.transpose()?;
404411

412+
let hrn_config = match self.hrn {
413+
Some(hrn) => HumanReadableNamesConfig::try_from(hrn)?,
414+
None => HumanReadableNamesConfig::default(),
415+
};
416+
405417
Ok(Config {
406418
network,
407419
listening_addrs,
@@ -422,6 +434,7 @@ impl ConfigBuilder {
422434
metrics_username,
423435
metrics_password,
424436
tor_config: tor_proxy_address.map(|proxy_address| TorConfig { proxy_address }),
437+
hrn_config,
425438
})
426439
}
427440
}
@@ -439,6 +452,7 @@ pub struct TomlConfig {
439452
tls: Option<TomlTlsConfig>,
440453
metrics: Option<MetricsTomlConfig>,
441454
tor: Option<TomlTorConfig>,
455+
hrn: Option<HrnTomlConfig>,
442456
}
443457

444458
#[derive(Deserialize, Serialize)]
@@ -505,6 +519,52 @@ struct TomlTorConfig {
505519
proxy_address: String,
506520
}
507521

522+
#[derive(Deserialize, Serialize)]
523+
struct HrnTomlConfig {
524+
mode: Option<String>,
525+
dns_server_address: Option<String>,
526+
enable_resolution_service: Option<bool>,
527+
}
528+
529+
impl TryFrom<HrnTomlConfig> for HumanReadableNamesConfig {
530+
type Error = io::Error;
531+
532+
fn try_from(value: HrnTomlConfig) -> Result<Self, Self::Error> {
533+
let mut config = HumanReadableNamesConfig::default();
534+
535+
match value.mode.as_deref() {
536+
None | Some("dns") => {},
537+
Some("blip32") => {
538+
config.resolution_config = HRNResolverConfig::Blip32;
539+
},
540+
Some(other) => {
541+
return Err(io::Error::new(
542+
io::ErrorKind::InvalidInput,
543+
format!("Invalid HRN mode '{}' configured; expected 'dns' or 'blip32'", other),
544+
))
545+
},
546+
}
547+
548+
if let HRNResolverConfig::Dns { dns_server_address, enable_hrn_resolution_service } =
549+
&mut config.resolution_config
550+
{
551+
if let Some(addr) = value.dns_server_address.as_deref() {
552+
*dns_server_address = SocketAddress::from_str(addr).map_err(|e| {
553+
io::Error::new(
554+
io::ErrorKind::InvalidInput,
555+
format!("Invalid HRN DNS server address configured: {}", e),
556+
)
557+
})?;
558+
}
559+
if let Some(enable) = value.enable_resolution_service {
560+
*enable_hrn_resolution_service = enable;
561+
}
562+
}
563+
564+
Ok(config)
565+
}
566+
}
567+
508568
#[derive(Deserialize, Serialize)]
509569
struct LiquidityConfig {
510570
lsps2_client: Option<LSPSClientTomlConfig>,
@@ -936,6 +996,7 @@ mod tests {
936996
tor_config: Some(TorConfig {
937997
proxy_address: SocketAddress::from_str("127.0.0.1:9050").unwrap(),
938998
}),
999+
hrn_config: HumanReadableNamesConfig::default(),
9391000
};
9401001

9411002
assert_eq!(config.listening_addrs, expected.listening_addrs);
@@ -1241,6 +1302,7 @@ mod tests {
12411302
metrics_username: None,
12421303
metrics_password: None,
12431304
tor_config: None,
1305+
hrn_config: HumanReadableNamesConfig::default(),
12441306
};
12451307

12461308
assert_eq!(config.listening_addrs, expected.listening_addrs);
@@ -1350,6 +1412,7 @@ mod tests {
13501412
tor_config: Some(TorConfig {
13511413
proxy_address: SocketAddress::from_str("127.0.0.1:9050").unwrap(),
13521414
}),
1415+
hrn_config: HumanReadableNamesConfig::default(),
13531416
};
13541417

13551418
assert_eq!(config.listening_addrs, expected.listening_addrs);
@@ -1501,4 +1564,81 @@ mod tests {
15011564
let err = result.unwrap_err();
15021565
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
15031566
}
1567+
1568+
#[test]
1569+
fn test_hrn_config() {
1570+
let storage_path = std::env::temp_dir();
1571+
let config_file_name = "test_hrn_config.toml";
1572+
1573+
let base_config = r#"
1574+
[node]
1575+
network = "regtest"
1576+
1577+
[bitcoind]
1578+
rpc_address = "127.0.0.1:8332"
1579+
rpc_user = "bitcoind-testuser"
1580+
rpc_password = "bitcoind-testpassword"
1581+
1582+
[liquidity.lsps2_service]
1583+
advertise_service = false
1584+
channel_opening_fee_ppm = 1000
1585+
channel_over_provisioning_ppm = 500000
1586+
min_channel_opening_fee_msat = 10000000
1587+
min_channel_lifetime = 4320
1588+
max_client_to_self_delay = 1440
1589+
min_payment_size_msat = 10000000
1590+
max_payment_size_msat = 25000000000
1591+
client_trusts_lsp = true
1592+
disable_client_reserve = false
1593+
"#;
1594+
1595+
let mut args_config = empty_args_config();
1596+
args_config.config_file =
1597+
Some(storage_path.join(config_file_name).to_string_lossy().to_string());
1598+
1599+
// Default: no `[hrn]` section -> DNS against 8.8.8.8:53, resolution service disabled.
1600+
fs::write(storage_path.join(config_file_name), base_config).unwrap();
1601+
let config = load_config(&args_config).unwrap();
1602+
match config.hrn_config.resolution_config {
1603+
HRNResolverConfig::Dns { dns_server_address, enable_hrn_resolution_service } => {
1604+
assert_eq!(dns_server_address, SocketAddress::from_str("8.8.8.8:53").unwrap());
1605+
assert!(!enable_hrn_resolution_service);
1606+
},
1607+
other => panic!("unexpected default HRN resolver config: {:?}", other),
1608+
}
1609+
1610+
// Custom DNS server address with resolution service enabled.
1611+
let toml_config = format!(
1612+
"{}\n[hrn]\ndns_server_address = \"1.1.1.1:53\"\nenable_resolution_service = true\n",
1613+
base_config
1614+
);
1615+
fs::write(storage_path.join(config_file_name), &toml_config).unwrap();
1616+
let config = load_config(&args_config).unwrap();
1617+
match config.hrn_config.resolution_config {
1618+
HRNResolverConfig::Dns { dns_server_address, enable_hrn_resolution_service } => {
1619+
assert_eq!(dns_server_address, SocketAddress::from_str("1.1.1.1:53").unwrap());
1620+
assert!(enable_hrn_resolution_service);
1621+
},
1622+
other => panic!("unexpected HRN resolver config: {:?}", other),
1623+
}
1624+
1625+
// Blip32 mode.
1626+
let toml_config = format!("{}\n[hrn]\nmode = \"blip32\"\n", base_config);
1627+
fs::write(storage_path.join(config_file_name), &toml_config).unwrap();
1628+
let config = load_config(&args_config).unwrap();
1629+
assert!(matches!(config.hrn_config.resolution_config, HRNResolverConfig::Blip32));
1630+
1631+
// Invalid mode is rejected.
1632+
let toml_config = format!("{}\n[hrn]\nmode = \"bogus\"\n", base_config);
1633+
fs::write(storage_path.join(config_file_name), &toml_config).unwrap();
1634+
let err = load_config(&args_config).unwrap_err();
1635+
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
1636+
1637+
// Invalid DNS server address is rejected.
1638+
let toml_config =
1639+
format!("{}\n[hrn]\ndns_server_address = \"not-a-socket-address\"\n", base_config);
1640+
fs::write(storage_path.join(config_file_name), &toml_config).unwrap();
1641+
let err = load_config(&args_config).unwrap_err();
1642+
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
1643+
}
15041644
}

0 commit comments

Comments
 (0)