Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: paper wallet cli #6522

Merged
merged 2 commits into from
Sep 4, 2024
Merged
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
162 changes: 159 additions & 3 deletions applications/minotari_console_wallet/src/automation/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use std::{
io,
io::{LineWriter, Write},
path::{Path, PathBuf},
str::FromStr,
time::{Duration, Instant},
};

Expand Down Expand Up @@ -66,6 +67,7 @@ use tari_common_types::{
use tari_comms::{
connectivity::{ConnectivityEvent, ConnectivityRequester},
multiaddr::Multiaddr,
peer_manager::{Peer, PeerQuery},
types::CommsPublicKey,
};
use tari_comms_dht::{envelope::NodeDestination, DhtDiscoveryRequester};
Expand All @@ -89,9 +91,14 @@ use tari_core::{
},
};
use tari_crypto::ristretto::{pedersen::PedersenCommitment, RistrettoSecretKey};
use tari_key_manager::key_manager_service::{KeyId, KeyManagerInterface};
use tari_key_manager::{
key_manager_service::{KeyId, KeyManagerInterface},
SeedWords,
};
use tari_p2p::{auto_update::AutoUpdateConfig, peer_seeds::SeedPeer, PeerSeedsConfig};
use tari_script::{script, CheckSigSchnorrSignature};
use tari_utilities::{hex::Hex, ByteArray};
use tari_shutdown::Shutdown;
use tari_utilities::{hex::Hex, ByteArray, SafePassword};
use tokio::{
sync::{broadcast, mpsc},
time::{sleep, timeout},
Expand Down Expand Up @@ -119,7 +126,10 @@ use crate::{
PreMineSpendStep4OutputsForLeader,
},
cli::{CliCommands, MakeItRainTransactionType},
utils::db::{CUSTOM_BASE_NODE_ADDRESS_KEY, CUSTOM_BASE_NODE_PUBLIC_KEY_KEY},
init::init_wallet,
recovery::{get_seed_from_seed_words, wallet_recovery},
utils::db::{get_custom_base_node_peer_from_db, CUSTOM_BASE_NODE_ADDRESS_KEY, CUSTOM_BASE_NODE_PUBLIC_KEY_KEY},
wallet_modes::PeerConfig,
};

pub const LOG_TARGET: &str = "wallet::automation::commands";
Expand Down Expand Up @@ -1816,6 +1826,152 @@ pub async fn command_runner(
println!("Spend key: {}", spend_key_hex);
}
},
ImportPaperWallet(args) => {
let temp_path = config
.db_file
.parent()
.ok_or(CommandError::General("No parent".to_string()))?
.join("temp");
println!("saving temp wallet in: {:?}", temp_path);
{
let seed_words = SeedWords::from_str(args.seed_words.as_str())
.map_err(|e| CommandError::General(e.to_string()))?;
let seed =
get_seed_from_seed_words(&seed_words).map_err(|e| CommandError::General(e.to_string()))?;
let wallet_type = WalletType::DerivedKeys;
let password = SafePassword::from("password".to_string());
let shutdown = Shutdown::new();
let shutdown_signal = shutdown.to_signal();
let mut new_config = config.clone();
new_config.set_base_path(temp_path.clone());

let peer_config = PeerSeedsConfig::default();
let mut new_wallet = init_wallet(
&new_config,
AutoUpdateConfig::default(),
peer_config,
password,
None,
Some(seed),
shutdown_signal,
true,
Some(wallet_type),
)
.await
.map_err(|e| CommandError::General(e.to_string()))?;
// config

let query = PeerQuery::new().select_where(|p| p.is_seed());
let peer_seeds = wallet
.comms
.peer_manager()
.perform_query(query)
.await
.map_err(|e| CommandError::General(e.to_string()))?;
// config
let base_node_peers = config
.base_node_service_peers
.iter()
.map(|s| SeedPeer::from_str(s))
.map(|r| r.map(Peer::from))
.collect::<Result<Vec<_>, _>>()
.map_err(|e| CommandError::General(e.to_string()))?;
let selected_base_node = match config.custom_base_node {
Some(ref custom) => SeedPeer::from_str(custom)
.map(|node| Some(Peer::from(node)))
.map_err(|e| CommandError::General(e.to_string()))?,
None => get_custom_base_node_peer_from_db(&wallet),
};

let peer_config = PeerConfig::new(selected_base_node, base_node_peers, peer_seeds);

let base_node = peer_config
.get_base_node_peer()
.map_err(|e| CommandError::General(e.to_string()))?;
new_wallet
.set_base_node_peer(
base_node.public_key.clone(),
Some(
base_node
.last_address_used()
.ok_or(CommandError::General("No address found".to_string()))?,
),
)
.await
.map_err(|e| CommandError::General(e.to_string()))?;
wallet_recovery(&new_wallet, &peer_config, new_config.recovery_retry_limit)
.await
.map_err(|e| CommandError::General(e.to_string()))?;
print!("Wallet recovery completed");
let mut oms = new_wallet.output_manager_service.clone();
oms.validate_txos().await?;
let mut event = oms.get_event_stream();
loop {
match event.recv().await {
Ok(event) => match *event {
OutputManagerEvent::TxoValidationSuccess(_) => {
println!("Validation succeeded");
break;
},
OutputManagerEvent::TxoValidationAlreadyBusy(_) => {
println!("Validation already busy");
},
_ => {
println!("Validation failed");
break;
},
},
Err(e) => {
eprintln!("Sync error! {}", e);
break;
},
}
}
println!("balance as of scanning height");
match oms.clone().get_balance().await {
Ok(balance) => {
println!("{}", balance);
},
Err(e) => eprintln!("GetBalance error! {}", e),
}
let mut tms = new_wallet.transaction_service.clone();
match tms
.scrape_wallet(
wallet
.get_wallet_one_sided_address()
.await
.map_err(|e| CommandError::General(e.to_string()))?,
config.fee_per_gram * uT,
)
.await
.map_err(CommandError::TransactionServiceError)
{
Ok(tx_id) => {
debug!(target: LOG_TARGET, "send-minotari concluded with tx_id {}", tx_id);
let duration = config.command_send_wait_timeout;
match timeout(duration, monitor_transactions(tms.clone(), vec![tx_id], wait_stage)).await {
Ok(txs) => {
debug!(
target: LOG_TARGET,
"monitor_transactions done to stage {:?} with tx_ids: {:?}", wait_stage, txs
);
println!("Done! All transactions monitored to {:?} stage.", wait_stage);
},
Err(_e) => {
println!(
"The configured timeout ({:#?}) was reached before all transactions reached \
the {:?} stage. See the logs for more info.",
duration, wait_stage
);
},
}
},
Err(e) => eprintln!("SendMinotari error! {}", e),
}
}
println!("removing temp wallet in: {:?}", temp_path);
fs::remove_dir_all(temp_path)?;
},
}
}

Expand Down
7 changes: 7 additions & 0 deletions applications/minotari_console_wallet/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ pub enum CliCommands {
CreateTlsCerts,
Sync(SyncArgs),
ExportViewKeyAndSpendKey(ExportViewKeyAndSpendKeyArgs),
ImportPaperWallet(ImportPaperWalletArgs),
}

#[derive(Debug, Args, Clone)]
Expand Down Expand Up @@ -319,6 +320,12 @@ pub struct ExportViewKeyAndSpendKeyArgs {
pub output_file: Option<PathBuf>,
}

#[derive(Debug, Args, Clone)]
pub struct ImportPaperWalletArgs {
#[clap(short, long)]
pub seed_words: String,
}

#[derive(Debug, Args, Clone)]
pub struct ImportTxArgs {
#[clap(short, long)]
Expand Down
70 changes: 22 additions & 48 deletions applications/minotari_console_wallet/src/init/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ use tari_key_manager::{
key_manager_service::{storage::database::KeyManagerBackend, KeyManagerInterface},
mnemonic::MnemonicLanguage,
};
use tari_p2p::{peer_seeds::SeedPeer, TransportType};
use tari_p2p::{auto_update::AutoUpdateConfig, peer_seeds::SeedPeer, PeerSeedsConfig, TransportType};
use tari_shutdown::ShutdownSignal;
use tari_utilities::{encoding::Base58, hex::Hex, ByteArray, SafePassword};
use zxcvbn::zxcvbn;
Expand Down Expand Up @@ -262,7 +262,9 @@ pub async fn change_password(
non_interactive_mode: bool,
) -> Result<(), ExitError> {
let mut wallet = init_wallet(
config,
&config.wallet,
config.auto_update.clone(),
config.peer_seeds.clone(),
existing.clone(),
None,
None,
Expand Down Expand Up @@ -290,13 +292,13 @@ pub async fn change_password(
/// 3. The detected local base node if any
/// 4. The service peers defined in config they exist
/// 5. The peer seeds defined in config
pub async fn get_base_node_peer_config(
config: &ApplicationConfig,
pub async fn set_peer_and_get_base_node_peer_config(
config: &WalletConfig,
wallet: &mut WalletSqlite,
non_interactive_mode: bool,
) -> Result<PeerConfig, ExitError> {
let mut use_custom_base_node_peer = false;
let mut selected_base_node = match config.wallet.custom_base_node {
let mut selected_base_node = match config.custom_base_node {
Some(ref custom) => SeedPeer::from_str(custom)
.map(|node| Some(Peer::from(node)))
.map_err(|err| ExitError::new(ExitCode::ConfigError, format!("Malformed custom base node: {}", err)))?,
Expand All @@ -311,8 +313,8 @@ pub async fn get_base_node_peer_config(
};

// If the user has not explicitly set a base node in the config, we try detect one
if !non_interactive_mode && config.wallet.custom_base_node.is_none() && !use_custom_base_node_peer {
if let Some(detected_node) = detect_local_base_node(config.wallet.network).await {
if !non_interactive_mode && config.custom_base_node.is_none() && !use_custom_base_node_peer {
if let Some(detected_node) = detect_local_base_node(config.network).await {
match selected_base_node {
Some(ref base_node) if base_node.public_key == detected_node.public_key => {
// Skip asking because it's already set
Expand Down Expand Up @@ -349,10 +351,8 @@ pub async fn get_base_node_peer_config(
format!("Could net get seed peers from peer manager: {}", err),
)
})?;

// config
let base_node_peers = config
.wallet
.base_node_service_peers
.iter()
.map(|s| SeedPeer::from_str(s))
Expand Down Expand Up @@ -394,7 +394,9 @@ pub(crate) fn wallet_mode(cli: &Cli, boot_mode: WalletBoot) -> WalletMode {
/// Set up the app environment and state for use by the UI
#[allow(clippy::too_many_lines)]
pub async fn init_wallet(
config: &ApplicationConfig,
config: &WalletConfig,
auto_update: AutoUpdateConfig,
peer_seeds: PeerSeedsConfig,
arg_password: SafePassword,
seed_words_file_name: Option<PathBuf>,
recovery_seed: Option<CipherSeed>,
Expand All @@ -404,47 +406,46 @@ pub async fn init_wallet(
) -> Result<WalletSqlite, ExitError> {
fs::create_dir_all(
config
.wallet
.db_file
.parent()
.expect("console_wallet_db_file cannot be set to a root directory"),
)
.map_err(|e| ExitError::new(ExitCode::WalletError, format!("Error creating Wallet folder. {}", e)))?;
fs::create_dir_all(&config.wallet.p2p.datastore_path)
fs::create_dir_all(&config.p2p.datastore_path)
.map_err(|e| ExitError::new(ExitCode::WalletError, format!("Error creating peer db folder. {}", e)))?;

debug!(target: LOG_TARGET, "Running Wallet database migrations");

let db_path = &config.wallet.db_file;
let db_path = &config.db_file;

// wallet should be encrypted from the beginning, so we must require a password to be provided by the user
let (wallet_backend, transaction_backend, output_manager_backend, contacts_backend, key_manager_backend) =
initialize_sqlite_database_backends(db_path, arg_password, config.wallet.db_connection_pool_size)?;
initialize_sqlite_database_backends(db_path, arg_password, config.db_connection_pool_size)?;

let wallet_db = WalletDatabase::new(wallet_backend);
let output_db = OutputManagerDatabase::new(output_manager_backend.clone());

debug!(target: LOG_TARGET, "Databases Initialized. Wallet is encrypted.",);

let node_addresses = if config.wallet.p2p.public_addresses.is_empty() {
let node_addresses = if config.p2p.public_addresses.is_empty() {
match wallet_db.get_node_address()? {
Some(addr) => MultiaddrList::from(vec![addr]),
None => MultiaddrList::default(),
}
} else {
config.wallet.p2p.public_addresses.clone()
config.p2p.public_addresses.clone()
};

let master_seed = read_or_create_master_seed(recovery_seed.clone(), &wallet_db)?;

let node_identity = setup_identity_from_db(&wallet_db, &master_seed, node_addresses.to_vec())?;

let mut wallet_config = config.wallet.clone();
if let TransportType::Tor = config.wallet.p2p.transport.transport_type {
let mut wallet_config = config.clone();
if let TransportType::Tor = config.p2p.transport.transport_type {
wallet_config.p2p.transport.tor.identity = wallet_db.get_tor_id()?;
}

let consensus_manager = ConsensusManager::builder(config.wallet.network)
let consensus_manager = ConsensusManager::builder(config.network)
.build()
.map_err(|e| ExitError::new(ExitCode::WalletError, format!("Error consensus manager. {}", e)))?;
let factories = CryptoFactories::default();
Expand All @@ -453,8 +454,8 @@ pub async fn init_wallet(
let user_agent = format!("tari/wallet/{}", consts::APP_VERSION_NUMBER);
let mut wallet = Wallet::start(
wallet_config,
config.peer_seeds.clone(),
config.auto_update.clone(),
peer_seeds,
auto_update,
node_identity,
consensus_manager,
factories,
Expand Down Expand Up @@ -572,33 +573,6 @@ pub async fn start_wallet(
base_node: &Peer,
wallet_mode: &WalletMode,
) -> Result<(), ExitError> {
// Verify ledger build if wallet type is Ledger
if let WalletType::Ledger(_) = *wallet.key_manager_service.get_wallet_type().await {
#[cfg(not(feature = "ledger"))]
{
return Err(ExitError::new(
ExitCode::WalletError,
format!("{}", LEDGER_NOT_SUPPORTED),
));
}

#[cfg(feature = "ledger")]
{
let key_id = TariKeyId::Managed {
branch: TransactionKeyManagerBranch::RandomKey.get_branch_key(),
index: 0,
};
match wallet.key_manager_service.get_public_key_at_key_id(&key_id).await {
Ok(public_key) => {},
Err(e) => {
if e.to_string().contains(LEDGER_NOT_SUPPORTED) {
return Err(ExitError::new(ExitCode::WalletError, format!(" {}", e)));
}
},
}
}
}

debug!(target: LOG_TARGET, "Setting base node peer");

let net_address = base_node
Expand Down
Loading
Loading