Skip to content

Commit

Permalink
Restore ability for programs to upgrade themselves (#20265)
Browse files Browse the repository at this point in the history
* Make helper associated fn

* Add feature definition

* Add handling to preserve program-id write lock when upgradeable loader is present; restore bpf upgrade-self test

* Use single feature
  • Loading branch information
CriesofCarrots authored Sep 28, 2021
1 parent 30bce9d commit 2cd9dc9
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 16 deletions.
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(),
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

0 comments on commit 2cd9dc9

Please sign in to comment.