Add CLI command to show/dump v4 programs (#33693)
This commit is contained in:
parent
09e858d939
commit
1155d46266
|
@ -2148,6 +2148,72 @@ impl fmt::Display for CliProgram {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliProgramV4 {
|
||||
pub program_id: String,
|
||||
pub owner: String,
|
||||
pub authority: String,
|
||||
pub last_deploy_slot: u64,
|
||||
pub status: String,
|
||||
pub data_len: usize,
|
||||
}
|
||||
impl QuietDisplay for CliProgramV4 {}
|
||||
impl VerboseDisplay for CliProgramV4 {}
|
||||
impl fmt::Display for CliProgramV4 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln_name_value(f, "Program Id:", &self.program_id)?;
|
||||
writeln_name_value(f, "Owner:", &self.owner)?;
|
||||
writeln_name_value(f, "Authority:", &self.authority)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Last Deployed In Slot:",
|
||||
&self.last_deploy_slot.to_string(),
|
||||
)?;
|
||||
writeln_name_value(f, "Status:", &self.status)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Data Length:",
|
||||
&format!("{:?} ({:#x?}) bytes", self.data_len, self.data_len),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliProgramsV4 {
|
||||
pub programs: Vec<CliProgramV4>,
|
||||
}
|
||||
impl QuietDisplay for CliProgramsV4 {}
|
||||
impl VerboseDisplay for CliProgramsV4 {}
|
||||
impl fmt::Display for CliProgramsV4 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!(
|
||||
"{:<44} | {:<9} | {:<44} | {:<10}",
|
||||
"Program Id", "Slot", "Authority", "Status"
|
||||
))
|
||||
.bold()
|
||||
)?;
|
||||
for program in self.programs.iter() {
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
&format!(
|
||||
"{:<44} | {:<9} | {:<44} | {:<10}",
|
||||
program.program_id, program.last_deploy_slot, program.authority, program.status,
|
||||
)
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliUpgradeableProgram {
|
||||
|
|
|
@ -9,12 +9,13 @@ use {
|
|||
},
|
||||
clap::{App, AppSettings, Arg, ArgMatches, SubCommand},
|
||||
log::*,
|
||||
solana_account_decoder::{UiAccountEncoding, UiDataSliceConfig},
|
||||
solana_clap_utils::{
|
||||
input_parsers::{pubkey_of, pubkey_of_signer, signer_of},
|
||||
input_validators::is_valid_signer,
|
||||
input_validators::{is_valid_pubkey, is_valid_signer},
|
||||
keypair::{DefaultSigner, SignerIndex},
|
||||
},
|
||||
solana_cli_output::{CliProgramId, OutputFormat},
|
||||
solana_cli_output::{CliProgramId, CliProgramV4, CliProgramsV4, OutputFormat},
|
||||
solana_client::{
|
||||
connection_cache::ConnectionCache,
|
||||
send_and_confirm_transactions_in_parallel::{
|
||||
|
@ -26,7 +27,10 @@ use {
|
|||
solana_rbpf::{elf::Executable, verifier::RequisiteVerifier},
|
||||
solana_remote_wallet::remote_wallet::RemoteWalletManager,
|
||||
solana_rpc_client::rpc_client::RpcClient,
|
||||
solana_rpc_client_api::config::RpcSendTransactionConfig,
|
||||
solana_rpc_client_api::{
|
||||
config::{RpcAccountInfoConfig, RpcProgramAccountsConfig, RpcSendTransactionConfig},
|
||||
filter::{Memcmp, RpcFilterType},
|
||||
},
|
||||
solana_sdk::{
|
||||
account::Account,
|
||||
commitment_config::CommitmentConfig,
|
||||
|
@ -42,7 +46,14 @@ use {
|
|||
system_instruction::{self, SystemError},
|
||||
transaction::Transaction,
|
||||
},
|
||||
std::{cmp::Ordering, fs::File, io::Read, rc::Rc, sync::Arc},
|
||||
std::{
|
||||
cmp::Ordering,
|
||||
fs::File,
|
||||
io::{Read, Write},
|
||||
mem::size_of,
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
|
@ -66,6 +77,15 @@ pub enum ProgramV4CliCommand {
|
|||
program_address: Pubkey,
|
||||
authority_signer_index: SignerIndex,
|
||||
},
|
||||
Show {
|
||||
account_pubkey: Option<Pubkey>,
|
||||
authority: Pubkey,
|
||||
all: bool,
|
||||
},
|
||||
Dump {
|
||||
account_pubkey: Option<Pubkey>,
|
||||
output_location: String,
|
||||
},
|
||||
}
|
||||
|
||||
pub trait ProgramV4SubCommands {
|
||||
|
@ -177,6 +197,51 @@ impl ProgramV4SubCommands for App<'_, '_> {
|
|||
.help("Program authority [default: the default configured keypair]")
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("show")
|
||||
.about("Display information about a buffer or program")
|
||||
.arg(
|
||||
Arg::with_name("account")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.help("Address of the program to show")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("all")
|
||||
.long("all")
|
||||
.conflicts_with("account")
|
||||
.conflicts_with("buffer_authority")
|
||||
.help("Show accounts for all authorities")
|
||||
)
|
||||
.arg(
|
||||
pubkey!(Arg::with_name("authority")
|
||||
.long("authority")
|
||||
.value_name("AUTHORITY")
|
||||
.conflicts_with("all"),
|
||||
"Authority [default: the default configured keypair]"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("dump")
|
||||
.about("Write the program data to a file")
|
||||
.arg(
|
||||
Arg::with_name("account")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("Address of the buffer or program")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("output_location")
|
||||
.index(2)
|
||||
.value_name("OUTPUT_FILEPATH")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("/path/to/program.so"),
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -306,6 +371,32 @@ pub fn parse_program_v4_subcommand(
|
|||
signers: signer_info.signers,
|
||||
}
|
||||
}
|
||||
("show", Some(matches)) => {
|
||||
let authority =
|
||||
if let Some(authority) = pubkey_of_signer(matches, "authority", wallet_manager)? {
|
||||
authority
|
||||
} else {
|
||||
default_signer
|
||||
.signer_from_path(matches, wallet_manager)?
|
||||
.pubkey()
|
||||
};
|
||||
|
||||
CliCommandInfo {
|
||||
command: CliCommand::ProgramV4(ProgramV4CliCommand::Show {
|
||||
account_pubkey: pubkey_of(matches, "account"),
|
||||
authority,
|
||||
all: matches.is_present("all"),
|
||||
}),
|
||||
signers: vec![],
|
||||
}
|
||||
}
|
||||
("dump", Some(matches)) => CliCommandInfo {
|
||||
command: CliCommand::ProgramV4(ProgramV4CliCommand::Dump {
|
||||
account_pubkey: pubkey_of(matches, "account"),
|
||||
output_location: matches.value_of("output_location").unwrap().to_string(),
|
||||
}),
|
||||
signers: vec![],
|
||||
},
|
||||
_ => unreachable!(),
|
||||
};
|
||||
Ok(response)
|
||||
|
@ -415,6 +506,20 @@ pub fn process_program_v4_subcommand(
|
|||
&ProgramV4CommandConfig::new_from_cli_config(config, authority_signer_index),
|
||||
program_address,
|
||||
),
|
||||
ProgramV4CliCommand::Show {
|
||||
account_pubkey,
|
||||
authority,
|
||||
all,
|
||||
} => process_show(rpc_client, config, *account_pubkey, *authority, *all),
|
||||
ProgramV4CliCommand::Dump {
|
||||
account_pubkey,
|
||||
output_location,
|
||||
} => process_dump(
|
||||
rpc_client,
|
||||
config.commitment,
|
||||
*account_pubkey,
|
||||
output_location,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -601,6 +706,78 @@ fn process_finalize_program(
|
|||
Ok(config.output_format.formatted_string(&program_id))
|
||||
}
|
||||
|
||||
fn process_show(
|
||||
rpc_client: Arc<RpcClient>,
|
||||
config: &CliConfig,
|
||||
account_pubkey: Option<Pubkey>,
|
||||
authority: Pubkey,
|
||||
all: bool,
|
||||
) -> ProcessResult {
|
||||
if let Some(account_pubkey) = account_pubkey {
|
||||
if let Some(account) = rpc_client
|
||||
.get_account_with_commitment(&account_pubkey, config.commitment)?
|
||||
.value
|
||||
{
|
||||
if loader_v4::check_id(&account.owner) {
|
||||
if let Ok(state) = solana_loader_v4_program::get_state(&account.data) {
|
||||
let status = match state.status {
|
||||
LoaderV4Status::Retracted => "retracted",
|
||||
LoaderV4Status::Deployed => "deployed",
|
||||
LoaderV4Status::Finalized => "finalized",
|
||||
};
|
||||
Ok(config.output_format.formatted_string(&CliProgramV4 {
|
||||
program_id: account_pubkey.to_string(),
|
||||
owner: account.owner.to_string(),
|
||||
authority: state.authority_address.to_string(),
|
||||
last_deploy_slot: state.slot,
|
||||
data_len: account
|
||||
.data
|
||||
.len()
|
||||
.saturating_sub(LoaderV4State::program_data_offset()),
|
||||
status: status.to_string(),
|
||||
}))
|
||||
} else {
|
||||
Err(format!("{account_pubkey} SBF program state is invalid").into())
|
||||
}
|
||||
} else {
|
||||
Err(format!("{account_pubkey} is not an SBF program").into())
|
||||
}
|
||||
} else {
|
||||
Err(format!("Unable to find the account {account_pubkey}").into())
|
||||
}
|
||||
} else {
|
||||
let authority_pubkey = if all { None } else { Some(authority) };
|
||||
let programs = get_programs(rpc_client, authority_pubkey)?;
|
||||
Ok(config.output_format.formatted_string(&programs))
|
||||
}
|
||||
}
|
||||
|
||||
fn process_dump(
|
||||
rpc_client: Arc<RpcClient>,
|
||||
commitment: CommitmentConfig,
|
||||
account_pubkey: Option<Pubkey>,
|
||||
output_location: &str,
|
||||
) -> ProcessResult {
|
||||
if let Some(account_pubkey) = account_pubkey {
|
||||
if let Some(account) = rpc_client
|
||||
.get_account_with_commitment(&account_pubkey, commitment)?
|
||||
.value
|
||||
{
|
||||
if loader_v4::check_id(&account.owner) {
|
||||
let mut f = File::create(output_location)?;
|
||||
f.write_all(&account.data[LoaderV4State::program_data_offset()..])?;
|
||||
Ok(format!("Wrote program to {output_location}"))
|
||||
} else {
|
||||
Err(format!("{account_pubkey} is not an SBF program").into())
|
||||
}
|
||||
} else {
|
||||
Err(format!("Unable to find the account {account_pubkey}").into())
|
||||
}
|
||||
} else {
|
||||
Err("No account specified".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn check_payer(
|
||||
rpc_client: &RpcClient,
|
||||
config: &ProgramV4CommandConfig,
|
||||
|
@ -1025,6 +1202,70 @@ fn build_truncate_instructions(
|
|||
}
|
||||
}
|
||||
|
||||
fn get_accounts_with_filter(
|
||||
rpc_client: Arc<RpcClient>,
|
||||
filters: Vec<RpcFilterType>,
|
||||
length: usize,
|
||||
) -> Result<Vec<(Pubkey, Account)>, Box<dyn std::error::Error>> {
|
||||
let results = rpc_client.get_program_accounts_with_config(
|
||||
&loader_v4::id(),
|
||||
RpcProgramAccountsConfig {
|
||||
filters: Some(filters),
|
||||
account_config: RpcAccountInfoConfig {
|
||||
encoding: Some(UiAccountEncoding::Base64),
|
||||
data_slice: Some(UiDataSliceConfig { offset: 0, length }),
|
||||
..RpcAccountInfoConfig::default()
|
||||
},
|
||||
..RpcProgramAccountsConfig::default()
|
||||
},
|
||||
)?;
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
fn get_programs(
|
||||
rpc_client: Arc<RpcClient>,
|
||||
authority_pubkey: Option<Pubkey>,
|
||||
) -> Result<CliProgramsV4, Box<dyn std::error::Error>> {
|
||||
let filters = if let Some(authority_pubkey) = authority_pubkey {
|
||||
vec![
|
||||
(RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
|
||||
size_of::<u64>(),
|
||||
authority_pubkey.as_ref(),
|
||||
))),
|
||||
]
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let results =
|
||||
get_accounts_with_filter(rpc_client, filters, LoaderV4State::program_data_offset())?;
|
||||
|
||||
let mut programs = vec![];
|
||||
for (program, account) in results.iter() {
|
||||
if let Ok(state) = solana_loader_v4_program::get_state(&account.data) {
|
||||
let status = match state.status {
|
||||
LoaderV4Status::Retracted => "retracted",
|
||||
LoaderV4Status::Deployed => "deployed",
|
||||
LoaderV4Status::Finalized => "finalized",
|
||||
};
|
||||
programs.push(CliProgramV4 {
|
||||
program_id: program.to_string(),
|
||||
owner: account.owner.to_string(),
|
||||
authority: state.authority_address.to_string(),
|
||||
last_deploy_slot: state.slot,
|
||||
status: status.to_string(),
|
||||
data_len: account
|
||||
.data
|
||||
.len()
|
||||
.saturating_sub(LoaderV4State::program_data_offset()),
|
||||
});
|
||||
} else {
|
||||
return Err(format!("Error parsing Program account {program}").into());
|
||||
}
|
||||
}
|
||||
Ok(CliProgramsV4 { programs })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use {
|
||||
|
|
Loading…
Reference in New Issue