cli: add command to dump the upgradeable program to a file (#15029)

This commit is contained in:
Jack May
2021-02-02 22:33:03 -08:00
committed by GitHub
parent 31d30bb5e8
commit 9c6d899efb
3 changed files with 97 additions and 254 deletions

View File

@ -1533,55 +1533,28 @@ impl fmt::Display for CliProgramAuthority {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CliUpgradeableProgram { pub struct CliUpgradeableProgram {
pub program_id: String, pub program_id: String,
pub program_executable: bool,
pub program_lamports: u64,
pub programdata_address: String, pub programdata_address: String,
pub programdata_lamports: u64, pub authority: String,
pub programdata_authority: String, pub last_upgrade_slot: u64,
pub programdata_slot: u64, pub program_len: usize,
pub programdata_data_len: usize,
pub programdata_program_len: usize,
pub use_lamports_unit: bool,
} }
impl QuietDisplay for CliUpgradeableProgram {} impl QuietDisplay for CliUpgradeableProgram {}
impl VerboseDisplay for CliUpgradeableProgram {} impl VerboseDisplay for CliUpgradeableProgram {}
impl fmt::Display for CliUpgradeableProgram { impl fmt::Display for CliUpgradeableProgram {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f)?; writeln!(f)?;
writeln_name_value(f, "Program Address:", &self.program_id)?; writeln_name_value(f, "Program Id:", &self.program_id)?;
writeln_name_value(f, " Executable:", &self.program_executable.to_string())?; writeln_name_value(f, "ProgramData Address:", &self.programdata_address)?;
writeln_name_value(f, "Authority:", &self.authority)?;
writeln_name_value( writeln_name_value(
f, f,
" Balance:", "Last Upgraded In Slot:",
&build_balance_message(self.program_lamports, self.use_lamports_unit, true), &self.last_upgrade_slot.to_string(),
)?;
writeln_name_value(f, " ProgramData Address:", &self.programdata_address)?;
writeln_name_value(f, " Authority:", &self.programdata_authority)?;
writeln_name_value(
f,
" Balance:",
&build_balance_message(self.programdata_lamports, self.use_lamports_unit, true),
)?; )?;
writeln_name_value( writeln_name_value(
f, f,
" Total Length:", "Program Length:",
&format!( &format!("{:?} ({:#x?}) bytes", self.program_len, self.program_len),
"{:?} ({:#x?}) bytes",
self.programdata_data_len, self.programdata_data_len
),
)?;
writeln_name_value(
f,
" Program Length:",
&format!(
"{:?} ({:#x?}) bytes",
self.programdata_program_len, self.programdata_program_len
),
)?;
writeln_name_value(
f,
" Last Upgraded In Slot:",
&self.programdata_slot.to_string(),
)?; )?;
Ok(()) Ok(())
} }
@ -1591,11 +1564,8 @@ impl fmt::Display for CliUpgradeableProgram {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CliUpgradeableBuffer { pub struct CliUpgradeableBuffer {
pub address: String, pub address: String,
pub lamports: u64,
pub authority: String, pub authority: String,
pub data_len: usize,
pub program_len: usize, pub program_len: usize,
pub use_lamports_unit: bool,
} }
impl QuietDisplay for CliUpgradeableBuffer {} impl QuietDisplay for CliUpgradeableBuffer {}
impl VerboseDisplay for CliUpgradeableBuffer {} impl VerboseDisplay for CliUpgradeableBuffer {}
@ -1603,20 +1573,10 @@ impl fmt::Display for CliUpgradeableBuffer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f)?; writeln!(f)?;
writeln_name_value(f, "Buffer Address:", &self.address)?; writeln_name_value(f, "Buffer Address:", &self.address)?;
writeln_name_value(f, " Authority:", &self.authority)?; writeln_name_value(f, "Authority:", &self.authority)?;
writeln_name_value( writeln_name_value(
f, f,
" Balance:", "Program Length:",
&build_balance_message(self.lamports, self.use_lamports_unit, true),
)?;
writeln_name_value(
f,
" Total Length:",
&format!("{:?} ({:#x?}) bytes", self.data_len, self.data_len),
)?;
writeln_name_value(
f,
" Program Length:",
&format!("{:?} ({:#x?}) bytes", self.program_len, self.program_len), &format!("{:?} ({:#x?}) bytes", self.program_len, self.program_len),
)?; )?;
Ok(()) Ok(())

View File

@ -46,7 +46,7 @@ use std::{
collections::HashMap, collections::HashMap,
error, error,
fs::File, fs::File,
io::Read, io::{Read, Write},
net::UdpSocket, net::UdpSocket,
path::PathBuf, path::PathBuf,
sync::Arc, sync::Arc,
@ -86,12 +86,12 @@ pub enum ProgramCliCommand {
upgrade_authority_index: Option<SignerIndex>, upgrade_authority_index: Option<SignerIndex>,
new_upgrade_authority: Option<Pubkey>, new_upgrade_authority: Option<Pubkey>,
}, },
GetAuthority { Show {
account_pubkey: Option<Pubkey>, account_pubkey: Option<Pubkey>,
}, },
GetInfo { Dump {
account_pubkey: Option<Pubkey>, account_pubkey: Option<Pubkey>,
use_lamports_unit: bool, output_location: String,
}, },
} }
@ -234,7 +234,7 @@ impl ProgramSubCommands for App<'_, '_> {
.value_name("PROGRAM_ADDRESS") .value_name("PROGRAM_ADDRESS")
.takes_value(true) .takes_value(true)
.required(true) .required(true)
.help("Public key of the program to upgrade") .help("Address of the program to upgrade")
) )
.arg( .arg(
Arg::with_name("upgrade_authority") Arg::with_name("upgrade_authority")
@ -259,33 +259,35 @@ impl ProgramSubCommands for App<'_, '_> {
) )
) )
.subcommand( .subcommand(
SubCommand::with_name("get-authority") SubCommand::with_name("show")
.about("Gets a buffer or program account's authority") .about("Display information about a buffer or program")
.arg( .arg(
Arg::with_name("account") Arg::with_name("account")
.index(1) .index(1)
.value_name("ACCOUNT_ADDRESS") .value_name("ACCOUNT_ADDRESS")
.takes_value(true) .takes_value(true)
.required(true) .required(true)
.help("Public key of the account to query") .help("Address of the buffer or program to show")
), )
) )
.subcommand( .subcommand(
SubCommand::with_name("get-info") SubCommand::with_name("dump")
.about("Display a buffer or program") .about("Write the program data to a file")
.arg( .arg(
Arg::with_name("account") Arg::with_name("account")
.index(1) .index(1)
.value_name("ACCOUNT_ADDRESS") .value_name("ACCOUNT_ADDRESS")
.takes_value(true) .takes_value(true)
.required(true) .required(true)
.help("Public key of the buffer or program to query") .help("Address of the buffer or program")
) )
.arg( .arg(
Arg::with_name("lamports") Arg::with_name("output_location")
.long("lamports") .index(2)
.takes_value(false) .value_name("OUTPUT_FILEPATH")
.help("Display balance in lamports instead of SOL"), .takes_value(true)
.required(true)
.help("/path/to/program.so"),
), ),
) )
) )
@ -485,16 +487,16 @@ pub fn parse_program_subcommand(
signers: signer_info.signers, signers: signer_info.signers,
} }
} }
("get-authority", Some(matches)) => CliCommandInfo { ("show", Some(matches)) => CliCommandInfo {
command: CliCommand::Program(ProgramCliCommand::GetAuthority { command: CliCommand::Program(ProgramCliCommand::Show {
account_pubkey: pubkey_of(matches, "account"), account_pubkey: pubkey_of(matches, "account"),
}), }),
signers: vec![], signers: vec![],
}, },
("get-info", Some(matches)) => CliCommandInfo { ("dump", Some(matches)) => CliCommandInfo {
command: CliCommand::Program(ProgramCliCommand::GetInfo { command: CliCommand::Program(ProgramCliCommand::Dump {
account_pubkey: pubkey_of(matches, "account"), account_pubkey: pubkey_of(matches, "account"),
use_lamports_unit: matches.is_present("lamports"), output_location: matches.value_of("output_location").unwrap().to_string(),
}), }),
signers: vec![], signers: vec![],
}, },
@ -571,13 +573,13 @@ pub fn process_program_subcommand(
*upgrade_authority_index, *upgrade_authority_index,
*new_upgrade_authority, *new_upgrade_authority,
), ),
ProgramCliCommand::GetAuthority { account_pubkey } => { ProgramCliCommand::Show { account_pubkey } => {
process_get_authority(&rpc_client, config, *account_pubkey) process_show(&rpc_client, config, *account_pubkey)
} }
ProgramCliCommand::GetInfo { ProgramCliCommand::Dump {
account_pubkey, account_pubkey,
use_lamports_unit, output_location,
} => process_get_info(&rpc_client, config, *account_pubkey, *use_lamports_unit), } => process_dump(&rpc_client, config, *account_pubkey, output_location),
} }
} }
@ -918,70 +920,10 @@ fn process_set_authority(
Ok(config.output_format.formatted_string(&authority)) Ok(config.output_format.formatted_string(&authority))
} }
fn process_get_authority( fn process_show(
rpc_client: &RpcClient, rpc_client: &RpcClient,
config: &CliConfig, config: &CliConfig,
account_pubkey: Option<Pubkey>, account_pubkey: Option<Pubkey>,
) -> ProcessResult {
if let Some(account_pubkey) = account_pubkey {
if let Some(account) = rpc_client
.get_account_with_commitment(&account_pubkey, config.commitment)?
.value
{
if let Ok(UpgradeableLoaderState::Program {
programdata_address,
}) = account.state()
{
if let Some(account) = rpc_client
.get_account_with_commitment(&programdata_address, config.commitment)?
.value
{
if let Ok(UpgradeableLoaderState::ProgramData {
upgrade_authority_address,
..
}) = account.state()
{
let authority = CliProgramAuthority {
authority: upgrade_authority_address
.map(|pubkey| pubkey.to_string())
.unwrap_or_else(|| "none".to_string()),
account_type: CliProgramAccountType::Program,
};
Ok(config.output_format.formatted_string(&authority))
} else {
Err("Invalid associated ProgramData account found for the program".into())
}
} else {
Err(
"Failed to find associated ProgramData account for the provided program"
.into(),
)
}
} else if let Ok(UpgradeableLoaderState::Buffer { authority_address }) = account.state()
{
let authority = CliProgramAuthority {
authority: authority_address
.map(|pubkey| pubkey.to_string())
.unwrap_or_else(|| "none".to_string()),
account_type: CliProgramAccountType::Buffer,
};
Ok(config.output_format.formatted_string(&authority))
} else {
Err("Not a buffer or program account".into())
}
} else {
Err("Unable to find the account".into())
}
} else {
Err("No account specified".into())
}
}
fn process_get_info(
rpc_client: &RpcClient,
config: &CliConfig,
account_pubkey: Option<Pubkey>,
use_lamports_unit: bool,
) -> ProcessResult { ) -> ProcessResult {
if let Some(account_pubkey) = account_pubkey { if let Some(account_pubkey) = account_pubkey {
if let Some(account) = rpc_client if let Some(account) = rpc_client
@ -1005,18 +947,13 @@ fn process_get_info(
.output_format .output_format
.formatted_string(&CliUpgradeableProgram { .formatted_string(&CliUpgradeableProgram {
program_id: account_pubkey.to_string(), program_id: account_pubkey.to_string(),
program_executable: account.executable,
program_lamports: account.lamports,
programdata_address: programdata_address.to_string(), programdata_address: programdata_address.to_string(),
programdata_lamports: programdata_account.lamports, authority: upgrade_authority_address
programdata_authority: upgrade_authority_address
.map(|pubkey| pubkey.to_string()) .map(|pubkey| pubkey.to_string())
.unwrap_or_else(|| "none".to_string()), .unwrap_or_else(|| "none".to_string()),
programdata_slot: slot, last_upgrade_slot: slot,
programdata_data_len: programdata_account.data.len(), program_len: programdata_account.data.len()
programdata_program_len: programdata_account.data.len()
- UpgradeableLoaderState::programdata_data_offset()?, - UpgradeableLoaderState::programdata_data_offset()?,
use_lamports_unit,
})) }))
} else { } else {
Err("Invalid associated ProgramData account found for the program".into()) Err("Invalid associated ProgramData account found for the program".into())
@ -1033,14 +970,11 @@ fn process_get_info(
.output_format .output_format
.formatted_string(&CliUpgradeableBuffer { .formatted_string(&CliUpgradeableBuffer {
address: account_pubkey.to_string(), address: account_pubkey.to_string(),
lamports: account.lamports,
authority: authority_address authority: authority_address
.map(|pubkey| pubkey.to_string()) .map(|pubkey| pubkey.to_string())
.unwrap_or_else(|| "none".to_string()), .unwrap_or_else(|| "none".to_string()),
data_len: account.data.len(),
program_len: account.data.len() program_len: account.data.len()
- UpgradeableLoaderState::buffer_data_offset()?, - UpgradeableLoaderState::buffer_data_offset()?,
use_lamports_unit,
})) }))
} else { } else {
Err("Not a buffer or program account".into()) Err("Not a buffer or program account".into())
@ -1053,6 +987,59 @@ fn process_get_info(
} }
} }
fn process_dump(
rpc_client: &RpcClient,
config: &CliConfig,
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, config.commitment)?
.value
{
if let Ok(UpgradeableLoaderState::Program {
programdata_address,
}) = account.state()
{
if let Some(programdata_account) = rpc_client
.get_account_with_commitment(&programdata_address, config.commitment)?
.value
{
if let Ok(UpgradeableLoaderState::ProgramData { .. }) =
programdata_account.state()
{
let offset = UpgradeableLoaderState::programdata_data_offset().unwrap_or(0);
let program_data = &programdata_account.data[offset..];
let mut f = File::create(output_location)?;
f.write_all(&program_data)?;
Ok(format!("Wrote program to {}", output_location))
} else {
Err("Invalid associated ProgramData account found for the program".into())
}
} else {
Err(
"Failed to find associated ProgramData account for the provided program"
.into(),
)
}
} else if let Ok(UpgradeableLoaderState::Buffer { .. }) = account.state() {
let offset = UpgradeableLoaderState::buffer_data_offset().unwrap_or(0);
let program_data = &account.data[offset..];
let mut f = File::create(output_location)?;
f.write_all(&program_data)?;
Ok(format!("Wrote program to {}", output_location))
} else {
Err("Not a buffer or program account".into())
}
} else {
Err("Unable to find the account".into())
}
} else {
Err("No account specified".into())
}
}
/// Deploy using non-upgradeable loader /// Deploy using non-upgradeable loader
pub fn process_deploy( pub fn process_deploy(
rpc_client: &RpcClient, rpc_client: &RpcClient,
@ -2298,37 +2285,6 @@ mod tests {
); );
} }
#[test]
#[allow(clippy::cognitive_complexity)]
fn test_cli_parse_get_authority() {
let test_commands = app("test", "desc", "version");
let default_keypair = Keypair::new();
let keypair_file = make_tmp_path("keypair_file");
write_keypair_file(&default_keypair, &keypair_file).unwrap();
let default_signer = DefaultSigner {
path: keypair_file,
arg_name: "".to_string(),
};
let buffer_pubkey = Pubkey::new_unique();
let test_deploy = test_commands.clone().get_matches_from(vec![
"test",
"program",
"get-authority",
&buffer_pubkey.to_string(),
]);
assert_eq!(
parse_command(&test_deploy, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::Program(ProgramCliCommand::GetAuthority {
account_pubkey: Some(buffer_pubkey)
}),
signers: vec![],
}
);
}
#[test] #[test]
fn test_cli_keypair_file() { fn test_cli_keypair_file() {
solana_logger::setup(); solana_logger::setup();

View File

@ -438,25 +438,6 @@ fn test_cli_program_deploy_with_authority() {
program_data[..] program_data[..]
); );
// Get upgrade authority
config.signers = vec![&keypair];
config.command = CliCommand::Program(ProgramCliCommand::GetAuthority {
account_pubkey: Some(program_pubkey),
});
let response = process_command(&config);
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
let authority_pubkey_str = json
.as_object()
.unwrap()
.get("authority")
.unwrap()
.as_str()
.unwrap();
assert_eq!(
new_upgrade_authority.pubkey(),
Pubkey::from_str(&authority_pubkey_str).unwrap()
);
// Set no authority // Set no authority
config.signers = vec![&keypair, &new_upgrade_authority]; config.signers = vec![&keypair, &new_upgrade_authority];
config.command = CliCommand::Program(ProgramCliCommand::SetUpgradeAuthority { config.command = CliCommand::Program(ProgramCliCommand::SetUpgradeAuthority {
@ -525,22 +506,6 @@ fn test_cli_program_deploy_with_authority() {
} else { } else {
panic!("not a buffer account"); panic!("not a buffer account");
} }
// Get buffer authority
config.signers = vec![&keypair];
config.command = CliCommand::Program(ProgramCliCommand::GetAuthority {
account_pubkey: Some(program_pubkey),
});
let response = process_command(&config);
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
let authority_pubkey_str = json
.as_object()
.unwrap()
.get("authority")
.unwrap()
.as_str()
.unwrap();
assert_eq!("none", authority_pubkey_str);
} }
#[test] #[test]
@ -656,25 +621,6 @@ fn test_cli_program_write_buffer() {
program_data[..] program_data[..]
); );
// Get buffer authority
config.signers = vec![&keypair];
config.command = CliCommand::Program(ProgramCliCommand::GetAuthority {
account_pubkey: Some(buffer_keypair.pubkey()),
});
let response = process_command(&config);
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
let authority_pubkey_str = json
.as_object()
.unwrap()
.get("authority")
.unwrap()
.as_str()
.unwrap();
assert_eq!(
keypair.pubkey(),
Pubkey::from_str(&authority_pubkey_str).unwrap()
);
// Specify buffer authority // Specify buffer authority
let buffer_keypair = Keypair::new(); let buffer_keypair = Keypair::new();
let authority_keypair = Keypair::new(); let authority_keypair = Keypair::new();
@ -745,25 +691,6 @@ fn test_cli_program_write_buffer() {
buffer_account.data[UpgradeableLoaderState::buffer_data_offset().unwrap()..], buffer_account.data[UpgradeableLoaderState::buffer_data_offset().unwrap()..],
program_data[..] program_data[..]
); );
// Get buffer authority
config.signers = vec![&keypair];
config.command = CliCommand::Program(ProgramCliCommand::GetAuthority {
account_pubkey: Some(buffer_pubkey),
});
let response = process_command(&config);
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
let authority_pubkey_str = json
.as_object()
.unwrap()
.get("authority")
.unwrap()
.as_str()
.unwrap();
assert_eq!(
authority_keypair.pubkey(),
Pubkey::from_str(&authority_pubkey_str).unwrap()
);
} }
#[test] #[test]