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

Restore ability for programs to upgrade themselves #20265

Merged
merged 4 commits into from
Sep 28, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
96 changes: 92 additions & 4 deletions programs/bpf/tests/programs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1867,7 +1867,7 @@ fn test_program_bpf_upgrade_and_invoke_in_same_tx() {
"solana_bpf_rust_panic",
);

// Attempt to invoke, then upgrade the program in same tx
// Invoke, then upgrade the program, and then invoke again in same tx
let message = Message::new(
&[
invoke_instruction.clone(),
Expand All @@ -1886,12 +1886,10 @@ fn test_program_bpf_upgrade_and_invoke_in_same_tx() {
message.clone(),
bank.last_blockhash(),
);
// program_id is automatically demoted to readonly, preventing the upgrade, which requires
// writeability
let (result, _) = process_transaction_and_record_inner(&bank, tx);
assert_eq!(
result.unwrap_err(),
TransactionError::InstructionError(1, InstructionError::InvalidArgument)
TransactionError::InstructionError(2, InstructionError::ProgramFailedToComplete)
);
}

Expand Down Expand Up @@ -2187,6 +2185,96 @@ fn test_program_bpf_upgrade_via_cpi() {
assert_ne!(programdata, original_programdata);
}

#[cfg(feature = "bpf_rust")]
#[test]
fn test_program_bpf_upgrade_self_via_cpi() {
solana_logger::setup();

let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let mut bank = Bank::new_for_tests(&genesis_config);
let (name, id, entrypoint) = solana_bpf_loader_program!();
bank.add_builtin(&name, id, entrypoint);
let (name, id, entrypoint) = solana_bpf_loader_upgradeable_program!();
bank.add_builtin(&name, id, entrypoint);
let bank = Arc::new(bank);
let bank_client = BankClient::new_shared(&bank);
let noop_program_id = load_bpf_program(
&bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_bpf_rust_noop",
);

// Deploy upgradeable program
let buffer_keypair = Keypair::new();
let program_keypair = Keypair::new();
let program_id = program_keypair.pubkey();
let authority_keypair = Keypair::new();
load_upgradeable_bpf_program(
&bank_client,
&mint_keypair,
&buffer_keypair,
&program_keypair,
&authority_keypair,
"solana_bpf_rust_invoke_and_return",
);

let mut invoke_instruction = Instruction::new_with_bytes(
program_id,
&[0],
vec![
AccountMeta::new_readonly(noop_program_id, false),
AccountMeta::new_readonly(noop_program_id, false),
AccountMeta::new_readonly(clock::id(), false),
],
);

// Call the upgraded program
invoke_instruction.data[0] += 1;
let result =
bank_client.send_and_confirm_instruction(&mint_keypair, invoke_instruction.clone());
assert!(result.is_ok());

// Prepare for upgrade
let buffer_keypair = Keypair::new();
load_upgradeable_buffer(
&bank_client,
&mint_keypair,
&buffer_keypair,
&authority_keypair,
"solana_bpf_rust_panic",
);

// Invoke, then upgrade the program, and then invoke again in same tx
let message = Message::new(
&[
invoke_instruction.clone(),
bpf_loader_upgradeable::upgrade(
&program_id,
&buffer_keypair.pubkey(),
&authority_keypair.pubkey(),
&mint_keypair.pubkey(),
),
invoke_instruction,
],
Some(&mint_keypair.pubkey()),
);
let tx = Transaction::new(
&[&mint_keypair, &authority_keypair],
message.clone(),
bank.last_blockhash(),
);
let (result, _) = process_transaction_and_record_inner(&bank, tx);
assert_eq!(
result.unwrap_err(),
TransactionError::InstructionError(2, InstructionError::ProgramFailedToComplete)
);
}

#[cfg(feature = "bpf_rust")]
#[test]
fn test_program_bpf_set_upgrade_authority_via_cpi() {
Expand Down
9 changes: 1 addition & 8 deletions runtime/src/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,6 @@ impl Accounts {
let rent_for_sysvars = feature_set.is_active(&feature_set::rent_for_sysvars::id());
let demote_program_write_locks =
feature_set.is_active(&feature_set::demote_program_write_locks::id());
let is_upgradeable_loader_present = is_upgradeable_loader_present(message);

for (i, key) in message.account_keys_iter().enumerate() {
let account = if !message.is_non_loader_key(i) {
Expand Down Expand Up @@ -280,7 +279,7 @@ impl Accounts {
if bpf_loader_upgradeable::check_id(account.owner()) {
if demote_program_write_locks
&& message.is_writable(i, demote_program_write_locks)
&& !is_upgradeable_loader_present
&& !message.is_upgradeable_loader_present()
{
error_counters.invalid_writable_account += 1;
return Err(TransactionError::InvalidWritableAccount);
Expand Down Expand Up @@ -1133,12 +1132,6 @@ pub fn prepare_if_nonce_account(
false
}

fn is_upgradeable_loader_present(message: &SanitizedMessage) -> bool {
message
.account_keys_iter()
.any(|&key| key == bpf_loader_upgradeable::id())
}

pub fn create_test_accounts(
accounts: &Accounts,
pubkeys: &mut Vec<Pubkey>,
Expand Down
12 changes: 11 additions & 1 deletion sdk/program/src/message/legacy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,9 @@ impl Message {
}

pub fn is_writable(&self, i: usize, demote_program_write_locks: bool) -> bool {
let demote_program_id = demote_program_write_locks
&& self.is_key_called_as_program(i)
&& !self.is_upgradeable_loader_present();
(i < (self.header.num_required_signatures - self.header.num_readonly_signed_accounts)
as usize
|| (i >= self.header.num_required_signatures as usize
Expand All @@ -363,7 +366,7 @@ impl Message {
let key = self.account_keys[i];
sysvar::is_sysvar_id(&key) || BUILTIN_PROGRAMS_KEYS.contains(&key)
}
&& !(demote_program_write_locks && self.is_key_called_as_program(i))
&& !demote_program_id
}

pub fn is_signer(&self, i: usize) -> bool {
Expand Down Expand Up @@ -503,6 +506,13 @@ impl Message {
}
false
}

/// Returns true if any account is the bpf upgradeable loader
pub fn is_upgradeable_loader_present(&self) -> bool {
self.account_keys
.iter()
.any(|&key| key == bpf_loader_upgradeable::id())
}
}

#[cfg(test)]
Expand Down
15 changes: 13 additions & 2 deletions sdk/program/src/message/mapped.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use {
crate::{
bpf_loader_upgradeable,
message::{legacy::BUILTIN_PROGRAMS_KEYS, v0},
pubkey::Pubkey,
sysvar,
Expand Down Expand Up @@ -99,8 +100,12 @@ impl MappedMessage {
pub fn is_writable(&self, key_index: usize, demote_program_write_locks: bool) -> bool {
if self.is_writable_index(key_index) {
if let Some(key) = self.get_account_key(key_index) {
return !(sysvar::is_sysvar_id(key) || BUILTIN_PROGRAMS_KEYS.contains(key)
|| (demote_program_write_locks && self.is_key_called_as_program(key_index)));
let demote_program_id = demote_program_write_locks
&& self.is_key_called_as_program(key_index)
&& !self.is_upgradeable_loader_present();
return !(sysvar::is_sysvar_id(key)
|| BUILTIN_PROGRAMS_KEYS.contains(key)
|| demote_program_id);
}
}
false
Expand All @@ -116,6 +121,12 @@ impl MappedMessage {
false
}
}

/// Returns true if any account is the bpf upgradeable loader
pub fn is_upgradeable_loader_present(&self) -> bool {
self.account_keys_iter()
.any(|&key| key == bpf_loader_upgradeable::id())
}
}

#[cfg(test)]
Expand Down
8 changes: 8 additions & 0 deletions sdk/program/src/message/sanitized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,14 @@ impl SanitizedMessage {
.saturating_add(num_secp256k1_signatures),
)
}

/// Inspect all message keys for the bpf upgradeable loader
pub fn is_upgradeable_loader_present(&self) -> bool {
match self {
Self::Legacy(message) => message.is_upgradeable_loader_present(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side note, but @jstarry this might be clearer if the messages implemented a trait containing is_upgradeable_loader_present?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, sounds good to me

Self::V0(message) => message.is_upgradeable_loader_present(),
}
}
}

#[cfg(test)]
Expand Down
2 changes: 1 addition & 1 deletion sdk/src/feature_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ lazy_static! {
(instructions_sysvar_owned_by_sysvar::id(), "fix owner for instructions sysvar"),
(close_upgradeable_program_accounts::id(), "enable closing upgradeable program accounts"),
(stake_program_advance_activating_credits_observed::id(), "Enable advancing credits observed for activation epoch #19309"),
(demote_program_write_locks::id(), "demote program write locks to readonly #19593"),
(demote_program_write_locks::id(), "demote program write locks to readonly, except when upgradeable loader present #19593 #20265"),
(ed25519_program_enabled::id(), "enable builtin ed25519 signature verify program"),
(allow_native_ids::id(), "allow native program ids in program derived addresses"),
(check_seed_length::id(), "Check program address seed lengths"),
Expand Down