use crate::config::{
config, GlobalWalletConfig, GlobalWalletConfigMembers, TorConfig, WalletConfig, GRIN_WALLET_DIR,
};
use crate::core::global;
use crate::keychain::Keychain;
use crate::libwallet::{Error, NodeClient, WalletBackend, WalletInitStatus, WalletLCProvider};
use crate::lifecycle::seed::WalletSeed;
use crate::util::secp::key::SecretKey;
use crate::util::ZeroingString;
use crate::LMDBBackend;
use grin_util::logger::LoggingConfig;
use std::fs;
use std::path::PathBuf;
pub struct DefaultLCProvider<'a, C, K>
where
C: NodeClient + 'a,
K: Keychain + 'a,
{
data_dir: String,
node_client: C,
backend: Option<Box<dyn WalletBackend<'a, C, K> + 'a>>,
}
impl<'a, C, K> DefaultLCProvider<'a, C, K>
where
C: NodeClient + 'a,
K: Keychain + 'a,
{
pub fn new(node_client: C) -> Self {
DefaultLCProvider {
node_client,
data_dir: "default".to_owned(),
backend: None,
}
}
}
impl<'a, C, K> WalletLCProvider<'a, C, K> for DefaultLCProvider<'a, C, K>
where
C: NodeClient + 'a,
K: Keychain + 'a,
{
fn set_top_level_directory(&mut self, dir: &str) -> Result<(), Error> {
self.data_dir = dir.to_owned();
Ok(())
}
fn get_top_level_directory(&self) -> Result<String, Error> {
Ok(self.data_dir.to_owned())
}
fn create_config(
&self,
chain_type: &global::ChainTypes,
file_name: &str,
wallet_config: Option<WalletConfig>,
logging_config: Option<LoggingConfig>,
tor_config: Option<TorConfig>,
) -> Result<(), Error> {
let mut default_config = GlobalWalletConfig::for_chain(&chain_type);
let config_file_version = match default_config.members.as_ref() {
Some(m) => m.clone().config_file_version,
None => None,
};
let logging = match logging_config {
Some(l) => Some(l),
None => match default_config.members.as_ref() {
Some(m) => m.clone().logging,
None => None,
},
};
let wallet = match wallet_config {
Some(w) => w,
None => match default_config.members.as_ref() {
Some(m) => m.clone().wallet,
None => WalletConfig::default(),
},
};
let tor = match tor_config {
Some(t) => Some(t),
None => match default_config.members.as_ref() {
Some(m) => m.clone().tor,
None => Some(TorConfig::default()),
},
};
default_config = GlobalWalletConfig {
members: Some(GlobalWalletConfigMembers {
config_file_version,
wallet,
tor,
logging,
}),
..default_config
};
let mut config_file_name = PathBuf::from(self.data_dir.clone());
config_file_name.push(file_name);
let dd = PathBuf::from(self.data_dir.clone());
if !dd.exists() {
fs::create_dir_all(dd)?;
}
let mut data_dir_name = PathBuf::from(self.data_dir.clone());
data_dir_name.push(GRIN_WALLET_DIR);
if config_file_name.exists() && data_dir_name.exists() {
let msg = format!(
"{} already exists in the target directory ({}). Please remove it first",
file_name,
config_file_name.to_str().unwrap()
);
return Err(Error::Lifecycle(msg));
}
if config_file_name.exists() {
return Ok(());
}
let mut abs_path = std::env::current_dir()?;
abs_path.push(self.data_dir.clone());
default_config.update_paths(&abs_path);
let res =
default_config.write_to_file(config_file_name.to_str().unwrap(), false, None, None);
if let Err(e) = res {
let msg = format!(
"Error creating config file as ({}): {}",
config_file_name.to_str().unwrap(),
e
);
return Err(Error::Lifecycle(msg));
}
info!(
"File {} configured and created",
config_file_name.to_str().unwrap(),
);
let mut api_secret_path = PathBuf::from(self.data_dir.clone());
api_secret_path.push(PathBuf::from(config::API_SECRET_FILE_NAME));
if !api_secret_path.exists() {
config::init_api_secret(&api_secret_path).unwrap();
} else {
config::check_api_secret(&api_secret_path).unwrap();
}
Ok(())
}
fn create_wallet(
&mut self,
_name: Option<&str>,
mnemonic: Option<ZeroingString>,
mnemonic_length: usize,
password: ZeroingString,
test_mode: bool,
) -> Result<(), Error> {
let mut data_dir_name = PathBuf::from(self.data_dir.clone());
data_dir_name.push(GRIN_WALLET_DIR);
let data_dir_name = data_dir_name.to_str().unwrap();
let exists = WalletSeed::seed_file_exists(&data_dir_name);
if !test_mode {
if let Ok(true) = exists {
let msg = format!("Wallet seed already exists at: {}", data_dir_name);
return Err(Error::WalletSeedExists(msg));
}
}
WalletSeed::init_file(
&data_dir_name,
mnemonic_length,
mnemonic.clone(),
password,
test_mode,
)
.map_err(|_| {
Error::Lifecycle("Error creating wallet seed (is mnemonic valid?)".to_owned())
})?;
info!("Wallet seed file created");
let mut wallet: LMDBBackend<'a, C, K> =
match LMDBBackend::new(&data_dir_name, self.node_client.clone()) {
Err(e) => {
let msg = format!("Error creating wallet: {}, Data Dir: {}", e, &data_dir_name);
error!("{}", msg);
return Err(Error::Lifecycle(msg).into());
}
Ok(d) => d,
};
let mut batch = wallet.batch_no_mask()?;
match mnemonic {
Some(_) => batch.save_init_status(WalletInitStatus::InitNeedsScanning)?,
None => batch.save_init_status(WalletInitStatus::InitNoScanning)?,
};
batch.commit()?;
info!("Wallet database backend created at {}", data_dir_name);
Ok(())
}
fn open_wallet(
&mut self,
_name: Option<&str>,
password: ZeroingString,
create_mask: bool,
use_test_rng: bool,
) -> Result<Option<SecretKey>, Error> {
let mut data_dir_name = PathBuf::from(self.data_dir.clone());
data_dir_name.push(GRIN_WALLET_DIR);
let data_dir_name = data_dir_name.to_str().unwrap();
let mut wallet: LMDBBackend<'a, C, K> =
match LMDBBackend::new(&data_dir_name, self.node_client.clone()) {
Err(e) => {
let msg = format!("Error opening wallet: {}, Data Dir: {}", e, &data_dir_name);
return Err(Error::Lifecycle(msg));
}
Ok(d) => d,
};
let wallet_seed = WalletSeed::from_file(&data_dir_name, password).map_err(|_| {
Error::Lifecycle("Error opening wallet (is password correct?)".to_owned())
})?;
let keychain = wallet_seed
.derive_keychain(global::is_testnet())
.map_err(|_| Error::Lifecycle("Error deriving keychain".to_owned()))?;
let mask = wallet.set_keychain(Box::new(keychain), create_mask, use_test_rng)?;
self.backend = Some(Box::new(wallet));
Ok(mask)
}
fn close_wallet(&mut self, _name: Option<&str>) -> Result<(), Error> {
if let Some(b) = self.backend.as_mut() {
b.close()?
}
self.backend = None;
Ok(())
}
fn wallet_exists(&self, _name: Option<&str>) -> Result<bool, Error> {
let mut data_dir_name = PathBuf::from(self.data_dir.clone());
data_dir_name.push(GRIN_WALLET_DIR);
let data_dir_name = data_dir_name.to_str().unwrap();
let res = WalletSeed::seed_file_exists(&data_dir_name)
.map_err(|_| Error::CallbackImpl("Error checking for wallet existence"))?;
Ok(res)
}
fn get_mnemonic(
&self,
_name: Option<&str>,
password: ZeroingString,
) -> Result<ZeroingString, Error> {
let mut data_dir_name = PathBuf::from(self.data_dir.clone());
data_dir_name.push(GRIN_WALLET_DIR);
let data_dir_name = data_dir_name.to_str().unwrap();
let wallet_seed = WalletSeed::from_file(&data_dir_name, password)
.map_err(|_| Error::Lifecycle("Error opening wallet seed file".into()))?;
let res = wallet_seed
.to_mnemonic()
.map_err(|_| Error::Lifecycle("Error recovering wallet seed".into()))?;
Ok(ZeroingString::from(res))
}
fn validate_mnemonic(&self, mnemonic: ZeroingString) -> Result<(), Error> {
match WalletSeed::from_mnemonic(mnemonic) {
Ok(_) => Ok(()),
Err(_) => Err(Error::GenericError("Validating mnemonic".into())),
}
}
fn recover_from_mnemonic(
&self,
mnemonic: ZeroingString,
password: ZeroingString,
) -> Result<(), Error> {
let mut data_dir_name = PathBuf::from(self.data_dir.clone());
data_dir_name.push(GRIN_WALLET_DIR);
let data_dir_name = data_dir_name.to_str().unwrap();
WalletSeed::recover_from_phrase(data_dir_name, mnemonic, password)
.map_err(|_| Error::Lifecycle("Error recovering from mnemonic".into()))?;
Ok(())
}
fn change_password(
&self,
_name: Option<&str>,
old: ZeroingString,
new: ZeroingString,
) -> Result<(), Error> {
let mut data_dir_name = PathBuf::from(self.data_dir.clone());
data_dir_name.push(GRIN_WALLET_DIR);
let data_dir_name = data_dir_name.to_str().unwrap();
let orig_wallet_seed = WalletSeed::from_file(&data_dir_name, old)
.map_err(|_| Error::Lifecycle("Error opening wallet seed file".into()))?;
let orig_mnemonic = orig_wallet_seed
.to_mnemonic()
.map_err(|_| Error::Lifecycle("Error recovering mnemonic".into()))?;
let backup_name = WalletSeed::backup_seed(data_dir_name)
.map_err(|_| Error::Lifecycle("Error temporarily backing up existing seed".into()))?;
WalletSeed::delete_seed_file(data_dir_name).map_err(|_| {
Error::Lifecycle("Unable to delete seed file for password change".into())
})?;
let _ = WalletSeed::init_file(
data_dir_name,
0,
Some(ZeroingString::from(orig_mnemonic)),
new.clone(),
false,
);
info!("Wallet seed file created");
let new_wallet_seed = WalletSeed::from_file(&data_dir_name, new)
.map_err(|_| Error::Lifecycle("Error opening wallet seed file".into()))?;
if orig_wallet_seed != new_wallet_seed {
let msg =
"New and Old wallet seeds are not equal on password change, not removing backups."
.to_string();
return Err(Error::Lifecycle(msg));
}
info!("Password change confirmed, removing old seed file.");
fs::remove_file(backup_name).map_err(|e| Error::IO(e.to_string()))?;
Ok(())
}
fn delete_wallet(&self, _name: Option<&str>) -> Result<(), Error> {
let data_dir_name = PathBuf::from(self.data_dir.clone());
warn!(
"Removing all wallet data from: {}",
data_dir_name.to_str().unwrap()
);
fs::remove_dir_all(data_dir_name).map_err(|e| Error::IO(e.to_string()))?;
Ok(())
}
fn wallet_inst(&mut self) -> Result<&mut Box<dyn WalletBackend<'a, C, K> + 'a>, Error> {
match self.backend.as_mut() {
None => {
let msg = "Wallet has not been opened".into();
Err(Error::Lifecycle(msg))
}
Some(_) => Ok(&mut *self.backend.as_mut().unwrap()),
}
}
}