Permit users to assign labels to account addresses
This commit is contained in:
committed by
mergify[bot]
parent
a25ea8e774
commit
b297d0b423
@ -56,6 +56,7 @@ use solana_stake_program::{
|
||||
use solana_transaction_status::{EncodedTransaction, TransactionEncoding};
|
||||
use solana_vote_program::vote_state::VoteAuthorize;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
error,
|
||||
fmt::Write as FmtWrite,
|
||||
fs::File,
|
||||
@ -493,6 +494,7 @@ pub struct CliConfig<'a> {
|
||||
pub output_format: OutputFormat,
|
||||
pub commitment: CommitmentConfig,
|
||||
pub send_transaction_config: RpcSendTransactionConfig,
|
||||
pub address_labels: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl CliConfig<'_> {
|
||||
@ -596,6 +598,7 @@ impl Default for CliConfig<'_> {
|
||||
output_format: OutputFormat::Display,
|
||||
commitment: CommitmentConfig::default(),
|
||||
send_transaction_config: RpcSendTransactionConfig::default(),
|
||||
address_labels: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1837,7 +1840,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||
CliCommand::ShowBlockProduction { epoch, slot_limit } => {
|
||||
process_show_block_production(&rpc_client, config, *epoch, *slot_limit)
|
||||
}
|
||||
CliCommand::ShowGossip => process_show_gossip(&rpc_client),
|
||||
CliCommand::ShowGossip => process_show_gossip(&rpc_client, config),
|
||||
CliCommand::ShowStakes {
|
||||
use_lamports_unit,
|
||||
vote_account_pubkeys,
|
||||
|
@ -1,4 +1,7 @@
|
||||
use crate::{cli::build_balance_message, display::writeln_name_value};
|
||||
use crate::{
|
||||
cli::build_balance_message,
|
||||
display::{format_labeled_address, writeln_name_value},
|
||||
};
|
||||
use chrono::{DateTime, NaiveDateTime, SecondsFormat, Utc};
|
||||
use console::{style, Emoji};
|
||||
use inflector::cases::titlecase::to_title_case;
|
||||
@ -18,7 +21,11 @@ use solana_vote_program::{
|
||||
authorized_voters::AuthorizedVoters,
|
||||
vote_state::{BlockTimestamp, Lockout},
|
||||
};
|
||||
use std::{collections::BTreeMap, fmt, time::Duration};
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
fmt,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
static WARNING: Emoji = Emoji("⚠️", "!");
|
||||
|
||||
@ -404,10 +411,15 @@ pub struct CliValidator {
|
||||
}
|
||||
|
||||
impl CliValidator {
|
||||
pub fn new(vote_account: &RpcVoteAccountInfo, current_epoch: Epoch, version: String) -> Self {
|
||||
pub fn new(
|
||||
vote_account: &RpcVoteAccountInfo,
|
||||
current_epoch: Epoch,
|
||||
version: String,
|
||||
address_labels: &HashMap<String, String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
identity_pubkey: vote_account.node_pubkey.to_string(),
|
||||
vote_account_pubkey: vote_account.vote_pubkey.to_string(),
|
||||
identity_pubkey: format_labeled_address(&vote_account.node_pubkey, address_labels),
|
||||
vote_account_pubkey: format_labeled_address(&vote_account.vote_pubkey, address_labels),
|
||||
commission: vote_account.commission,
|
||||
last_vote: vote_account.last_vote,
|
||||
root_slot: vote_account.root_slot,
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
|
||||
cli_output::*,
|
||||
display::{new_spinner_progress_bar, println_name_value},
|
||||
display::{format_labeled_address, new_spinner_progress_bar, println_name_value},
|
||||
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
|
||||
};
|
||||
use clap::{value_t, value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||
@ -734,11 +734,12 @@ pub fn process_show_block_production(
|
||||
let leader_schedule = leader_schedule.unwrap();
|
||||
|
||||
let mut leader_per_slot_index = Vec::new();
|
||||
leader_per_slot_index.resize(total_slots, "?");
|
||||
leader_per_slot_index.resize(total_slots, "?".to_string());
|
||||
for (pubkey, leader_slots) in leader_schedule.iter() {
|
||||
let pubkey = format_labeled_address(pubkey, &config.address_labels);
|
||||
for slot_index in leader_slots.iter() {
|
||||
if *slot_index >= start_slot_index && *slot_index <= end_slot_index {
|
||||
leader_per_slot_index[*slot_index - start_slot_index] = pubkey;
|
||||
leader_per_slot_index[*slot_index - start_slot_index] = pubkey.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1085,7 +1086,7 @@ pub fn process_live_slots(url: &str) -> ProcessResult {
|
||||
Ok("".to_string())
|
||||
}
|
||||
|
||||
pub fn process_show_gossip(rpc_client: &RpcClient) -> ProcessResult {
|
||||
pub fn process_show_gossip(rpc_client: &RpcClient, config: &CliConfig) -> ProcessResult {
|
||||
let cluster_nodes = rpc_client.get_cluster_nodes()?;
|
||||
|
||||
fn format_port(addr: Option<SocketAddr>) -> String {
|
||||
@ -1101,7 +1102,7 @@ pub fn process_show_gossip(rpc_client: &RpcClient) -> ProcessResult {
|
||||
node.gossip
|
||||
.map(|addr| addr.ip().to_string())
|
||||
.unwrap_or_else(|| "none".to_string()),
|
||||
node.pubkey,
|
||||
format_labeled_address(&node.pubkey, &config.address_labels),
|
||||
format_port(node.gossip),
|
||||
format_port(node.tpu),
|
||||
format_port(node.rpc),
|
||||
@ -1235,6 +1236,7 @@ pub fn process_show_validators(
|
||||
.get(&vote_account.node_pubkey)
|
||||
.unwrap_or(&unknown_version)
|
||||
.clone(),
|
||||
&config.address_labels,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
@ -1250,6 +1252,7 @@ pub fn process_show_validators(
|
||||
.get(&vote_account.node_pubkey)
|
||||
.unwrap_or(&unknown_version)
|
||||
.clone(),
|
||||
&config.address_labels,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
@ -6,7 +6,7 @@ use solana_sdk::{
|
||||
transaction::Transaction,
|
||||
};
|
||||
use solana_transaction_status::RpcTransactionStatusMeta;
|
||||
use std::{fmt, io};
|
||||
use std::{collections::HashMap, fmt, io};
|
||||
|
||||
// Pretty print a "name value"
|
||||
pub fn println_name_value(name: &str, value: &str) {
|
||||
@ -27,6 +27,19 @@ pub fn writeln_name_value(f: &mut fmt::Formatter, name: &str, value: &str) -> fm
|
||||
writeln!(f, "{} {}", style(name).bold(), styled_value)
|
||||
}
|
||||
|
||||
pub fn format_labeled_address(pubkey: &str, address_labels: &HashMap<String, String>) -> String {
|
||||
let label = address_labels.get(pubkey);
|
||||
match label {
|
||||
Some(label) => format!(
|
||||
"{:.31} ({:.4}..{})",
|
||||
label,
|
||||
pubkey,
|
||||
pubkey.split_at(pubkey.len() - 4).1
|
||||
),
|
||||
None => pubkey.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn println_name_value_or(name: &str, value: &str, setting_type: SettingType) {
|
||||
let description = match setting_type {
|
||||
SettingType::Explicit => "",
|
||||
@ -210,3 +223,32 @@ pub fn new_spinner_progress_bar() -> ProgressBar {
|
||||
progress_bar.enable_steady_tick(100);
|
||||
progress_bar
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
#[test]
|
||||
fn test_format_labeled_address() {
|
||||
let pubkey = Pubkey::default().to_string();
|
||||
let mut address_labels = HashMap::new();
|
||||
|
||||
assert_eq!(format_labeled_address(&pubkey, &address_labels), pubkey);
|
||||
|
||||
address_labels.insert(pubkey.to_string(), "Default Address".to_string());
|
||||
assert_eq!(
|
||||
&format_labeled_address(&pubkey, &address_labels),
|
||||
"Default Address (1111..1111)"
|
||||
);
|
||||
|
||||
address_labels.insert(
|
||||
pubkey.to_string(),
|
||||
"abcdefghijklmnopqrstuvwxyz1234567890".to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
&format_labeled_address(&pubkey, &address_labels),
|
||||
"abcdefghijklmnopqrstuvwxyz12345 (1111..1111)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
use clap::{crate_description, crate_name, AppSettings, Arg, ArgGroup, ArgMatches, SubCommand};
|
||||
use clap::{
|
||||
crate_description, crate_name, value_t_or_exit, AppSettings, Arg, ArgGroup, ArgMatches,
|
||||
SubCommand,
|
||||
};
|
||||
use console::style;
|
||||
|
||||
use solana_clap_utils::{
|
||||
@ -13,15 +16,25 @@ use solana_cli::{
|
||||
use solana_cli_config::{Config, CONFIG_FILE};
|
||||
use solana_client::rpc_config::RpcSendTransactionConfig;
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
use std::{error, sync::Arc};
|
||||
use std::{collections::HashMap, error, path::PathBuf, sync::Arc};
|
||||
|
||||
fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error>> {
|
||||
let parse_args = match matches.subcommand() {
|
||||
("config", Some(matches)) => match matches.subcommand() {
|
||||
("get", Some(subcommand_matches)) => {
|
||||
if let Some(config_file) = matches.value_of("config_file") {
|
||||
let config = Config::load(config_file).unwrap_or_default();
|
||||
("config", Some(matches)) => {
|
||||
let config_file = match matches.value_of("config_file") {
|
||||
None => {
|
||||
println!(
|
||||
"{} Either provide the `--config` arg or ensure home directory exists to use the default config location",
|
||||
style("No config file found.").bold()
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
Some(config_file) => config_file,
|
||||
};
|
||||
let mut config = Config::load(config_file).unwrap_or_default();
|
||||
|
||||
match matches.subcommand() {
|
||||
("get", Some(subcommand_matches)) => {
|
||||
let (url_setting_type, json_rpc_url) =
|
||||
CliConfig::compute_json_rpc_url_setting("", &config.json_rpc_url);
|
||||
let (ws_setting_type, websocket_url) = CliConfig::compute_websocket_url_setting(
|
||||
@ -47,17 +60,8 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
|
||||
println_name_value_or("WebSocket URL:", &websocket_url, ws_setting_type);
|
||||
println_name_value_or("Keypair Path:", &keypair_path, keypair_setting_type);
|
||||
}
|
||||
} else {
|
||||
println!(
|
||||
"{} Either provide the `--config` arg or ensure home directory exists to use the default config location",
|
||||
style("No config file found.").bold()
|
||||
);
|
||||
}
|
||||
false
|
||||
}
|
||||
("set", Some(subcommand_matches)) => {
|
||||
if let Some(config_file) = matches.value_of("config_file") {
|
||||
let mut config = Config::load(config_file).unwrap_or_default();
|
||||
("set", Some(subcommand_matches)) => {
|
||||
if let Some(url) = subcommand_matches.value_of("json_rpc_url") {
|
||||
config.json_rpc_url = url.to_string();
|
||||
// Revert to a computed `websocket_url` value when `json_rpc_url` is
|
||||
@ -70,6 +74,7 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
|
||||
if let Some(keypair) = subcommand_matches.value_of("keypair") {
|
||||
config.keypair_path = keypair.to_string();
|
||||
}
|
||||
|
||||
config.save(config_file)?;
|
||||
|
||||
let (url_setting_type, json_rpc_url) =
|
||||
@ -87,16 +92,22 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
|
||||
println_name_value_or("RPC URL:", &json_rpc_url, url_setting_type);
|
||||
println_name_value_or("WebSocket URL:", &websocket_url, ws_setting_type);
|
||||
println_name_value_or("Keypair Path:", &keypair_path, keypair_setting_type);
|
||||
} else {
|
||||
println!(
|
||||
"{} Either provide the `--config` arg or ensure home directory exists to use the default config location",
|
||||
style("No config file found.").bold()
|
||||
);
|
||||
}
|
||||
false
|
||||
("import-address-labels", Some(subcommand_matches)) => {
|
||||
let filename = value_t_or_exit!(subcommand_matches, "filename", PathBuf);
|
||||
config.import_address_labels(&filename)?;
|
||||
config.save(config_file)?;
|
||||
println!("Address labels imported from {:?}", filename);
|
||||
}
|
||||
("export-address-labels", Some(subcommand_matches)) => {
|
||||
let filename = value_t_or_exit!(subcommand_matches, "filename", PathBuf);
|
||||
config.export_address_labels(&filename)?;
|
||||
println!("Address labels exported to {:?}", filename);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
false
|
||||
}
|
||||
_ => true,
|
||||
};
|
||||
Ok(parse_args)
|
||||
@ -144,6 +155,12 @@ pub fn parse_args<'a>(
|
||||
.and_then(|sub_matches| commitment_of(sub_matches, COMMITMENT_ARG.long))
|
||||
.unwrap_or_default();
|
||||
|
||||
let address_labels = if matches.is_present("no_address_labels") {
|
||||
HashMap::new()
|
||||
} else {
|
||||
config.address_labels
|
||||
};
|
||||
|
||||
Ok((
|
||||
CliConfig {
|
||||
command,
|
||||
@ -156,6 +173,7 @@ pub fn parse_args<'a>(
|
||||
output_format,
|
||||
commitment,
|
||||
send_transaction_config: RpcSendTransactionConfig::default(),
|
||||
address_labels,
|
||||
},
|
||||
signers,
|
||||
))
|
||||
@ -217,6 +235,12 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
.global(true)
|
||||
.help("Show additional information"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("no_address_labels")
|
||||
.long("no-address-labels")
|
||||
.global(true)
|
||||
.help("Do not use address labels in the output"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("output_format")
|
||||
.long("output")
|
||||
@ -258,6 +282,28 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
.multiple(true)
|
||||
.required(true),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("import-address-labels")
|
||||
.about("Import a list of address labels")
|
||||
.arg(
|
||||
Arg::with_name("filename")
|
||||
.index(1)
|
||||
.value_name("FILENAME")
|
||||
.takes_value(true)
|
||||
.help("YAML file of address labels"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("export-address-labels")
|
||||
.about("Export the current address labels")
|
||||
.arg(
|
||||
Arg::with_name("filename")
|
||||
.index(1)
|
||||
.value_name("FILENAME")
|
||||
.takes_value(true)
|
||||
.help("YAML file to receive the current address labels"),
|
||||
),
|
||||
),
|
||||
)
|
||||
.get_matches();
|
||||
|
Reference in New Issue
Block a user