Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ fn main() -> eyre::Result<()> {
mining_config.gas_limit = Some(gas_limit);
}

if let Err(err) = mining_config.derive_validator_address_from_keys() {
return Err(eyre::eyre!(err));
}

// Ensure keys are available if enabled but none provided
mining_config = mining_config.ensure_keys_available();

Expand Down
95 changes: 66 additions & 29 deletions src/node/miner/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,33 @@ impl MiningConfig {
}.ensure_keys_available()
}

/// Ensures `validator_address` is populated by deriving it from the configured key material.
/// Returns an error if a key source is configured but cannot be decoded.
pub fn derive_validator_address_from_keys(&mut self) -> Result<(), String> {
if self.validator_address.is_some() {
return Ok(());
}

if let Some(ref pk_hex) = self.private_key_hex {
let signing_key = keystore::load_private_key_from_hex(pk_hex)
.map_err(|err| format!("Failed to load mining private key: {err}"))?;
self.validator_address = Some(keystore::get_validator_address(&signing_key));
return Ok(());
}

if let Some(ref path) = self.keystore_path {
let password = self
.keystore_password
.as_deref()
.ok_or_else(|| "Keystore password must be provided when deriving validator address".to_string())?;
let signing_key = keystore::load_private_key_from_keystore(path, password)
.map_err(|err| format!("Failed to decrypt keystore {}: {err}", path.display()))?;
self.validator_address = Some(keystore::get_validator_address(&signing_key));
}

Ok(())
}

/// Load configuration from environment variables
pub fn from_env() -> Self {
let enabled = std::env::var("BSC_MINING_ENABLED")
Expand Down Expand Up @@ -197,17 +224,8 @@ impl MiningConfig {
..Default::default()
};

// If a private key is present but validator_address is not, derive it automatically.
if cfg.validator_address.is_none() {
if let Some(ref pk_hex) = cfg.private_key_hex {
if let Ok(sk) = keystore::load_private_key_from_hex(pk_hex) {
cfg.validator_address = Some(keystore::get_validator_address(&sk));
}
} else if let (Some(ref path), Some(ref pass)) = (&cfg.keystore_path, &cfg.keystore_password) {
if let Ok(sk) = keystore::load_private_key_from_keystore(path, pass) {
cfg.validator_address = Some(keystore::get_validator_address(&sk));
}
}
if let Err(err) = cfg.derive_validator_address_from_keys() {
tracing::warn!("Failed to derive validator address from configured keys: {err}");
}

cfg.ensure_keys_available()
Expand Down Expand Up @@ -286,6 +304,23 @@ pub mod keystore {
#[cfg(test)]
mod tests {
use super::*;
use std::{fs, io::Write, path::PathBuf};
use uuid::Uuid;

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}"#;
const SAMPLE_KEYSTORE_PASSWORD: &str = "0123456789";
const SAMPLE_KEYSTORE_ADDRESS: &str = "0xbcdd0d2cda5f6423e57b6a4dcd75decbe31aecf0";

fn write_sample_keystore() -> PathBuf {
let mut path: PathBuf = std::env::temp_dir();
let fname =
format!("UTC--test-{}--bcdd0d2cda5f6423e57b6a4dcd75decbe31aecf0", Uuid::new_v4());
path.push(fname);
let mut file = fs::File::create(&path).unwrap();
file.write_all(SAMPLE_KEYSTORE_JSON.as_bytes()).unwrap();
file.sync_all().unwrap();
path
}

#[test]
fn test_mining_config_validation() {
Expand Down Expand Up @@ -314,37 +349,39 @@ mod tests {

#[test]
fn test_load_private_key_from_keystore_file() {
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use uuid::Uuid;

// This is a real V3 keystore JSON (address bcdd0d2c...) with password "0123456789"
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}"#;

// Write to a temporary file
let mut path: PathBuf = std::env::temp_dir();
let fname = format!("UTC--test-{}--bcdd0d2cda5f6423e57b6a4dcd75decbe31aecf0", Uuid::new_v4());
path.push(fname);
{
let mut f = fs::File::create(&path).unwrap();
f.write_all(keystore_json.as_bytes()).unwrap();
f.sync_all().unwrap();
}
let path = write_sample_keystore();

// Decrypt with the known password
let signing_key = keystore::load_private_key_from_keystore(&path, "0123456789").unwrap();
let signing_key =
keystore::load_private_key_from_keystore(&path, SAMPLE_KEYSTORE_PASSWORD).unwrap();
// convert signing_key to hex string
let signing_key_hex = alloy_primitives::hex::encode(signing_key.to_bytes());
println!("signing_key: 0x{}", signing_key_hex);

let address = keystore::get_validator_address(&signing_key);

// Expect derived address to match the keystore address
let expected: Address = "0xbcdd0d2cda5f6423e57b6a4dcd75decbe31aecf0".parse().unwrap();
let expected: Address = SAMPLE_KEYSTORE_ADDRESS.parse().unwrap();
assert_eq!(address, expected);

// Cleanup best-effort
let _ = fs::remove_file(&path);
}

#[test]
fn derive_validator_address_from_keystore_config() {
let path = write_sample_keystore();
let mut cfg = MiningConfig {
enabled: true,
validator_address: None,
keystore_path: Some(path.clone()),
keystore_password: Some(SAMPLE_KEYSTORE_PASSWORD.to_string()),
..Default::default()
};

cfg.derive_validator_address_from_keys().unwrap();
assert_eq!(cfg.validator_address, Some(SAMPLE_KEYSTORE_ADDRESS.parse().unwrap()));
let _ = fs::remove_file(path);
}
}
40 changes: 5 additions & 35 deletions src/rpc/mev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ use alloy_primitives::Address;
use alloy_consensus::BlobTransactionSidecar;
use alloy_consensus::{TxEip4844WithSidecar, transaction::RlpEcdsaDecodableTx};
use crate::node::miner::config::MiningConfig;
use crate::node::miner::config::keystore;

/// Raw bid data structure from builder
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
Expand Down Expand Up @@ -90,39 +89,10 @@ impl MevApiImpl {
MiningConfig::from_env()
};

let mut validator_address = mining_config.validator_address.unwrap_or(Address::ZERO);

// Try to load signing key and derive validator address
if let Some(keystore_path) = &mining_config.keystore_path {
let password = mining_config.keystore_password.as_deref().unwrap_or("");
if let Ok(signing_key) = keystore::load_private_key_from_keystore(keystore_path, password) {
let derived_address = keystore::get_validator_address(&signing_key);
if derived_address != validator_address && validator_address != Address::ZERO {
tracing::warn!(
"Validator address mismatch, configured: {}, derived: {}",
validator_address, derived_address
);
}
validator_address = derived_address;
tracing::info!("Derived validator address from keystore: {}", derived_address);
} else {
tracing::warn!("Failed to load signing key from keystore");
}
} else if let Some(hex_key) = &mining_config.private_key_hex {
if let Ok(signing_key) = keystore::load_private_key_from_hex(hex_key) {
let derived_address = keystore::get_validator_address(&signing_key);
if derived_address != validator_address && validator_address != Address::ZERO {
tracing::warn!(
"Validator address mismatch, configured: {}, derived: {}",
validator_address, derived_address
);
}
validator_address = derived_address;
tracing::info!("Derived validator address from hex key: {}", derived_address);
} else {
tracing::warn!("Failed to load signing key from hex");
}
}
let validator_address = mining_config.validator_address.unwrap_or_else(|| {
tracing::warn!("Validator address not configured; MEV API will default to Address::ZERO");
Address::ZERO
});

Self { snapshot_provider, chain_spec, validator_address }
}
Expand Down Expand Up @@ -625,4 +595,4 @@ impl BscMevApiServer for MevApiImpl {

Ok(bid_hash)
}
}
}
Loading