@@ -15,6 +15,7 @@ use std::{fs, io};
1515use clap:: Parser ;
1616use ldk_node:: bitcoin:: secp256k1:: PublicKey ;
1717use ldk_node:: bitcoin:: Network ;
18+ use ldk_node:: config:: { HRNResolverConfig , HumanReadableNamesConfig } ;
1819use ldk_node:: lightning:: ln:: msgs:: SocketAddress ;
1920use ldk_node:: lightning:: routing:: gossip:: NodeAlias ;
2021use 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
119122impl 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,93 @@ 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 HrnTomlConfig { mode, dns_server_address, enable_resolution_service } = value;
534+
535+ let resolution_config = match mode. as_deref ( ) {
536+ None | Some ( "dns" ) => {
537+ // Start from LDK Node's DNS defaults so we don't have to hardcode them, but fall
538+ // back to explicit values if the upstream default ever stops being `Dns`.
539+ let ( mut dns_server_address_val, mut enable_hrn_resolution_service) =
540+ if let HRNResolverConfig :: Dns {
541+ dns_server_address,
542+ enable_hrn_resolution_service,
543+ } = HumanReadableNamesConfig :: default ( ) . resolution_config
544+ {
545+ ( dns_server_address, enable_hrn_resolution_service)
546+ } else {
547+ (
548+ SocketAddress :: from_str ( "8.8.8.8:53" )
549+ . expect ( "`8.8.8.8:53` is a valid socket address" ) ,
550+ false ,
551+ )
552+ } ;
553+
554+ if let Some ( addr) = dns_server_address. as_deref ( ) {
555+ dns_server_address_val = parse_dns_server_address ( addr) ?;
556+ }
557+ if let Some ( enable) = enable_resolution_service {
558+ enable_hrn_resolution_service = enable;
559+ }
560+
561+ HRNResolverConfig :: Dns {
562+ dns_server_address : dns_server_address_val,
563+ enable_hrn_resolution_service,
564+ }
565+ } ,
566+ Some ( "blip32" ) => {
567+ if dns_server_address. is_some ( ) {
568+ return Err ( io:: Error :: new (
569+ io:: ErrorKind :: InvalidInput ,
570+ "`hrn.dns_server_address` only applies when `hrn.mode = \" dns\" `"
571+ . to_string ( ) ,
572+ ) ) ;
573+ }
574+ if enable_resolution_service. is_some ( ) {
575+ return Err ( io:: Error :: new (
576+ io:: ErrorKind :: InvalidInput ,
577+ "`hrn.enable_resolution_service` only applies when `hrn.mode = \" dns\" `"
578+ . to_string ( ) ,
579+ ) ) ;
580+ }
581+ HRNResolverConfig :: Blip32
582+ } ,
583+ Some ( other) => {
584+ return Err ( io:: Error :: new (
585+ io:: ErrorKind :: InvalidInput ,
586+ format ! ( "Invalid HRN mode '{}' configured; expected 'dns' or 'blip32'" , other) ,
587+ ) )
588+ } ,
589+ } ;
590+
591+ Ok ( HumanReadableNamesConfig { resolution_config } )
592+ }
593+ }
594+
595+ /// Parses a DNS server address, falling back to port 53 if the user omitted the port.
596+ fn parse_dns_server_address ( addr : & str ) -> io:: Result < SocketAddress > {
597+ if let Ok ( sa) = SocketAddress :: from_str ( addr) {
598+ return Ok ( sa) ;
599+ }
600+ let with_default_port = format ! ( "{}:53" , addr) ;
601+ SocketAddress :: from_str ( & with_default_port) . map_err ( |e| {
602+ io:: Error :: new (
603+ io:: ErrorKind :: InvalidInput ,
604+ format ! ( "Invalid HRN DNS server address configured: {}" , e) ,
605+ )
606+ } )
607+ }
608+
508609#[ derive( Deserialize , Serialize ) ]
509610struct LiquidityConfig {
510611 lsps2_client : Option < LSPSClientTomlConfig > ,
@@ -936,6 +1037,7 @@ mod tests {
9361037 tor_config : Some ( TorConfig {
9371038 proxy_address : SocketAddress :: from_str ( "127.0.0.1:9050" ) . unwrap ( ) ,
9381039 } ) ,
1040+ hrn_config : HumanReadableNamesConfig :: default ( ) ,
9391041 } ;
9401042
9411043 assert_eq ! ( config. listening_addrs, expected. listening_addrs) ;
@@ -1241,6 +1343,7 @@ mod tests {
12411343 metrics_username : None ,
12421344 metrics_password : None ,
12431345 tor_config : None ,
1346+ hrn_config : HumanReadableNamesConfig :: default ( ) ,
12441347 } ;
12451348
12461349 assert_eq ! ( config. listening_addrs, expected. listening_addrs) ;
@@ -1350,6 +1453,7 @@ mod tests {
13501453 tor_config : Some ( TorConfig {
13511454 proxy_address : SocketAddress :: from_str ( "127.0.0.1:9050" ) . unwrap ( ) ,
13521455 } ) ,
1456+ hrn_config : HumanReadableNamesConfig :: default ( ) ,
13531457 } ;
13541458
13551459 assert_eq ! ( config. listening_addrs, expected. listening_addrs) ;
@@ -1501,4 +1605,113 @@ mod tests {
15011605 let err = result. unwrap_err ( ) ;
15021606 assert_eq ! ( err. kind( ) , io:: ErrorKind :: InvalidInput ) ;
15031607 }
1608+
1609+ #[ test]
1610+ fn test_hrn_config ( ) {
1611+ let storage_path = std:: env:: temp_dir ( ) ;
1612+ let config_file_name = "test_hrn_config.toml" ;
1613+
1614+ let base_config = r#"
1615+ [node]
1616+ network = "regtest"
1617+
1618+ [bitcoind]
1619+ rpc_address = "127.0.0.1:8332"
1620+ rpc_user = "bitcoind-testuser"
1621+ rpc_password = "bitcoind-testpassword"
1622+
1623+ [liquidity.lsps2_service]
1624+ advertise_service = false
1625+ channel_opening_fee_ppm = 1000
1626+ channel_over_provisioning_ppm = 500000
1627+ min_channel_opening_fee_msat = 10000000
1628+ min_channel_lifetime = 4320
1629+ max_client_to_self_delay = 1440
1630+ min_payment_size_msat = 10000000
1631+ max_payment_size_msat = 25000000000
1632+ client_trusts_lsp = true
1633+ disable_client_reserve = false
1634+ "# ;
1635+
1636+ let mut args_config = empty_args_config ( ) ;
1637+ args_config. config_file =
1638+ Some ( storage_path. join ( config_file_name) . to_string_lossy ( ) . to_string ( ) ) ;
1639+
1640+ // Default: no `[hrn]` section -> DNS against 8.8.8.8:53, resolution service disabled.
1641+ fs:: write ( storage_path. join ( config_file_name) , base_config) . unwrap ( ) ;
1642+ let config = load_config ( & args_config) . unwrap ( ) ;
1643+ match config. hrn_config . resolution_config {
1644+ HRNResolverConfig :: Dns { dns_server_address, enable_hrn_resolution_service } => {
1645+ assert_eq ! ( dns_server_address, SocketAddress :: from_str( "8.8.8.8:53" ) . unwrap( ) ) ;
1646+ assert ! ( !enable_hrn_resolution_service) ;
1647+ } ,
1648+ other => panic ! ( "unexpected default HRN resolver config: {:?}" , other) ,
1649+ }
1650+
1651+ // Custom DNS server address with resolution service enabled.
1652+ let toml_config = format ! (
1653+ "{}\n [hrn]\n dns_server_address = \" 1.1.1.1:53\" \n enable_resolution_service = true\n " ,
1654+ base_config
1655+ ) ;
1656+ fs:: write ( storage_path. join ( config_file_name) , & toml_config) . unwrap ( ) ;
1657+ let config = load_config ( & args_config) . unwrap ( ) ;
1658+ match config. hrn_config . resolution_config {
1659+ HRNResolverConfig :: Dns { dns_server_address, enable_hrn_resolution_service } => {
1660+ assert_eq ! ( dns_server_address, SocketAddress :: from_str( "1.1.1.1:53" ) . unwrap( ) ) ;
1661+ assert ! ( enable_hrn_resolution_service) ;
1662+ } ,
1663+ other => panic ! ( "unexpected HRN resolver config: {:?}" , other) ,
1664+ }
1665+
1666+ // Blip32 mode.
1667+ let toml_config = format ! ( "{}\n [hrn]\n mode = \" blip32\" \n " , base_config) ;
1668+ fs:: write ( storage_path. join ( config_file_name) , & toml_config) . unwrap ( ) ;
1669+ let config = load_config ( & args_config) . unwrap ( ) ;
1670+ assert ! ( matches!( config. hrn_config. resolution_config, HRNResolverConfig :: Blip32 ) ) ;
1671+
1672+ // Invalid mode is rejected.
1673+ let toml_config = format ! ( "{}\n [hrn]\n mode = \" bogus\" \n " , base_config) ;
1674+ fs:: write ( storage_path. join ( config_file_name) , & toml_config) . unwrap ( ) ;
1675+ let err = load_config ( & args_config) . unwrap_err ( ) ;
1676+ assert_eq ! ( err. kind( ) , io:: ErrorKind :: InvalidInput ) ;
1677+
1678+ // Invalid DNS server address is rejected (contains chars disallowed in hostnames, so
1679+ // neither the as-is parse nor the `:53` fallback can accept it).
1680+ let toml_config =
1681+ format ! ( "{}\n [hrn]\n dns_server_address = \" invalid@address\" \n " , base_config) ;
1682+ fs:: write ( storage_path. join ( config_file_name) , & toml_config) . unwrap ( ) ;
1683+ let err = load_config ( & args_config) . unwrap_err ( ) ;
1684+ assert_eq ! ( err. kind( ) , io:: ErrorKind :: InvalidInput ) ;
1685+
1686+ // DNS server address without an explicit port defaults to port 53.
1687+ let toml_config = format ! ( "{}\n [hrn]\n dns_server_address = \" 1.1.1.1\" \n " , base_config) ;
1688+ fs:: write ( storage_path. join ( config_file_name) , & toml_config) . unwrap ( ) ;
1689+ let config = load_config ( & args_config) . unwrap ( ) ;
1690+ match config. hrn_config . resolution_config {
1691+ HRNResolverConfig :: Dns { dns_server_address, .. } => {
1692+ assert_eq ! ( dns_server_address, SocketAddress :: from_str( "1.1.1.1:53" ) . unwrap( ) ) ;
1693+ } ,
1694+ other => panic ! ( "unexpected HRN resolver config: {:?}" , other) ,
1695+ }
1696+
1697+ // `blip32` mode combined with DNS-only settings is rejected so users aren't confused
1698+ // by settings that would silently have no effect.
1699+ let toml_config = format ! (
1700+ "{}\n [hrn]\n mode = \" blip32\" \n dns_server_address = \" 1.1.1.1:53\" \n " ,
1701+ base_config
1702+ ) ;
1703+ fs:: write ( storage_path. join ( config_file_name) , & toml_config) . unwrap ( ) ;
1704+ let err = load_config ( & args_config) . unwrap_err ( ) ;
1705+ assert_eq ! ( err. kind( ) , io:: ErrorKind :: InvalidInput ) ;
1706+ assert ! ( err. to_string( ) . contains( "dns_server_address" ) ) ;
1707+
1708+ let toml_config = format ! (
1709+ "{}\n [hrn]\n mode = \" blip32\" \n enable_resolution_service = true\n " ,
1710+ base_config
1711+ ) ;
1712+ fs:: write ( storage_path. join ( config_file_name) , & toml_config) . unwrap ( ) ;
1713+ let err = load_config ( & args_config) . unwrap_err ( ) ;
1714+ assert_eq ! ( err. kind( ) , io:: ErrorKind :: InvalidInput ) ;
1715+ assert ! ( err. to_string( ) . contains( "enable_resolution_service" ) ) ;
1716+ }
15041717}
0 commit comments