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:
parent
3e649d2aa0
commit
721496b900
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue