Skip to content

Commit 4f64afe

Browse files
sasuke0787Chiaretta LentoCaro BeaverAlgernon De AccelerandoMariella Von Diminuendo
authored andcommitted
[cli] interactive config init and fix (#416)
Co-authored-by: Chiaretta Lento <[email protected]> Co-authored-by: Caro Beaver <[email protected]> Co-authored-by: Algernon De Accelerando <[email protected]> Co-authored-by: Mariella Von Diminuendo <[email protected]> Co-authored-by: Franci Von Ritardando <[email protected]> Co-authored-by: Vale di Bittern <[email protected]> Co-authored-by: Peregrine Von Stag <[email protected]> Co-authored-by: Crispin Saint Hind <[email protected]> Co-authored-by: Crispin LeLento <[email protected]> Co-authored-by: Mariella Leveret <[email protected]> Co-authored-by: Sere Mezzo <[email protected]> Co-authored-by: Raffa Tempo <[email protected]> Co-authored-by: 0o-de-lally <[email protected]>
1 parent 1b69da2 commit 4f64afe

File tree

14 files changed

+1144
-383
lines changed

14 files changed

+1144
-383
lines changed

tools/config/src/config_cli.rs

Lines changed: 12 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
use crate::{
22
config_wizard,
33
get_genesis_artifacts::{download_genesis, get_genesis_waypoint},
4+
interactive::options::{fix_config, FixOptions},
45
make_yaml_public_fullnode::init_fullnode_yaml,
56
validator_config::{validator_dialogue, vfn_dialogue},
67
};
7-
use anyhow::{anyhow, bail, Context, Result};
8+
use anyhow::{bail, Context, Result};
89
use clap::Parser;
910
use libra_types::{
10-
core_types::{
11-
app_cfg::{self, AppCfg},
12-
network_playlist::NetworkPlaylist,
13-
},
14-
exports::{AccountAddress, AuthenticationKey, Client, NamedChain},
11+
core_types::network_playlist::NetworkPlaylist,
12+
exports::{AccountAddress, AuthenticationKey, NamedChain},
1513
global_config_dir, ol_progress,
16-
type_extensions::client_ext::ClientExt,
1714
};
1815
use libra_wallet::{utils::read_operator_file, validator_files::OPERATOR_FILE};
1916
use std::path::PathBuf;
@@ -98,107 +95,14 @@ impl ConfigCli {
9895
remove_profile,
9996
fullnode_url: force_url,
10097
}) => {
101-
// Validate exactly one argument is provided
102-
let args_count = [
103-
*reset_address,
104-
remove_profile.is_some(),
105-
force_url.is_some(),
106-
]
107-
.iter()
108-
.filter(|&&x| x)
109-
.count();
110-
111-
if args_count == 0 {
112-
return Err(anyhow!(
113-
"At least one argument must be provided to 'fix' command"
114-
));
115-
}
116-
if args_count > 1 {
117-
return Err(anyhow!(
118-
"Only one argument can be provided to 'fix' command"
119-
));
120-
}
121-
122-
// Load configuration file
123-
let mut cfg = AppCfg::load(self.path.clone())
124-
.map_err(|e| anyhow!("no config file found for libra tools, {}", e))?;
125-
if !cfg.user_profiles.is_empty() {
126-
println!("your profiles:");
127-
for p in &cfg.user_profiles {
128-
println!("- address: {}, nickname: {}", p.account, p.nickname);
129-
}
130-
} else {
131-
println!("no profiles found");
132-
}
133-
134-
// Handle address fix option
135-
let profile = if *reset_address {
136-
let mut account_keys = config_wizard::prompt_for_account()?;
137-
138-
let client = Client::new(cfg.pick_url(self.chain_name)?);
139-
140-
// Lookup originating address if client index is successful
141-
if client.get_index().await.is_ok() {
142-
account_keys.account = match client
143-
.lookup_originating_address(account_keys.auth_key)
144-
.await
145-
{
146-
Ok(r) => r,
147-
_ => {
148-
println!("This looks like a new account, and it's not yet on chain. If this is not what you expected, are you sure you are using the correct recovery mnemonic?");
149-
// do nothing
150-
account_keys.account
151-
}
152-
};
153-
};
154-
155-
// Create profile based on account keys
156-
let profile =
157-
app_cfg::Profile::new(account_keys.auth_key, account_keys.account);
158-
159-
// Add profile to configuration
160-
cfg.maybe_add_profile(profile)?;
161-
162-
// Prompt to set as default profile
163-
if dialoguer::Confirm::new()
164-
.with_prompt("set as default profile?")
165-
.interact()?
166-
{
167-
cfg.workspace
168-
.set_default(account_keys.account.to_hex_literal());
169-
}
170-
171-
cfg.get_profile_mut(Some(account_keys.account.to_hex_literal()))
172-
} else {
173-
// get default profile
174-
println!("will try to fix your default profile");
175-
cfg.get_profile_mut(None)
176-
}?;
177-
178-
println!("using profile: {}", &profile.nickname);
179-
180-
// user can take pledge here on fix or on init
181-
profile.maybe_offer_basic_pledge();
182-
profile.maybe_offer_validator_pledge();
183-
184-
// Remove profile if specified
185-
if let Some(p) = remove_profile {
186-
let r = cfg.try_remove_profile(p);
187-
if r.is_err() {
188-
println!("no profile found matching {}", &p)
189-
}
190-
}
191-
192-
// Force URL overwrite if specified
193-
if let Some(u) = force_url {
194-
let np = cfg.get_network_profile_mut(self.chain_name)?;
195-
np.nodes = vec![];
196-
np.add_url(u.to_owned());
197-
}
198-
199-
// Save configuration file
200-
cfg.save_file()?;
201-
Ok(())
98+
fix_config(FixOptions {
99+
reset_address: *reset_address,
100+
remove_profile: remove_profile.clone(),
101+
fullnode_url: force_url.clone(),
102+
config_path: self.path.clone(),
103+
chain_name: self.chain_name,
104+
})
105+
.await
202106
}
203107

204108
// Initialize configuration wizard

tools/config/src/config_wizard.rs

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::interactive::account_selection;
12
use anyhow::Context;
23
use diem_crypto::{ed25519::Ed25519PrivateKey, ValidCryptoMaterialStringExt};
34
use diem_types::chain_id::NamedChain;
@@ -7,7 +8,6 @@ use libra_types::{
78
ol_progress,
89
type_extensions::client_ext::ClientExt,
910
};
10-
use libra_wallet::account_keys::{get_ol_legacy_address, AccountKeys};
1111
use std::path::PathBuf;
1212
use url::Url;
1313

@@ -29,9 +29,7 @@ pub async fn wizard(
2929
let account_keys = libra_wallet::account_keys::get_account_from_private(&pk);
3030
(account_keys.auth_key, account_keys.account)
3131
} else {
32-
let account_keys = prompt_for_account()?;
33-
34-
(account_keys.auth_key, account_keys.account)
32+
account_selection::interactive_account_selection()?
3533
};
3634

3735
let spin = ol_progress::OLProgress::spin_steady(250, "fetching metadata".to_string());
@@ -81,18 +79,3 @@ pub async fn wizard(
8179

8280
Ok(cfg)
8381
}
84-
85-
/// Wrapper on get keys_from_prompt,
86-
/// Prompts the user for account details and checks if it is a legacy account.
87-
pub fn prompt_for_account() -> anyhow::Result<AccountKeys> {
88-
let mut account_keys = libra_wallet::account_keys::get_keys_from_prompt()?.child_0_owner;
89-
90-
if dialoguer::Confirm::new()
91-
.with_prompt("Is this a legacy pre-v7 address (16 characters)?")
92-
.interact()?
93-
{
94-
account_keys.account = get_ol_legacy_address(account_keys.account)?;
95-
}
96-
97-
Ok(account_keys)
98-
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
use anyhow::Context;
2+
use libra_types::exports::{AccountAddress, AuthenticationKey};
3+
use libra_wallet::account_keys::{get_ol_legacy_address, AccountKeys};
4+
5+
/// Interactive account selection - allows user to choose between existing address, mnemonic, or dummy account
6+
pub fn interactive_account_selection() -> anyhow::Result<(AuthenticationKey, AccountAddress)> {
7+
// Ask the user if they want to include an account address in profile
8+
if dialoguer::Confirm::new()
9+
.with_prompt("Do you want to configure with an account address in this profile?")
10+
.interact()?
11+
{
12+
// User wants to configure with a real account
13+
println!("You can either provide an existing address or derive one from a mnemonic.");
14+
15+
let use_existing = dialoguer::Confirm::new()
16+
.with_prompt("Do you want to enter an address? If not, will derive from mnemonic.")
17+
.interact()?;
18+
19+
if use_existing {
20+
// User wants to provide an existing address
21+
let address_input: String = dialoguer::Input::new()
22+
.with_prompt("Enter your account address (hex format)")
23+
.interact()?;
24+
25+
let address = AccountAddress::from_hex_literal(&address_input)
26+
.context("Invalid account address format")?;
27+
28+
// Use dummy authkey for existing addresses since we don't control them
29+
let dummy_authkey = AuthenticationKey::zero();
30+
31+
Ok((dummy_authkey, address))
32+
} else {
33+
// User wants to derive from mnemonic
34+
let account_keys = prompt_for_account()?;
35+
Ok((account_keys.auth_key, account_keys.account))
36+
}
37+
} else {
38+
// User doesn't want to configure with account, use dummy values
39+
println!("Using dummy account values (0x0) - you can configure a real account later.");
40+
let dummy_address = AccountAddress::ZERO;
41+
let dummy_authkey = AuthenticationKey::zero();
42+
43+
Ok((dummy_authkey, dummy_address))
44+
}
45+
}
46+
47+
/// Wrapper on get keys_from_prompt,
48+
/// Prompts the user for account details and checks if it is a legacy account.
49+
pub fn prompt_for_account() -> anyhow::Result<AccountKeys> {
50+
let mut account_keys = libra_wallet::account_keys::get_keys_from_prompt()?.child_0_owner;
51+
52+
if dialoguer::Confirm::new()
53+
.with_prompt("Is this a legacy pre-v7 address (16 characters)?")
54+
.interact()?
55+
{
56+
account_keys.account = get_ol_legacy_address(account_keys.account)?;
57+
}
58+
59+
Ok(account_keys)
60+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
use anyhow::{anyhow, Result};
2+
use libra_types::{core_types::app_cfg::AppCfg, exports::NamedChain};
3+
4+
/// Change the default profile
5+
pub fn change_default_profile(cfg: &mut AppCfg) -> Result<()> {
6+
if cfg.user_profiles.is_empty() {
7+
return Err(anyhow!("No profiles available"));
8+
}
9+
10+
let profile_options: Vec<String> = cfg
11+
.user_profiles
12+
.iter()
13+
.map(|p| {
14+
let is_default = cfg
15+
.workspace
16+
.default_profile
17+
.as_ref()
18+
.map(|default| default == &p.account.to_hex_literal())
19+
.unwrap_or(false);
20+
21+
let default_marker = if is_default { " (current default)" } else { "" };
22+
format!("{} ({}){}", p.nickname, p.account, default_marker)
23+
})
24+
.collect();
25+
26+
let selection = dialoguer::Select::new()
27+
.with_prompt("Choose default profile")
28+
.items(&profile_options)
29+
.default(0)
30+
.interact()?;
31+
32+
let selected_profile = &cfg.user_profiles[selection];
33+
cfg.workspace
34+
.set_default(selected_profile.account.to_hex_literal());
35+
36+
println!(
37+
"Default profile changed to: {} ({})",
38+
selected_profile.nickname, selected_profile.account
39+
);
40+
Ok(())
41+
}
42+
43+
/// Change the default chain
44+
pub fn change_default_chain(cfg: &mut AppCfg) -> Result<()> {
45+
use dialoguer::Select;
46+
47+
if cfg.network_playlist.is_empty() {
48+
return Err(anyhow!(
49+
"No networks configured. Please configure a network first."
50+
));
51+
}
52+
53+
let configured_chains: Vec<NamedChain> = cfg
54+
.network_playlist
55+
.iter()
56+
.map(|np| np.chain_name)
57+
.collect();
58+
59+
let chain_options: Vec<String> = configured_chains
60+
.iter()
61+
.map(|chain| format!("{:?}", chain).to_lowercase())
62+
.collect();
63+
64+
let selection = Select::new()
65+
.with_prompt("Choose default chain")
66+
.items(&chain_options)
67+
.default(0)
68+
.interact()?;
69+
70+
let selected_chain = configured_chains[selection];
71+
72+
cfg.workspace.default_chain_id = selected_chain;
73+
println!("Default chain changed to: {:?}", selected_chain);
74+
Ok(())
75+
}
76+
77+
/// Interactive defaults management
78+
pub async fn interactive_change_defaults(cfg: &mut AppCfg) -> Result<bool> {
79+
println!("\nWhat default would you like to change?");
80+
81+
let options = vec!["Change default profile", "Change default chain", "Cancel"];
82+
83+
let selection = dialoguer::Select::new()
84+
.with_prompt("Choose an option")
85+
.items(&options)
86+
.default(0)
87+
.interact()?;
88+
89+
match selection {
90+
0 => {
91+
// Change default profile
92+
if cfg.user_profiles.len() > 1 {
93+
change_default_profile(cfg)?;
94+
Ok(true)
95+
} else {
96+
println!("Only one profile available. Cannot change default.");
97+
Ok(false)
98+
}
99+
}
100+
1 => {
101+
// Change default chain
102+
change_default_chain(cfg)?;
103+
Ok(true)
104+
}
105+
2 => {
106+
// Cancel
107+
Ok(false)
108+
}
109+
_ => unreachable!(),
110+
}
111+
}

0 commit comments

Comments
 (0)