Skip to content

Commit

Permalink
Restore ability for programs to upgrade themselves (backport #20265) (#…
Browse files Browse the repository at this point in the history
…20295)

* Restore ability for programs to upgrade themselves (#20265)

* 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

(cherry picked from commit 2cd9dc9)

# Conflicts:
#	runtime/src/accounts.rs
#	sdk/program/src/message.rs
#	sdk/program/src/message/mapped.rs
#	sdk/program/src/message/sanitized.rs
#	sdk/src/feature_set.rs

* Fix conflicts

Co-authored-by: Tyera Eulberg <[email protected]>
Co-authored-by: Tyera Eulberg <[email protected]>
  • Loading branch information
3 people authored Sep 29, 2021
1 parent 2f2948f commit a005a6b
Show file tree
Hide file tree
Showing 6 changed files with 1,010 additions and 15 deletions.
96 changes: 92 additions & 4 deletions programs/bpf/tests/programs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1782,7 +1782,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 @@ -1801,12 +1801,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 @@ -2105,6 +2103,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(&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
10 changes: 1 addition & 9 deletions runtime/src/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,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 key_check.is_non_loader_key(key, i) {
Expand Down Expand Up @@ -250,7 +249,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 @@ -1126,13 +1125,6 @@ pub fn prepare_if_nonce_account(
false
}

fn is_upgradeable_loader_present(message: &Message) -> 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.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,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 @@ -411,7 +414,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 @@ -537,6 +540,13 @@ impl Message {
.min(self.header.num_required_signatures as usize);
self.account_keys[..last_key].iter().collect()
}

/// 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
Loading

0 comments on commit a005a6b

Please sign in to comment.