Feature - Program redeployment cooldown (#29136)

* Registers the feature enable_program_redeployment_cooldown.

* Adds redeployment slot constraint.

* Adds test to assert that closed programs can not be reopened.

* Ensure that program close truncates the account data.

* Adds set_sysvar_for_tests() to SBF program tests.
This commit is contained in:
Alexander Meißner 2022-12-13 22:54:24 +01:00 committed by GitHub
parent 3e649d2aa0
commit 721496b900
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 146 additions and 12 deletions

View File

@ -43,8 +43,8 @@ use {
cap_accounts_data_allocations_per_transaction, cap_bpf_program_instruction_accounts, cap_accounts_data_allocations_per_transaction, cap_bpf_program_instruction_accounts,
check_slice_translation_size, disable_deploy_of_alloc_free_syscall, check_slice_translation_size, disable_deploy_of_alloc_free_syscall,
disable_deprecated_loader, enable_bpf_loader_extend_program_ix, disable_deprecated_loader, enable_bpf_loader_extend_program_ix,
enable_bpf_loader_set_authority_checked_ix, limit_max_instruction_trace_length, enable_bpf_loader_set_authority_checked_ix, enable_program_redeployment_cooldown,
FeatureSet, limit_max_instruction_trace_length, FeatureSet,
}, },
instruction::{AccountMeta, InstructionError}, instruction::{AccountMeta, InstructionError},
loader_instruction::LoaderInstruction, loader_instruction::LoaderInstruction,
@ -601,7 +601,6 @@ fn process_loader_upgradeable_instruction(
} }
// Create ProgramData account // Create ProgramData account
let (derived_address, bump_seed) = let (derived_address, bump_seed) =
Pubkey::find_program_address(&[new_program_id.as_ref()], program_id); Pubkey::find_program_address(&[new_program_id.as_ref()], program_id);
if derived_address != programdata_key { if derived_address != programdata_key {
@ -801,10 +800,18 @@ fn process_loader_upgradeable_instruction(
return Err(InstructionError::InsufficientFunds); return Err(InstructionError::InsufficientFunds);
} }
if let UpgradeableLoaderState::ProgramData { if let UpgradeableLoaderState::ProgramData {
slot: _, slot,
upgrade_authority_address, upgrade_authority_address,
} = programdata.get_state()? } = programdata.get_state()?
{ {
if invoke_context
.feature_set
.is_active(&enable_program_redeployment_cooldown::id())
&& clock.slot == slot
{
ic_logger_msg!(log_collector, "Program was deployed in this block already");
return Err(InstructionError::InvalidArgument);
}
if upgrade_authority_address.is_none() { if upgrade_authority_address.is_none() {
ic_logger_msg!(log_collector, "Program not upgradeable"); ic_logger_msg!(log_collector, "Program not upgradeable");
return Err(InstructionError::Immutable); return Err(InstructionError::Immutable);
@ -1048,7 +1055,14 @@ fn process_loader_upgradeable_instruction(
let mut close_account = let mut close_account =
instruction_context.try_borrow_instruction_account(transaction_context, 0)?; instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
let close_key = *close_account.get_key(); let close_key = *close_account.get_key();
match close_account.get_state()? { let close_account_state = close_account.get_state()?;
if invoke_context
.feature_set
.is_active(&enable_program_redeployment_cooldown::id())
{
close_account.set_data_length(UpgradeableLoaderState::size_of_uninitialized())?;
}
match close_account_state {
UpgradeableLoaderState::Uninitialized => { UpgradeableLoaderState::Uninitialized => {
let mut recipient_account = instruction_context let mut recipient_account = instruction_context
.try_borrow_instruction_account(transaction_context, 1)?; .try_borrow_instruction_account(transaction_context, 1)?;
@ -1518,7 +1532,7 @@ mod tests {
instruction::{AccountMeta, InstructionError}, instruction::{AccountMeta, InstructionError},
pubkey::Pubkey, pubkey::Pubkey,
rent::Rent, rent::Rent,
sysvar, system_program, sysvar,
}, },
std::{fs::File, io::Read, ops::Range}, std::{fs::File, io::Read, ops::Range},
}; };
@ -2299,7 +2313,7 @@ mod tests {
let spill_account = AccountSharedData::new(0, 0, &Pubkey::new_unique()); let spill_account = AccountSharedData::new(0, 0, &Pubkey::new_unique());
let rent_account = create_account_for_test(&rent); let rent_account = create_account_for_test(&rent);
let clock_account = create_account_for_test(&Clock { let clock_account = create_account_for_test(&Clock {
slot: SLOT, slot: SLOT.saturating_add(1),
..Clock::default() ..Clock::default()
}); });
let upgrade_authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique()); let upgrade_authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique());
@ -2394,7 +2408,7 @@ mod tests {
assert_eq!( assert_eq!(
state, state,
UpgradeableLoaderState::ProgramData { UpgradeableLoaderState::ProgramData {
slot: SLOT, slot: SLOT.saturating_add(1),
upgrade_authority_address: Some(upgrade_authority_address) upgrade_authority_address: Some(upgrade_authority_address)
} }
); );
@ -3529,7 +3543,7 @@ mod tests {
let recipient_account = AccountSharedData::new(1, 0, &Pubkey::new_unique()); let recipient_account = AccountSharedData::new(1, 0, &Pubkey::new_unique());
let buffer_address = Pubkey::new_unique(); let buffer_address = Pubkey::new_unique();
let mut buffer_account = let mut buffer_account =
AccountSharedData::new(1, UpgradeableLoaderState::size_of_buffer(0), &loader_id); AccountSharedData::new(1, UpgradeableLoaderState::size_of_buffer(128), &loader_id);
buffer_account buffer_account
.set_state(&UpgradeableLoaderState::Buffer { .set_state(&UpgradeableLoaderState::Buffer {
authority_address: Some(authority_address), authority_address: Some(authority_address),
@ -3547,7 +3561,7 @@ mod tests {
let programdata_address = Pubkey::new_unique(); let programdata_address = Pubkey::new_unique();
let mut programdata_account = AccountSharedData::new( let mut programdata_account = AccountSharedData::new(
1, 1,
UpgradeableLoaderState::size_of_programdata(0), UpgradeableLoaderState::size_of_programdata(128),
&loader_id, &loader_id,
); );
programdata_account programdata_account
@ -3603,6 +3617,10 @@ mod tests {
assert_eq!(2, accounts.get(1).unwrap().lamports()); assert_eq!(2, accounts.get(1).unwrap().lamports());
let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap(); let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap();
assert_eq!(state, UpgradeableLoaderState::Uninitialized); assert_eq!(state, UpgradeableLoaderState::Uninitialized);
assert_eq!(
UpgradeableLoaderState::size_of_uninitialized(),
accounts.first().unwrap().data().len()
);
// Case: close with wrong authority // Case: close with wrong authority
process_instruction( process_instruction(
@ -3651,6 +3669,10 @@ mod tests {
assert_eq!(2, accounts.get(1).unwrap().lamports()); assert_eq!(2, accounts.get(1).unwrap().lamports());
let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap(); let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap();
assert_eq!(state, UpgradeableLoaderState::Uninitialized); assert_eq!(state, UpgradeableLoaderState::Uninitialized);
assert_eq!(
UpgradeableLoaderState::size_of_uninitialized(),
accounts.first().unwrap().data().len()
);
// Case: close a program account // Case: close a program account
let accounts = process_instruction( let accounts = process_instruction(
@ -3659,8 +3681,8 @@ mod tests {
&instruction, &instruction,
vec![ vec![
(programdata_address, programdata_account.clone()), (programdata_address, programdata_account.clone()),
(recipient_address, recipient_account), (recipient_address, recipient_account.clone()),
(authority_address, authority_account), (authority_address, authority_account.clone()),
(program_address, program_account.clone()), (program_address, program_account.clone()),
], ],
vec![ vec![
@ -3683,8 +3705,14 @@ mod tests {
assert_eq!(2, accounts.get(1).unwrap().lamports()); assert_eq!(2, accounts.get(1).unwrap().lamports());
let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap(); let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap();
assert_eq!(state, UpgradeableLoaderState::Uninitialized); assert_eq!(state, UpgradeableLoaderState::Uninitialized);
assert_eq!(
UpgradeableLoaderState::size_of_uninitialized(),
accounts.first().unwrap().data().len()
);
// Try to invoke closed account // Try to invoke closed account
programdata_account = accounts.first().unwrap().clone();
program_account = accounts.get(3).unwrap().clone();
process_instruction( process_instruction(
&program_address, &program_address,
&[0, 1], &[0, 1],
@ -3696,6 +3724,78 @@ mod tests {
Vec::new(), Vec::new(),
Err(InstructionError::InvalidAccountData), Err(InstructionError::InvalidAccountData),
); );
// Case: Reopen should fail
process_instruction(
&loader_id,
&[],
&bincode::serialize(&UpgradeableLoaderInstruction::DeployWithMaxDataLen {
max_data_len: 0,
})
.unwrap(),
vec![
(recipient_address, recipient_account),
(programdata_address, programdata_account),
(program_address, program_account),
(buffer_address, buffer_account),
(
sysvar::rent::id(),
create_account_for_test(&Rent::default()),
),
(
sysvar::clock::id(),
create_account_for_test(&Clock::default()),
),
(
system_program::id(),
AccountSharedData::new(0, 0, &system_program::id()),
),
(authority_address, authority_account),
],
vec![
AccountMeta {
pubkey: recipient_address,
is_signer: true,
is_writable: true,
},
AccountMeta {
pubkey: programdata_address,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: program_address,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: buffer_address,
is_signer: false,
is_writable: false,
},
AccountMeta {
pubkey: sysvar::rent::id(),
is_signer: false,
is_writable: false,
},
AccountMeta {
pubkey: sysvar::clock::id(),
is_signer: false,
is_writable: false,
},
AccountMeta {
pubkey: system_program::id(),
is_signer: false,
is_writable: false,
},
AccountMeta {
pubkey: authority_address,
is_signer: false,
is_writable: false,
},
],
Err(InstructionError::AccountAlreadyInitialized),
);
} }
/// fuzzing utility function /// fuzzing utility function

View File

@ -163,6 +163,10 @@ fn load_upgradeable_sbf_program(
authority_keypair, authority_keypair,
elf, elf,
); );
bank_client.set_sysvar_for_tests(&clock::Clock {
slot: 1,
..clock::Clock::default()
});
} }
#[cfg(feature = "sbf_rust")] #[cfg(feature = "sbf_rust")]
@ -2002,6 +2006,10 @@ fn test_program_sbf_upgrade() {
&authority_keypair, &authority_keypair,
"solana_sbf_rust_upgraded", "solana_sbf_rust_upgraded",
); );
bank_client.set_sysvar_for_tests(&clock::Clock {
slot: 2,
..clock::Clock::default()
});
// Call upgraded program // Call upgraded program
instruction.data[0] += 1; instruction.data[0] += 1;
@ -2179,6 +2187,10 @@ fn test_program_sbf_invoke_upgradeable_via_cpi() {
&authority_keypair, &authority_keypair,
"solana_sbf_rust_upgraded", "solana_sbf_rust_upgraded",
); );
bank_client.set_sysvar_for_tests(&clock::Clock {
slot: 2,
..clock::Clock::default()
});
// Call the upgraded program // Call the upgraded program
instruction.data[0] += 1; instruction.data[0] += 1;

View File

@ -14,6 +14,7 @@ use {
signature::{Keypair, Signature, Signer}, signature::{Keypair, Signature, Signer},
signers::Signers, signers::Signers,
system_instruction, system_instruction,
sysvar::{Sysvar, SysvarId},
transaction::{self, Transaction, VersionedTransaction}, transaction::{self, Transaction, VersionedTransaction},
transport::{Result, TransportError}, transport::{Result, TransportError},
}, },
@ -324,6 +325,10 @@ impl BankClient {
pub fn new(bank: Bank) -> Self { pub fn new(bank: Bank) -> Self {
Self::new_shared(&Arc::new(bank)) Self::new_shared(&Arc::new(bank))
} }
pub fn set_sysvar_for_tests<T: Sysvar + SysvarId>(&self, sysvar: &T) {
self.bank.set_sysvar_for_tests(sysvar);
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -52,6 +52,11 @@ pub enum UpgradeableLoaderState {
}, },
} }
impl UpgradeableLoaderState { impl UpgradeableLoaderState {
/// Size of a serialized program account.
pub const fn size_of_uninitialized() -> usize {
4 // see test_state_size_of_uninitialized
}
/// Size of a buffer account's serialized metadata. /// Size of a buffer account's serialized metadata.
pub const fn size_of_buffer_metadata() -> usize { pub const fn size_of_buffer_metadata() -> usize {
37 // see test_state_size_of_buffer_metadata 37 // see test_state_size_of_buffer_metadata
@ -374,6 +379,13 @@ pub fn extend_program(
mod tests { mod tests {
use {super::*, bincode::serialized_size}; use {super::*, bincode::serialized_size};
#[test]
fn test_state_size_of_uninitialized() {
let buffer_state = UpgradeableLoaderState::Uninitialized;
let size = serialized_size(&buffer_state).unwrap();
assert_eq!(UpgradeableLoaderState::size_of_uninitialized() as u64, size);
}
#[test] #[test]
fn test_state_size_of_buffer_metadata() { fn test_state_size_of_buffer_metadata() {
let buffer_state = UpgradeableLoaderState::Buffer { let buffer_state = UpgradeableLoaderState::Buffer {

View File

@ -550,6 +550,10 @@ pub mod enable_alt_bn128_syscall {
solana_sdk::declare_id!("A16q37opZdQMCbe5qJ6xpBB9usykfv8jZaMkxvZQi4GJ"); solana_sdk::declare_id!("A16q37opZdQMCbe5qJ6xpBB9usykfv8jZaMkxvZQi4GJ");
} }
pub mod enable_program_redeployment_cooldown {
solana_sdk::declare_id!("J4HFT8usBxpcF63y46t1upYobJgChmKyZPm5uTBRg25Z");
}
lazy_static! { lazy_static! {
/// Map of feature identifiers to user-visible description /// Map of feature identifiers to user-visible description
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [ pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
@ -682,6 +686,7 @@ lazy_static! {
(enable_bpf_loader_set_authority_checked_ix::id(), "enable bpf upgradeable loader SetAuthorityChecked instruction #28424"), (enable_bpf_loader_set_authority_checked_ix::id(), "enable bpf upgradeable loader SetAuthorityChecked instruction #28424"),
(cap_transaction_accounts_data_size::id(), "cap transaction accounts data size up to its compute unit limits #27839"), (cap_transaction_accounts_data_size::id(), "cap transaction accounts data size up to its compute unit limits #27839"),
(enable_alt_bn128_syscall::id(), "add alt_bn128 syscalls #27961"), (enable_alt_bn128_syscall::id(), "add alt_bn128 syscalls #27961"),
(enable_program_redeployment_cooldown::id(), "enable program redeployment cooldown #29135"),
/*************** ADD NEW FEATURES HERE ***************/ /*************** ADD NEW FEATURES HERE ***************/
] ]
.iter() .iter()