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
This commit is contained in:
parent
30bce9ddbc
commit
2cd9dc99b6
|
@ -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(),
|
||||
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
|
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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>,
|
||||
|
|
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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)]
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use {
|
||||
crate::{
|
||||
bpf_loader_upgradeable,
|
||||
message::{legacy::BUILTIN_PROGRAMS_KEYS, v0},
|
||||
pubkey::Pubkey,
|
||||
sysvar,
|
||||
|
@ -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
|
||||
|
@ -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)]
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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"),
|
||||
|
|
Loading…
Reference in New Issue