Skip to content

Commit

Permalink
cli: program upgrade with offline signing (--sign-only mode) (#33860)
Browse files Browse the repository at this point in the history
* cli: program upgrade with offline signing (--sign-only mode)

* ditch universal NullSigner approach as overly complex

* rearrange some code to make it more coherent, fix typos

* apply clippy suggestions to simplify code

* review pass

* adjust brokens (due to rebase onto refactored structures) doc links

* fix linter complaint in doc formatting

* remove rebase artifacts

---------

Co-authored-by: norwnd <norwnd>
  • Loading branch information
norwnd authored Dec 14, 2023
1 parent 47dab26 commit 0be3b6e
Show file tree
Hide file tree
Showing 5 changed files with 468 additions and 6 deletions.
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,
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,
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(),
)
.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

0 comments on commit 0be3b6e

Please sign in to comment.