@@ -69,13 +69,14 @@ pub struct Config {
6969
7070/// Configuration for the node's entropy source.
7171///
72- /// When both `mnemonic_file` and `seed_file` are unset, the node defaults to loading or
73- /// generating a BIP39 mnemonic at `<storage_dir>/keys_mnemonic`. If a legacy raw-seed file
74- /// exists at `<storage_dir>/keys_seed`, it is used for backwards compatibility.
72+ /// When unset, the node defaults to loading or generating a BIP39 mnemonic at
73+ /// `<storage_dir>/keys_mnemonic`.
7574#[ derive( Debug , Default , Clone , PartialEq , Eq ) ]
76- pub struct EntropyConfig {
77- pub mnemonic_file : Option < String > ,
78- pub seed_file : Option < String > ,
75+ pub enum EntropyConfig {
76+ #[ default]
77+ Default ,
78+ MnemonicFile ( String ) ,
79+ SeedFile ( String ) ,
7980}
8081
8182#[ derive( Debug , Clone , PartialEq , Eq ) ]
@@ -135,7 +136,7 @@ struct ConfigBuilder {
135136}
136137
137138impl ConfigBuilder {
138- fn merge_toml ( & mut self , toml : TomlConfig ) {
139+ fn merge_toml ( & mut self , toml : TomlConfig ) -> io :: Result < ( ) > {
139140 if let Some ( node) = toml. node {
140141 self . network = node. network . or ( self . network ) ;
141142 self . listening_addresses =
@@ -151,9 +152,7 @@ impl ConfigBuilder {
151152 node. async_payments_role . or ( self . async_payments_role . clone ( ) ) ;
152153 self . rgs_server_url = node. rgs_server_url . or ( self . rgs_server_url . clone ( ) ) ;
153154 if let Some ( entropy) = node. entropy {
154- self . entropy . mnemonic_file =
155- entropy. mnemonic_file . or ( self . entropy . mnemonic_file . take ( ) ) ;
156- self . entropy . seed_file = entropy. seed_file . or ( self . entropy . seed_file . take ( ) ) ;
155+ self . entropy = EntropyConfig :: from_paths ( entropy. mnemonic_file , entropy. seed_file ) ?;
157156 }
158157 }
159158
@@ -209,9 +208,11 @@ impl ConfigBuilder {
209208 if let Some ( hrn) = toml. hrn {
210209 self . hrn = Some ( hrn) ;
211210 }
211+
212+ Ok ( ( ) )
212213 }
213214
214- fn merge_args ( & mut self , args : & ArgsConfig ) {
215+ fn merge_args ( & mut self , args : & ArgsConfig ) -> io :: Result < ( ) > {
215216 if let Some ( network) = args. node_network {
216217 self . network = Some ( network) ;
217218 }
@@ -275,6 +276,15 @@ impl ConfigBuilder {
275276 if let Some ( tor_proxy_address) = & args. tor_proxy_address {
276277 self . tor_proxy_address = Some ( tor_proxy_address. clone ( ) ) ;
277278 }
279+
280+ if args. node_entropy_mnemonic_file . is_some ( ) || args. node_entropy_seed_file . is_some ( ) {
281+ self . entropy = EntropyConfig :: from_paths (
282+ args. node_entropy_mnemonic_file . clone ( ) ,
283+ args. node_entropy_seed_file . clone ( ) ,
284+ ) ?;
285+ }
286+
287+ Ok ( ( ) )
278288 }
279289
280290 fn build ( self ) -> io:: Result < Config > {
@@ -450,13 +460,6 @@ impl ConfigBuilder {
450460 None => HumanReadableNamesConfig :: default ( ) ,
451461 } ;
452462
453- if self . entropy . mnemonic_file . is_some ( ) && self . entropy . seed_file . is_some ( ) {
454- return Err ( io:: Error :: new (
455- io:: ErrorKind :: InvalidInput ,
456- "Only one of `node.entropy.mnemonic_file` and `node.entropy.seed_file` may be configured." . to_string ( ) ,
457- ) ) ;
458- }
459-
460463 Ok ( Config {
461464 network,
462465 listening_addrs,
@@ -484,6 +487,20 @@ impl ConfigBuilder {
484487 }
485488}
486489
490+ impl EntropyConfig {
491+ fn from_paths ( mnemonic_file : Option < String > , seed_file : Option < String > ) -> io:: Result < Self > {
492+ match ( mnemonic_file, seed_file) {
493+ ( Some ( _) , Some ( _) ) => Err ( io:: Error :: new (
494+ io:: ErrorKind :: InvalidInput ,
495+ "Only one of `node.entropy.mnemonic_file`/`--node-entropy-mnemonic-file` and `node.entropy.seed_file`/`--node-entropy-seed-file` may be configured." . to_string ( ) ,
496+ ) ) ,
497+ ( Some ( mnemonic_file) , None ) => Ok ( Self :: MnemonicFile ( mnemonic_file) ) ,
498+ ( None , Some ( seed_file) ) => Ok ( Self :: SeedFile ( seed_file) ) ,
499+ ( None , None ) => Ok ( Self :: Default ) ,
500+ }
501+ }
502+ }
503+
487504/// Configuration loaded from a TOML file.
488505#[ derive( Deserialize , Serialize ) ]
489506#[ serde( deny_unknown_fields) ]
@@ -861,6 +878,20 @@ pub struct ArgsConfig {
861878 ) ]
862879 node_async_payments_role : Option < String > ,
863880
881+ #[ arg(
882+ long,
883+ env = "LDK_SERVER_NODE_ENTROPY_MNEMONIC_FILE" ,
884+ help = "Path to the BIP39 mnemonic file used for node entropy."
885+ ) ]
886+ node_entropy_mnemonic_file : Option < String > ,
887+
888+ #[ arg(
889+ long,
890+ env = "LDK_SERVER_NODE_ENTROPY_SEED_FILE" ,
891+ help = "Legacy path to a raw 64-byte seed file used for node entropy."
892+ ) ]
893+ node_entropy_seed_file : Option < String > ,
894+
864895 #[ arg(
865896 long,
866897 env = "LDK_SERVER_METRICS_ENABLED" ,
@@ -918,10 +949,10 @@ pub fn load_config(args: &ArgsConfig) -> io::Result<Config> {
918949 )
919950 } ) ?;
920951
921- builder. merge_toml ( toml_config) ;
952+ builder. merge_toml ( toml_config) ? ;
922953 }
923954
924- builder. merge_args ( args) ;
955+ builder. merge_args ( args) ? ;
925956
926957 builder. build ( )
927958}
@@ -961,8 +992,10 @@ fn parse_host_port(addr: &str) -> io::Result<(String, u16)> {
961992
962993#[ cfg( test) ]
963994mod tests {
995+ use std:: ffi:: OsString ;
964996 use std:: str:: FromStr ;
965997
998+ use clap:: Parser ;
966999 use ldk_node:: bitcoin:: secp256k1:: PublicKey ;
9671000 use ldk_node:: bitcoin:: Network ;
9681001 use ldk_node:: lightning:: ln:: msgs:: SocketAddress ;
@@ -1036,6 +1069,8 @@ mod tests {
10361069 metrics_username : None ,
10371070 metrics_password : None ,
10381071 tor_proxy_address : None ,
1072+ node_entropy_mnemonic_file : None ,
1073+ node_entropy_seed_file : None ,
10391074 }
10401075 }
10411076
@@ -1058,6 +1093,8 @@ mod tests {
10581093 metrics_username : None ,
10591094 metrics_password : None ,
10601095 tor_proxy_address : None ,
1096+ node_entropy_mnemonic_file : None ,
1097+ node_entropy_seed_file : None ,
10611098 }
10621099 }
10631100
@@ -1068,6 +1105,13 @@ mod tests {
10681105 )
10691106 }
10701107
1108+ fn restore_env_var ( key : & str , old_value : Option < OsString > ) {
1109+ match old_value {
1110+ Some ( value) => std:: env:: set_var ( key, value) ,
1111+ None => std:: env:: remove_var ( key) ,
1112+ }
1113+ }
1114+
10711115 #[ test]
10721116 fn test_config_from_file ( ) {
10731117 let storage_path = std:: env:: temp_dir ( ) ;
@@ -1923,8 +1967,10 @@ mod tests {
19231967 Some ( storage_path. join ( config_file_name) . to_string_lossy ( ) . to_string ( ) ) ;
19241968
19251969 let config = load_config ( & args_config) . unwrap ( ) ;
1926- assert_eq ! ( config. entropy. mnemonic_file, Some ( "/some/path/keys_mnemonic" . to_string( ) ) ) ;
1927- assert_eq ! ( config. entropy. seed_file, None ) ;
1970+ assert_eq ! (
1971+ config. entropy,
1972+ EntropyConfig :: MnemonicFile ( "/some/path/keys_mnemonic" . to_string( ) )
1973+ ) ;
19281974 }
19291975
19301976 #[ test]
@@ -1956,4 +2002,62 @@ mod tests {
19562002 assert_eq ! ( err. kind( ) , io:: ErrorKind :: InvalidInput ) ;
19572003 assert ! ( err. to_string( ) . contains( "Only one of" ) ) ;
19582004 }
2005+
2006+ #[ test]
2007+ fn test_node_entropy_args_override_file ( ) {
2008+ let storage_path = std:: env:: temp_dir ( ) ;
2009+ let config_file_name = "test_node_entropy_args_override_file.toml" ;
2010+
2011+ let toml_config = r#"
2012+ [node]
2013+ network = "regtest"
2014+ grpc_service_address = "127.0.0.1:3002"
2015+
2016+ [node.entropy]
2017+ mnemonic_file = "/config/keys_mnemonic"
2018+
2019+ [bitcoind]
2020+ rpc_address = "127.0.0.1:8332"
2021+ rpc_user = "bitcoind-testuser"
2022+ rpc_password = "bitcoind-testpassword"
2023+ "# ;
2024+
2025+ fs:: write ( storage_path. join ( config_file_name) , toml_config) . unwrap ( ) ;
2026+ let mut args_config = empty_args_config ( ) ;
2027+ args_config. config_file =
2028+ Some ( storage_path. join ( config_file_name) . to_string_lossy ( ) . to_string ( ) ) ;
2029+ args_config. node_entropy_seed_file = Some ( "/legacy/keys_seed" . to_string ( ) ) ;
2030+
2031+ let config = load_config ( & args_config) . unwrap ( ) ;
2032+ assert_eq ! ( config. entropy, EntropyConfig :: SeedFile ( "/legacy/keys_seed" . to_string( ) ) ) ;
2033+ }
2034+
2035+ #[ test]
2036+ fn test_rejects_both_entropy_args ( ) {
2037+ let mut args_config = default_args_config ( ) ;
2038+ args_config. node_entropy_mnemonic_file = Some ( "/some/path/keys_mnemonic" . to_string ( ) ) ;
2039+ args_config. node_entropy_seed_file = Some ( "/some/path/keys_seed" . to_string ( ) ) ;
2040+
2041+ let err = load_config ( & args_config) . unwrap_err ( ) ;
2042+ assert_eq ! ( err. kind( ) , io:: ErrorKind :: InvalidInput ) ;
2043+ assert ! ( err. to_string( ) . contains( "Only one of" ) ) ;
2044+ }
2045+
2046+ #[ test]
2047+ fn test_node_entropy_mnemonic_file_env ( ) {
2048+ let mnemonic_key = "LDK_SERVER_NODE_ENTROPY_MNEMONIC_FILE" ;
2049+ let seed_key = "LDK_SERVER_NODE_ENTROPY_SEED_FILE" ;
2050+ let old_mnemonic = std:: env:: var_os ( mnemonic_key) ;
2051+ let old_seed = std:: env:: var_os ( seed_key) ;
2052+
2053+ std:: env:: set_var ( mnemonic_key, "/env/keys_mnemonic" ) ;
2054+ std:: env:: remove_var ( seed_key) ;
2055+ let parse_result = ArgsConfig :: try_parse_from ( [ "ldk-server" ] ) ;
2056+ restore_env_var ( mnemonic_key, old_mnemonic) ;
2057+ restore_env_var ( seed_key, old_seed) ;
2058+
2059+ let args_config = parse_result. unwrap ( ) ;
2060+ assert_eq ! ( args_config. node_entropy_mnemonic_file, Some ( "/env/keys_mnemonic" . to_string( ) ) ) ;
2061+ assert_eq ! ( args_config. node_entropy_seed_file, None ) ;
2062+ }
19592063}
0 commit comments