@@ -162,6 +162,33 @@ impl MiningConfig {
162162 } . ensure_keys_available ( )
163163 }
164164
165+ /// Ensures `validator_address` is populated by deriving it from the configured key material.
166+ /// Returns an error if a key source is configured but cannot be decoded.
167+ pub fn derive_validator_address_from_keys ( & mut self ) -> Result < ( ) , String > {
168+ if self . validator_address . is_some ( ) {
169+ return Ok ( ( ) ) ;
170+ }
171+
172+ if let Some ( ref pk_hex) = self . private_key_hex {
173+ let signing_key = keystore:: load_private_key_from_hex ( pk_hex)
174+ . map_err ( |err| format ! ( "Failed to load mining private key: {err}" ) ) ?;
175+ self . validator_address = Some ( keystore:: get_validator_address ( & signing_key) ) ;
176+ return Ok ( ( ) ) ;
177+ }
178+
179+ if let Some ( ref path) = self . keystore_path {
180+ let password = self
181+ . keystore_password
182+ . as_deref ( )
183+ . ok_or_else ( || "Keystore password must be provided when deriving validator address" . to_string ( ) ) ?;
184+ let signing_key = keystore:: load_private_key_from_keystore ( path, password)
185+ . map_err ( |err| format ! ( "Failed to decrypt keystore {}: {err}" , path. display( ) ) ) ?;
186+ self . validator_address = Some ( keystore:: get_validator_address ( & signing_key) ) ;
187+ }
188+
189+ Ok ( ( ) )
190+ }
191+
165192 /// Load configuration from environment variables
166193 pub fn from_env ( ) -> Self {
167194 let enabled = std:: env:: var ( "BSC_MINING_ENABLED" )
@@ -197,17 +224,8 @@ impl MiningConfig {
197224 ..Default :: default ( )
198225 } ;
199226
200- // If a private key is present but validator_address is not, derive it automatically.
201- if cfg. validator_address . is_none ( ) {
202- if let Some ( ref pk_hex) = cfg. private_key_hex {
203- if let Ok ( sk) = keystore:: load_private_key_from_hex ( pk_hex) {
204- cfg. validator_address = Some ( keystore:: get_validator_address ( & sk) ) ;
205- }
206- } else if let ( Some ( ref path) , Some ( ref pass) ) = ( & cfg. keystore_path , & cfg. keystore_password ) {
207- if let Ok ( sk) = keystore:: load_private_key_from_keystore ( path, pass) {
208- cfg. validator_address = Some ( keystore:: get_validator_address ( & sk) ) ;
209- }
210- }
227+ if let Err ( err) = cfg. derive_validator_address_from_keys ( ) {
228+ tracing:: warn!( "Failed to derive validator address from configured keys: {err}" ) ;
211229 }
212230
213231 cfg. ensure_keys_available ( )
@@ -286,6 +304,23 @@ pub mod keystore {
286304#[ cfg( test) ]
287305mod tests {
288306 use super :: * ;
307+ use std:: { fs, io:: Write , path:: PathBuf } ;
308+ use uuid:: Uuid ;
309+
310+ const SAMPLE_KEYSTORE_JSON : & str = r#"{"address":"bcdd0d2cda5f6423e57b6a4dcd75decbe31aecf0","crypto":{"cipher":"aes-128-ctr","ciphertext":"f7505ced32fe3037d6dc25ae7e9716858e516bacfb0dc28c9995f01cf7fee84a","cipherparams":{"iv":"d93e5314f18ccfc8330e1ad37534fa29"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"1fb8f954f70fb0ddb8be5b1fb57d821df21dda700682f382baed311dde95a6c7"},"mac":"c501bb033f94cb9efc3c63fe1547555fc1412d3abb8b400cacda37bd9de888ec"},"id":"7246dc9c-d170-4009-8878-8628be169836","version":3}"# ;
311+ const SAMPLE_KEYSTORE_PASSWORD : & str = "0123456789" ;
312+ const SAMPLE_KEYSTORE_ADDRESS : & str = "0xbcdd0d2cda5f6423e57b6a4dcd75decbe31aecf0" ;
313+
314+ fn write_sample_keystore ( ) -> PathBuf {
315+ let mut path: PathBuf = std:: env:: temp_dir ( ) ;
316+ let fname =
317+ format ! ( "UTC--test-{}--bcdd0d2cda5f6423e57b6a4dcd75decbe31aecf0" , Uuid :: new_v4( ) ) ;
318+ path. push ( fname) ;
319+ let mut file = fs:: File :: create ( & path) . unwrap ( ) ;
320+ file. write_all ( SAMPLE_KEYSTORE_JSON . as_bytes ( ) ) . unwrap ( ) ;
321+ file. sync_all ( ) . unwrap ( ) ;
322+ path
323+ }
289324
290325 #[ test]
291326 fn test_mining_config_validation ( ) {
@@ -314,37 +349,39 @@ mod tests {
314349
315350 #[ test]
316351 fn test_load_private_key_from_keystore_file ( ) {
317- use std:: fs;
318- use std:: io:: Write ;
319- use std:: path:: PathBuf ;
320- use uuid:: Uuid ;
321-
322- // This is a real V3 keystore JSON (address bcdd0d2c...) with password "0123456789"
323- let keystore_json = r#"{"address":"bcdd0d2cda5f6423e57b6a4dcd75decbe31aecf0","crypto":{"cipher":"aes-128-ctr","ciphertext":"f7505ced32fe3037d6dc25ae7e9716858e516bacfb0dc28c9995f01cf7fee84a","cipherparams":{"iv":"d93e5314f18ccfc8330e1ad37534fa29"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"1fb8f954f70fb0ddb8be5b1fb57d821df21dda700682f382baed311dde95a6c7"},"mac":"c501bb033f94cb9efc3c63fe1547555fc1412d3abb8b400cacda37bd9de888ec"},"id":"7246dc9c-d170-4009-8878-8628be169836","version":3}"# ;
324-
325352 // Write to a temporary file
326- let mut path: PathBuf = std:: env:: temp_dir ( ) ;
327- let fname = format ! ( "UTC--test-{}--bcdd0d2cda5f6423e57b6a4dcd75decbe31aecf0" , Uuid :: new_v4( ) ) ;
328- path. push ( fname) ;
329- {
330- let mut f = fs:: File :: create ( & path) . unwrap ( ) ;
331- f. write_all ( keystore_json. as_bytes ( ) ) . unwrap ( ) ;
332- f. sync_all ( ) . unwrap ( ) ;
333- }
353+ let path = write_sample_keystore ( ) ;
334354
335355 // Decrypt with the known password
336- let signing_key = keystore:: load_private_key_from_keystore ( & path, "0123456789" ) . unwrap ( ) ;
356+ let signing_key =
357+ keystore:: load_private_key_from_keystore ( & path, SAMPLE_KEYSTORE_PASSWORD ) . unwrap ( ) ;
337358 // convert signing_key to hex string
338359 let signing_key_hex = alloy_primitives:: hex:: encode ( signing_key. to_bytes ( ) ) ;
339360 println ! ( "signing_key: 0x{}" , signing_key_hex) ;
340361
341362 let address = keystore:: get_validator_address ( & signing_key) ;
342363
343364 // Expect derived address to match the keystore address
344- let expected: Address = "0xbcdd0d2cda5f6423e57b6a4dcd75decbe31aecf0" . parse ( ) . unwrap ( ) ;
365+ let expected: Address = SAMPLE_KEYSTORE_ADDRESS . parse ( ) . unwrap ( ) ;
345366 assert_eq ! ( address, expected) ;
346367
347368 // Cleanup best-effort
348369 let _ = fs:: remove_file ( & path) ;
349370 }
371+
372+ #[ test]
373+ fn derive_validator_address_from_keystore_config ( ) {
374+ let path = write_sample_keystore ( ) ;
375+ let mut cfg = MiningConfig {
376+ enabled : true ,
377+ validator_address : None ,
378+ keystore_path : Some ( path. clone ( ) ) ,
379+ keystore_password : Some ( SAMPLE_KEYSTORE_PASSWORD . to_string ( ) ) ,
380+ ..Default :: default ( )
381+ } ;
382+
383+ cfg. derive_validator_address_from_keys ( ) . unwrap ( ) ;
384+ assert_eq ! ( cfg. validator_address, Some ( SAMPLE_KEYSTORE_ADDRESS . parse( ) . unwrap( ) ) ) ;
385+ let _ = fs:: remove_file ( path) ;
386+ }
350387}
0 commit comments