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

cli: program upgrade with offline signing (--sign-only mode) #33860

Merged
merged 8 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from 7 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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ assert_matches = { workspace = true }
solana-streamer = { workspace = true }
solana-test-validator = { workspace = true }
tempfile = { workspace = true }
test-case = { workspace = true }

[[bin]]
name = "solana"
Expand Down
171 changes: 168 additions & 3 deletions cli/src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ use {
input_parsers::*,
input_validators::*,
keypair::*,
offline::{OfflineArgs, DUMP_TRANSACTION_MESSAGE, SIGN_ONLY_ARG},
},
solana_cli_output::{
CliProgram, CliProgramAccountType, CliProgramAuthority, CliProgramBuffer, CliProgramId,
CliUpgradeableBuffer, CliUpgradeableBuffers, CliUpgradeableProgram,
CliUpgradeableProgramClosed, CliUpgradeableProgramExtended, CliUpgradeablePrograms,
return_signers_with_config, CliProgram, CliProgramAccountType, CliProgramAuthority,
CliProgramBuffer, CliProgramId, CliUpgradeableBuffer, CliUpgradeableBuffers,
CliUpgradeableProgram, CliUpgradeableProgramClosed, CliUpgradeableProgramExtended,
CliUpgradeablePrograms, ReturnSignersConfig,
},
solana_client::{
connection_cache::ConnectionCache,
Expand All @@ -40,6 +42,7 @@ use {
config::{RpcAccountInfoConfig, RpcProgramAccountsConfig, RpcSendTransactionConfig},
filter::{Memcmp, RpcFilterType},
},
solana_rpc_client_nonce_utils::blockhash_query::BlockhashQuery,
solana_sdk::{
account::Account,
account_utils::StateMut,
Expand Down Expand Up @@ -88,6 +91,15 @@ pub enum ProgramCliCommand {
allow_excessive_balance: bool,
skip_fee_check: bool,
},
Upgrade {
fee_payer_signer_index: SignerIndex,
program_pubkey: Pubkey,
joncinque marked this conversation as resolved.
Show resolved Hide resolved
buffer_pubkey: Pubkey,
upgrade_authority_signer_index: SignerIndex,
sign_only: bool,
dump_transaction_message: bool,
blockhash_query: BlockhashQuery,
},
WriteBuffer {
program_location: String,
fee_payer_signer_index: SignerIndex,
norwnd marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -220,6 +232,36 @@ impl ProgramSubCommands for App<'_, '_> {
),
),
)
.subcommand(
SubCommand::with_name("upgrade")
.about("Upgrade an upgradeable program")
.arg(pubkey!(
Arg::with_name("buffer")
.index(1)
.required(true)
.value_name("BUFFER_PUBKEY"),
"Intermediate buffer account with new program data"
))
.arg(pubkey!(
Arg::with_name("program_id")
.index(2)
.required(true)
.value_name("PROGRAM_ID"),
"Executable program's address (pubkey)"
))
.arg(fee_payer_arg())
.arg(
Arg::with_name("upgrade_authority")
.long("upgrade-authority")
.value_name("UPGRADE_AUTHORITY_SIGNER")
.takes_value(true)
.validator(is_valid_signer)
.help(
"Upgrade authority [default: the default configured keypair]",
),
)
.offline_args(),
joncinque marked this conversation as resolved.
Show resolved Hide resolved
)
.subcommand(
SubCommand::with_name("write-buffer")
.about("Writes a program into a buffer account")
Expand Down Expand Up @@ -571,6 +613,47 @@ pub fn parse_program_subcommand(
signers: signer_info.signers,
}
}
("upgrade", Some(matches)) => {
let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
let blockhash_query = BlockhashQuery::new_from_matches(matches);

let buffer_pubkey = pubkey_of_signer(matches, "buffer", wallet_manager)
.unwrap()
.unwrap();
let program_pubkey = pubkey_of_signer(matches, "program_id", wallet_manager)
.unwrap()
.unwrap();

let (fee_payer, fee_payer_pubkey) =
signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;

let mut bulk_signers = vec![
fee_payer, // if None, default signer will be supplied
];

let (upgrade_authority, upgrade_authority_pubkey) =
signer_of(matches, "upgrade_authority", wallet_manager)?;
bulk_signers.push(upgrade_authority);

let signer_info =
default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;

CliCommandInfo {
command: CliCommand::Program(ProgramCliCommand::Upgrade {
fee_payer_signer_index: signer_info.index_of(fee_payer_pubkey).unwrap(),
program_pubkey,
buffer_pubkey,
upgrade_authority_signer_index: signer_info
.index_of(upgrade_authority_pubkey)
.unwrap(),
sign_only,
dump_transaction_message,
blockhash_query,
}),
signers: signer_info.signers,
}
}
("write-buffer", Some(matches)) => {
let (fee_payer, fee_payer_pubkey) =
signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
Expand Down Expand Up @@ -816,6 +899,25 @@ pub fn process_program_subcommand(
*allow_excessive_balance,
*skip_fee_check,
),
ProgramCliCommand::Upgrade {
fee_payer_signer_index,
program_pubkey,
buffer_pubkey,
upgrade_authority_signer_index,
sign_only,
dump_transaction_message,
blockhash_query,
} => process_program_upgrade(
rpc_client,
config,
*fee_payer_signer_index,
*program_pubkey,
*buffer_pubkey,
*upgrade_authority_signer_index,
*sign_only,
*dump_transaction_message,
blockhash_query,
),
ProgramCliCommand::WriteBuffer {
program_location,
fee_payer_signer_index,
Expand Down Expand Up @@ -1175,6 +1277,69 @@ fn fetch_buffer_len(
}
}

/// Upgrade existing program using upgradeable loader
#[allow(clippy::too_many_arguments)]
fn process_program_upgrade(
rpc_client: Arc<RpcClient>,
config: &CliConfig,
fee_payer_signer_index: SignerIndex,
program_id: Pubkey,
buffer_pubkey: Pubkey,
upgrade_authority_signer_index: SignerIndex,
sign_only: bool,
dump_transaction_message: bool,
blockhash_query: &BlockhashQuery,
) -> ProcessResult {
let fee_payer_signer = config.signers[fee_payer_signer_index];
let upgrade_authority_signer = config.signers[upgrade_authority_signer_index];

let blockhash = blockhash_query.get_blockhash(&rpc_client, config.commitment)?;
let message = Message::new_with_blockhash(
&[bpf_loader_upgradeable::upgrade(
&program_id,
&buffer_pubkey,
&upgrade_authority_signer.pubkey(),
&fee_payer_signer.pubkey(),
)],
Some(&fee_payer_signer.pubkey()),
&blockhash,
);

if sign_only {
let mut tx = Transaction::new_unsigned(message);
let signers = &[fee_payer_signer, upgrade_authority_signer];
// Using try_partial_sign here because fee_payer_signer might not be the fee payer we
// end up using for this transaction (it might be NullSigner in `--sign-only` mode).
tx.try_partial_sign(signers, blockhash)?;
return_signers_with_config(
&tx,
&config.output_format,
&ReturnSignersConfig {
dump_transaction_message,
},
)
} else {
let fee = rpc_client.get_fee_for_message(&message)?;
check_account_for_spend_and_fee_with_commitment(
&rpc_client,
&fee_payer_signer.pubkey(),
0,
fee,
config.commitment,
)?;
let mut tx = Transaction::new_unsigned(message);
let signers = &[fee_payer_signer, upgrade_authority_signer];
tx.try_sign(signers, blockhash)?;
rpc_client
.send_and_confirm_transaction_with_spinner(&tx)
.map_err(|e| format!("Upgrading program failed: {e}"))?;
let program_id = CliProgramId {
program_id: program_id.to_string(),
};
Ok(config.output_format.formatted_string(&program_id))
}
}

fn process_write_buffer(
rpc_client: Arc<RpcClient>,
config: &CliConfig,
Expand Down
Loading