Resized accounts must be rent exempt

This commit is contained in:
Jack May 2022-02-24 17:49:33 -08:00
parent 82cb61dc36
commit 97d40ba3da
11 changed files with 636 additions and 122 deletions

1
Cargo.lock generated
View File

@ -5571,7 +5571,6 @@ dependencies = [
"dashmap", "dashmap",
"dir-diff", "dir-diff",
"ed25519-dalek", "ed25519-dalek",
"enum-iterator",
"flate2", "flate2",
"fnv", "fnv",
"index_list", "index_list",

View File

@ -3433,7 +3433,6 @@ dependencies = [
"crossbeam-channel", "crossbeam-channel",
"dashmap", "dashmap",
"dir-diff", "dir-diff",
"enum-iterator",
"flate2", "flate2",
"fnv", "fnv",
"index_list", "index_list",

View File

@ -190,7 +190,7 @@ fn process_instruction(
&system_instruction::create_account( &system_instruction::create_account(
accounts[0].key, accounts[0].key,
accounts[1].key, accounts[1].key,
1, 3000000, // large enough for rent exemption
pre_len as u64, pre_len as u64,
program_id, program_id,
), ),

View File

@ -54,6 +54,7 @@ use {
loader_instruction, loader_instruction,
message::{v0::LoadedAddresses, Message, SanitizedMessage}, message::{v0::LoadedAddresses, Message, SanitizedMessage},
pubkey::Pubkey, pubkey::Pubkey,
rent::Rent,
signature::{keypair_from_seed, Keypair, Signer}, signature::{keypair_from_seed, Keypair, Signer},
system_instruction::{self, MAX_PERMITTED_DATA_LENGTH}, system_instruction::{self, MAX_PERMITTED_DATA_LENGTH},
system_program, system_program,
@ -2641,11 +2642,13 @@ fn test_program_bpf_ro_account_modify() {
fn test_program_bpf_realloc() { fn test_program_bpf_realloc() {
solana_logger::setup(); solana_logger::setup();
const START_BALANCE: u64 = 100_000_000_000;
let GenesisConfigInfo { let GenesisConfigInfo {
genesis_config, genesis_config,
mint_keypair, mint_keypair,
.. ..
} = create_genesis_config(50); } = create_genesis_config(1_000_000_000_000);
let mint_pubkey = mint_keypair.pubkey(); let mint_pubkey = mint_keypair.pubkey();
let signer = &[&mint_keypair]; let signer = &[&mint_keypair];
@ -2665,7 +2668,7 @@ fn test_program_bpf_realloc() {
let mut bump = 0; let mut bump = 0;
let keypair = Keypair::new(); let keypair = Keypair::new();
let pubkey = keypair.pubkey(); let pubkey = keypair.pubkey();
let account = AccountSharedData::new(42, 5, &program_id); let account = AccountSharedData::new(START_BALANCE, 5, &program_id);
bank.store_account(&pubkey, &account); bank.store_account(&pubkey, &account);
// Realloc RO account // Realloc RO account
@ -2897,11 +2900,14 @@ fn test_program_bpf_realloc() {
fn test_program_bpf_realloc_invoke() { fn test_program_bpf_realloc_invoke() {
solana_logger::setup(); solana_logger::setup();
const START_BALANCE: u64 = 100_000_000_000;
let GenesisConfigInfo { let GenesisConfigInfo {
genesis_config, mut genesis_config,
mint_keypair, mint_keypair,
.. ..
} = create_genesis_config(50); } = create_genesis_config(1_000_000_000_000);
genesis_config.rent = Rent::default();
let mint_pubkey = mint_keypair.pubkey(); let mint_pubkey = mint_keypair.pubkey();
let signer = &[&mint_keypair]; let signer = &[&mint_keypair];
@ -2928,7 +2934,7 @@ fn test_program_bpf_realloc_invoke() {
let mut bump = 0; let mut bump = 0;
let keypair = Keypair::new(); let keypair = Keypair::new();
let pubkey = keypair.pubkey().clone(); let pubkey = keypair.pubkey().clone();
let account = AccountSharedData::new(42, 5, &realloc_program_id); let account = AccountSharedData::new(START_BALANCE, 5, &realloc_program_id);
bank.store_account(&pubkey, &account); bank.store_account(&pubkey, &account);
let invoke_keypair = Keypair::new(); let invoke_keypair = Keypair::new();
let invoke_pubkey = invoke_keypair.pubkey().clone(); let invoke_pubkey = invoke_keypair.pubkey().clone();
@ -2954,6 +2960,8 @@ fn test_program_bpf_realloc_invoke() {
.unwrap(), .unwrap(),
TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified) TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified)
); );
let account = bank.get_account(&pubkey).unwrap();
assert_eq!(account.lamports(), START_BALANCE);
// Realloc account to 0 // Realloc account to 0
bank_client bank_client
@ -2965,6 +2973,8 @@ fn test_program_bpf_realloc_invoke() {
), ),
) )
.unwrap(); .unwrap();
let account = bank.get_account(&pubkey).unwrap();
assert_eq!(account.lamports(), START_BALANCE);
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(0, data.len()); assert_eq!(0, data.len());
@ -3012,54 +3022,7 @@ fn test_program_bpf_realloc_invoke() {
TransactionError::InstructionError(0, InstructionError::InvalidRealloc) TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
); );
// Realloc to max length in max increase increments // Realloc account to 0
for i in 0..MAX_PERMITTED_DATA_LENGTH as usize / MAX_PERMITTED_DATA_INCREASE {
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_EXTEND_MAX, 1, i as u8, (i / 255) as u8],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!((i + 1) * MAX_PERMITTED_DATA_INCREASE, data.len());
}
for i in 0..data.len() {
assert_eq!(data[i], 1);
}
// and one more time should fail
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_EXTEND_MAX, 2, 1, 1],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// Realloc to 0
bank_client bank_client
.send_and_confirm_message( .send_and_confirm_message(
signer, signer,
@ -3069,6 +3032,8 @@ fn test_program_bpf_realloc_invoke() {
), ),
) )
.unwrap(); .unwrap();
let account = bank.get_account(&pubkey).unwrap();
assert_eq!(account.lamports(), START_BALANCE);
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap(); let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(0, data.len()); assert_eq!(0, data.len());
@ -3169,7 +3134,7 @@ fn test_program_bpf_realloc_invoke() {
assert_eq!(0, data.len()); assert_eq!(0, data.len());
// Realloc to 100 and check via CPI // Realloc to 100 and check via CPI
let invoke_account = AccountSharedData::new(42, 5, &realloc_invoke_program_id); let invoke_account = AccountSharedData::new(START_BALANCE, 5, &realloc_invoke_program_id);
bank.store_account(&invoke_pubkey, &invoke_account); bank.store_account(&invoke_pubkey, &invoke_account);
bank_client bank_client
.send_and_confirm_message( .send_and_confirm_message(
@ -3199,42 +3164,6 @@ fn test_program_bpf_realloc_invoke() {
assert_eq!(data[i], 2); assert_eq!(data[i], 2);
} }
// Realloc rescursively and fill data
let invoke_keypair = Keypair::new();
let invoke_pubkey = invoke_keypair.pubkey().clone();
let invoke_account = AccountSharedData::new(42, 0, &realloc_invoke_program_id);
bank.store_account(&invoke_pubkey, &invoke_account);
let mut instruction_data = vec![];
instruction_data.extend_from_slice(&[INVOKE_REALLOC_RECURSIVE, 1]);
instruction_data.extend_from_slice(&100_usize.to_le_bytes());
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&instruction_data,
vec![
AccountMeta::new(invoke_pubkey, false),
AccountMeta::new_readonly(realloc_invoke_program_id, false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client
.get_account_data(&invoke_pubkey)
.unwrap()
.unwrap();
assert_eq!(200, data.len());
for i in 0..100 {
assert_eq!(data[i], 1);
}
for i in 100..200 {
assert_eq!(data[i], 2);
}
// Create account, realloc, check // Create account, realloc, check
let new_keypair = Keypair::new(); let new_keypair = Keypair::new();
let new_pubkey = new_keypair.pubkey().clone(); let new_pubkey = new_keypair.pubkey().clone();
@ -3267,7 +3196,7 @@ fn test_program_bpf_realloc_invoke() {
// Invoke, dealloc, and assign // Invoke, dealloc, and assign
let pre_len = 100; let pre_len = 100;
let new_len = pre_len * 2; let new_len = pre_len * 2;
let mut invoke_account = AccountSharedData::new(42, pre_len, &realloc_program_id); let mut invoke_account = AccountSharedData::new(START_BALANCE, pre_len, &realloc_program_id);
invoke_account.set_data_from_slice(&vec![1; pre_len]); invoke_account.set_data_from_slice(&vec![1; pre_len]);
bank.store_account(&invoke_pubkey, &invoke_account); bank.store_account(&invoke_pubkey, &invoke_account);
let mut instruction_data = vec![]; let mut instruction_data = vec![];
@ -3347,6 +3276,102 @@ fn test_program_bpf_realloc_invoke() {
.unwrap(), .unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc) TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
); );
// Realloc to 0
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&realloc_program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(0, data.len());
// Realloc to max length in max increase increments
for i in 0..MAX_PERMITTED_DATA_LENGTH as usize / MAX_PERMITTED_DATA_INCREASE {
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_EXTEND_MAX, 1, i as u8, (i / 255) as u8],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!((i + 1) * MAX_PERMITTED_DATA_INCREASE, data.len());
}
for i in 0..data.len() {
assert_eq!(data[i], 1);
}
// and one more time should fail
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_EXTEND_MAX, 2, 1, 1],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// Realloc rescursively and fill data
let invoke_keypair = Keypair::new();
let invoke_pubkey = invoke_keypair.pubkey().clone();
let invoke_account = AccountSharedData::new(START_BALANCE, 0, &realloc_invoke_program_id);
bank.store_account(&invoke_pubkey, &invoke_account);
let mut instruction_data = vec![];
instruction_data.extend_from_slice(&[INVOKE_REALLOC_RECURSIVE, 1]);
instruction_data.extend_from_slice(&100_usize.to_le_bytes());
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&instruction_data,
vec![
AccountMeta::new(invoke_pubkey, false),
AccountMeta::new_readonly(realloc_invoke_program_id, false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client
.get_account_data(&invoke_pubkey)
.unwrap()
.unwrap();
assert_eq!(200, data.len());
for i in 0..100 {
assert_eq!(data[i], 1);
}
for i in 100..200 {
assert_eq!(data[i], 2);
}
} }
#[test] #[test]

View File

@ -20,7 +20,6 @@ bzip2 = "0.4.3"
dashmap = { version = "4.0.2", features = ["rayon", "raw-api"] } dashmap = { version = "4.0.2", features = ["rayon", "raw-api"] }
crossbeam-channel = "0.5" crossbeam-channel = "0.5"
dir-diff = "0.3.2" dir-diff = "0.3.2"
enum-iterator = "0.7.0"
flate2 = "1.0.22" flate2 = "1.0.22"
fnv = "1.0.7" fnv = "1.0.7"
index_list = "0.2.7" index_list = "0.2.7"

View File

@ -1,5 +1,4 @@
use { use {
enum_iterator::IntoEnumIterator,
log::*, log::*,
solana_sdk::{ solana_sdk::{
account::{AccountSharedData, ReadableAccount}, account::{AccountSharedData, ReadableAccount},
@ -10,11 +9,15 @@ use {
}, },
}; };
#[derive(Debug, PartialEq, IntoEnumIterator)] #[derive(Debug, PartialEq)]
pub(crate) enum RentState { pub(crate) enum RentState {
Uninitialized, // account.lamports == 0 /// account.lamports == 0
RentPaying, // 0 < account.lamports < rent-exempt-minimum Uninitialized,
RentExempt, // account.lamports >= rent-exempt-minimum /// 0 < account.lamports < rent-exempt-minimum
/// Parameter is the size of the account data
RentPaying(usize),
/// account.lamports >= rent-exempt-minimum
RentExempt,
} }
impl RentState { impl RentState {
@ -22,27 +25,42 @@ impl RentState {
if account.lamports() == 0 { if account.lamports() == 0 {
Self::Uninitialized Self::Uninitialized
} else if !rent.is_exempt(account.lamports(), account.data().len()) { } else if !rent.is_exempt(account.lamports(), account.data().len()) {
Self::RentPaying Self::RentPaying(account.data().len())
} else { } else {
Self::RentExempt Self::RentExempt
} }
} }
pub(crate) fn transition_allowed_from(&self, pre_rent_state: &RentState) -> bool { pub(crate) fn transition_allowed_from(
// Only a legacy RentPaying account may end in the RentPaying state after message processing &self,
!(self == &Self::RentPaying && pre_rent_state != &Self::RentPaying) pre_rent_state: &RentState,
do_support_realloc: bool,
) -> bool {
if let Self::RentPaying(post_data_size) = self {
if let Self::RentPaying(pre_data_size) = pre_rent_state {
if do_support_realloc {
post_data_size == pre_data_size // Cannot be RentPaying if resized
} else {
true // RentPaying can continue to be RentPaying
}
} else {
false // Only RentPaying can continue to be RentPaying
}
} else {
true // Post not-RentPaying always ok
}
} }
} }
pub(crate) fn submit_rent_state_metrics(pre_rent_state: &RentState, post_rent_state: &RentState) { pub(crate) fn submit_rent_state_metrics(pre_rent_state: &RentState, post_rent_state: &RentState) {
match (pre_rent_state, post_rent_state) { match (pre_rent_state, post_rent_state) {
(&RentState::Uninitialized, &RentState::RentPaying) => { (&RentState::Uninitialized, &RentState::RentPaying(_)) => {
inc_new_counter_info!("rent_paying_err-new_account", 1); inc_new_counter_info!("rent_paying_err-new_account", 1);
} }
(&RentState::RentPaying, &RentState::RentPaying) => { (&RentState::RentPaying(_), &RentState::RentPaying(_)) => {
inc_new_counter_info!("rent_paying_ok-legacy", 1); inc_new_counter_info!("rent_paying_ok-legacy", 1);
} }
(_, &RentState::RentPaying) => { (_, &RentState::RentPaying(_)) => {
inc_new_counter_info!("rent_paying_err-other", 1); inc_new_counter_info!("rent_paying_err-other", 1);
} }
_ => {} _ => {}
@ -54,6 +72,7 @@ pub(crate) fn check_rent_state(
post_rent_state: Option<&RentState>, post_rent_state: Option<&RentState>,
transaction_context: &TransactionContext, transaction_context: &TransactionContext,
index: usize, index: usize,
do_support_realloc: bool,
) -> Result<()> { ) -> Result<()> {
if let Some((pre_rent_state, post_rent_state)) = pre_rent_state.zip(post_rent_state) { if let Some((pre_rent_state, post_rent_state)) = pre_rent_state.zip(post_rent_state) {
let expect_msg = "account must exist at TransactionContext index if rent-states are Some"; let expect_msg = "account must exist at TransactionContext index if rent-states are Some";
@ -67,6 +86,7 @@ pub(crate) fn check_rent_state(
.get_account_at_index(index) .get_account_at_index(index)
.expect(expect_msg) .expect(expect_msg)
.borrow(), .borrow(),
do_support_realloc,
)?; )?;
} }
Ok(()) Ok(())
@ -77,10 +97,11 @@ pub(crate) fn check_rent_state_with_account(
post_rent_state: &RentState, post_rent_state: &RentState,
address: &Pubkey, address: &Pubkey,
account_state: &AccountSharedData, account_state: &AccountSharedData,
do_support_realloc: bool,
) -> Result<()> { ) -> Result<()> {
submit_rent_state_metrics(pre_rent_state, post_rent_state); submit_rent_state_metrics(pre_rent_state, post_rent_state);
if !solana_sdk::incinerator::check_id(address) if !solana_sdk::incinerator::check_id(address)
&& !post_rent_state.transition_allowed_from(pre_rent_state) && !post_rent_state.transition_allowed_from(pre_rent_state, do_support_realloc)
{ {
debug!( debug!(
"Account {} not rent exempt, state {:?}", "Account {} not rent exempt, state {:?}",
@ -133,7 +154,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
RentState::from_account(&rent_paying_account, &rent), RentState::from_account(&rent_paying_account, &rent),
RentState::RentPaying RentState::RentPaying(account_data_size)
); );
assert_eq!( assert_eq!(
RentState::from_account(&rent_exempt_account, &rent), RentState::from_account(&rent_exempt_account, &rent),
@ -143,16 +164,21 @@ mod tests {
#[test] #[test]
fn test_transition_allowed_from() { fn test_transition_allowed_from() {
for post_rent_state in RentState::into_enum_iter() { let post_rent_state = RentState::Uninitialized;
for pre_rent_state in RentState::into_enum_iter() { assert!(post_rent_state.transition_allowed_from(&RentState::Uninitialized, true));
if post_rent_state == RentState::RentPaying assert!(post_rent_state.transition_allowed_from(&RentState::RentExempt, true));
&& pre_rent_state != RentState::RentPaying assert!(post_rent_state.transition_allowed_from(&RentState::RentPaying(0), true));
{
assert!(!post_rent_state.transition_allowed_from(&pre_rent_state)); let post_rent_state = RentState::RentExempt;
} else { assert!(post_rent_state.transition_allowed_from(&RentState::Uninitialized, true));
assert!(post_rent_state.transition_allowed_from(&pre_rent_state)); assert!(post_rent_state.transition_allowed_from(&RentState::RentExempt, true));
} assert!(post_rent_state.transition_allowed_from(&RentState::RentPaying(0), true));
}
} let post_rent_state = RentState::RentPaying(2);
assert!(!post_rent_state.transition_allowed_from(&RentState::Uninitialized, true));
assert!(!post_rent_state.transition_allowed_from(&RentState::RentExempt, true));
assert!(!post_rent_state.transition_allowed_from(&RentState::RentPaying(3), true));
assert!(!post_rent_state.transition_allowed_from(&RentState::RentPaying(1), true));
assert!(post_rent_state.transition_allowed_from(&RentState::RentPaying(2), true));
} }
} }

View File

@ -377,6 +377,7 @@ impl Accounts {
&payer_post_rent_state, &payer_post_rent_state,
payer_address, payer_address,
payer_account, payer_account,
feature_set.is_active(&feature_set::do_support_realloc::id()),
); );
// Feature gate only wraps the actual error return so that the metrics and debug // Feature gate only wraps the actual error return so that the metrics and debug
// logging generated by `check_rent_state_with_account()` can be examined before // logging generated by `check_rent_state_with_account()` can be examined before

View File

@ -16285,12 +16285,22 @@ pub(crate) mod tests {
let rent_paying_account = Keypair::new(); let rent_paying_account = Keypair::new();
genesis_config.accounts.insert( genesis_config.accounts.insert(
rent_paying_account.pubkey(), rent_paying_account.pubkey(),
Account::new(rent_exempt_minimum - 1, account_data_size, &mock_program_id), Account::new_rent_epoch(
rent_exempt_minimum - 1,
account_data_size,
&mock_program_id,
INITIAL_RENT_EPOCH + 1,
),
); );
let rent_exempt_account = Keypair::new(); let rent_exempt_account = Keypair::new();
genesis_config.accounts.insert( genesis_config.accounts.insert(
rent_exempt_account.pubkey(), rent_exempt_account.pubkey(),
Account::new(rent_exempt_minimum, account_data_size, &mock_program_id), Account::new_rent_epoch(
rent_exempt_minimum,
account_data_size,
&mock_program_id,
INITIAL_RENT_EPOCH + 1,
),
); );
// Activate features, including require_rent_exempt_accounts // Activate features, including require_rent_exempt_accounts
activate_all_features(&mut genesis_config); activate_all_features(&mut genesis_config);
@ -16427,6 +16437,97 @@ pub(crate) mod tests {
assert!(check_account_is_rent_exempt(&rent_exempt_account.pubkey())); assert!(check_account_is_rent_exempt(&rent_exempt_account.pubkey()));
} }
#[test]
fn test_drained_created_account() {
let GenesisConfigInfo {
mut genesis_config,
mint_keypair,
..
} = create_genesis_config_with_leader(sol_to_lamports(100.), &Pubkey::new_unique(), 42);
genesis_config.rent = Rent::default();
activate_all_features(&mut genesis_config);
let mock_program_id = Pubkey::new_unique();
// small enough to not pay rent, thus bypassing the data clearing rent
// mechanism
let data_size_no_rent = 100;
// large enough to pay rent, will have data cleared
let data_size_rent = 10000;
let lamports_to_transfer = 100;
// Create legacy accounts of various kinds
let created_keypair = Keypair::new();
let mut bank = Bank::new_for_tests(&genesis_config);
bank.add_builtin(
"mock_program",
&mock_program_id,
mock_transfer_process_instruction,
);
let recent_blockhash = bank.last_blockhash();
// Create and drain a small data size account
let create_instruction = system_instruction::create_account(
&mint_keypair.pubkey(),
&created_keypair.pubkey(),
lamports_to_transfer,
data_size_no_rent,
&mock_program_id,
);
let account_metas = vec![
AccountMeta::new(mint_keypair.pubkey(), true),
AccountMeta::new(created_keypair.pubkey(), true),
AccountMeta::new(mint_keypair.pubkey(), false),
];
let transfer_from_instruction = Instruction::new_with_bincode(
mock_program_id,
&MockTransferInstruction::Transfer(lamports_to_transfer),
account_metas,
);
let tx = Transaction::new_signed_with_payer(
&[create_instruction, transfer_from_instruction],
Some(&mint_keypair.pubkey()),
&[&mint_keypair, &created_keypair],
recent_blockhash,
);
let result = bank.process_transaction(&tx);
assert!(result.is_ok());
// account data is not stored because of zero balance even though its
// data wasn't cleared
assert!(bank.get_account(&created_keypair.pubkey()).is_none());
// Create and drain a large data size account
let create_instruction = system_instruction::create_account(
&mint_keypair.pubkey(),
&created_keypair.pubkey(),
lamports_to_transfer,
data_size_rent,
&mock_program_id,
);
let account_metas = vec![
AccountMeta::new(mint_keypair.pubkey(), true),
AccountMeta::new(created_keypair.pubkey(), true),
AccountMeta::new(mint_keypair.pubkey(), false),
];
let transfer_from_instruction = Instruction::new_with_bincode(
mock_program_id,
&MockTransferInstruction::Transfer(lamports_to_transfer),
account_metas,
);
let tx = Transaction::new_signed_with_payer(
&[create_instruction, transfer_from_instruction],
Some(&mint_keypair.pubkey()),
&[&mint_keypair, &created_keypair],
recent_blockhash,
);
let result = bank.process_transaction(&tx);
assert!(result.is_ok());
// account data is not stored because of zero balance
assert!(bank.get_account(&created_keypair.pubkey()).is_none());
}
#[test] #[test]
fn test_rent_state_changes_sysvars() { fn test_rent_state_changes_sysvars() {
let GenesisConfigInfo { let GenesisConfigInfo {
@ -16879,4 +16980,329 @@ pub(crate) mod tests {
] ]
); );
} }
#[derive(Serialize, Deserialize)]
enum MockReallocInstruction {
Realloc(usize, u64, Pubkey),
}
fn mock_realloc_process_instruction(
_first_instruction_account: usize,
data: &[u8],
invoke_context: &mut InvokeContext,
) -> result::Result<(), InstructionError> {
let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
if let Ok(instruction) = bincode::deserialize(data) {
match instruction {
MockReallocInstruction::Realloc(new_size, new_balance, _) => {
// Set data length
instruction_context
.try_borrow_instruction_account(transaction_context, 1)?
.set_data_length(new_size);
// set balance
let current_balance = instruction_context
.try_borrow_instruction_account(transaction_context, 1)?
.get_lamports();
let diff_balance = (new_balance as i64).saturating_sub(current_balance as i64);
let amount = diff_balance.abs() as u64;
if diff_balance.is_positive() {
instruction_context
.try_borrow_instruction_account(transaction_context, 0)?
.checked_sub_lamports(amount)?;
instruction_context
.try_borrow_instruction_account(transaction_context, 1)?
.set_lamports(new_balance);
} else {
instruction_context
.try_borrow_instruction_account(transaction_context, 0)?
.checked_add_lamports(amount)?;
instruction_context
.try_borrow_instruction_account(transaction_context, 1)?
.set_lamports(new_balance);
}
Ok(())
}
}
} else {
Err(InstructionError::InvalidInstructionData)
}
}
fn create_mock_realloc_tx(
payer: &Keypair,
funder: &Keypair,
reallocd: &Pubkey,
new_size: usize,
new_balance: u64,
mock_program_id: Pubkey,
recent_blockhash: Hash,
) -> Transaction {
let account_metas = vec![
AccountMeta::new(funder.pubkey(), false),
AccountMeta::new(*reallocd, false),
];
let instruction = Instruction::new_with_bincode(
mock_program_id,
&MockReallocInstruction::Realloc(new_size, new_balance, Pubkey::new_unique()),
account_metas,
);
Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[payer],
recent_blockhash,
)
}
#[test]
fn test_resize_and_rent() {
let GenesisConfigInfo {
mut genesis_config,
mint_keypair,
..
} = create_genesis_config_with_leader(1_000_000_000, &Pubkey::new_unique(), 42);
genesis_config.rent = Rent::default();
activate_all_features(&mut genesis_config);
let mut bank = Bank::new_for_tests(&genesis_config);
let mock_program_id = Pubkey::new_unique();
bank.add_builtin(
"mock_realloc_program",
&mock_program_id,
mock_realloc_process_instruction,
);
let recent_blockhash = bank.last_blockhash();
let account_data_size_small = 1024;
let rent_exempt_minimum_small =
genesis_config.rent.minimum_balance(account_data_size_small);
let account_data_size_large = 2048;
let rent_exempt_minimum_large =
genesis_config.rent.minimum_balance(account_data_size_large);
let funding_keypair = Keypair::new();
bank.store_account(
&funding_keypair.pubkey(),
&AccountSharedData::new(1_000_000_000, 0, &mock_program_id),
);
let rent_paying_pubkey = solana_sdk::pubkey::new_rand();
let mut rent_paying_account = AccountSharedData::new(
rent_exempt_minimum_small - 1,
account_data_size_small,
&mock_program_id,
);
rent_paying_account.set_rent_epoch(1);
// restore program-owned account
bank.store_account(&rent_paying_pubkey, &rent_paying_account);
// rent paying, realloc larger, fail because not rent exempt
let tx = create_mock_realloc_tx(
&mint_keypair,
&funding_keypair,
&rent_paying_pubkey,
account_data_size_large,
rent_exempt_minimum_small - 1,
mock_program_id,
recent_blockhash,
);
assert_eq!(
bank.process_transaction(&tx).unwrap_err(),
TransactionError::InvalidRentPayingAccount,
);
assert_eq!(
rent_exempt_minimum_small - 1,
bank.get_account(&rent_paying_pubkey).unwrap().lamports()
);
// rent paying, realloc larger and rent exempt
let tx = create_mock_realloc_tx(
&mint_keypair,
&funding_keypair,
&rent_paying_pubkey,
account_data_size_large,
rent_exempt_minimum_large,
mock_program_id,
recent_blockhash,
);
let result = bank.process_transaction(&tx);
assert!(result.is_ok());
assert_eq!(
rent_exempt_minimum_large,
bank.get_account(&rent_paying_pubkey).unwrap().lamports()
);
// rent exempt, realloc small, fail because not rent exempt
let tx = create_mock_realloc_tx(
&mint_keypair,
&funding_keypair,
&rent_paying_pubkey,
account_data_size_small,
rent_exempt_minimum_small - 1,
mock_program_id,
recent_blockhash,
);
assert_eq!(
bank.process_transaction(&tx).unwrap_err(),
TransactionError::InvalidRentPayingAccount,
);
assert_eq!(
rent_exempt_minimum_large,
bank.get_account(&rent_paying_pubkey).unwrap().lamports()
);
// rent exempt, realloc smaller and rent exempt
let tx = create_mock_realloc_tx(
&mint_keypair,
&funding_keypair,
&rent_paying_pubkey,
account_data_size_small,
rent_exempt_minimum_small,
mock_program_id,
recent_blockhash,
);
let result = bank.process_transaction(&tx);
assert!(result.is_ok());
assert_eq!(
rent_exempt_minimum_small,
bank.get_account(&rent_paying_pubkey).unwrap().lamports()
);
// rent exempt, realloc large, fail because not rent exempt
let tx = create_mock_realloc_tx(
&mint_keypair,
&funding_keypair,
&rent_paying_pubkey,
account_data_size_large,
rent_exempt_minimum_large - 1,
mock_program_id,
recent_blockhash,
);
assert_eq!(
bank.process_transaction(&tx).unwrap_err(),
TransactionError::InvalidRentPayingAccount,
);
assert_eq!(
rent_exempt_minimum_small,
bank.get_account(&rent_paying_pubkey).unwrap().lamports()
);
// rent exempt, realloc large and rent exempt
let tx = create_mock_realloc_tx(
&mint_keypair,
&funding_keypair,
&rent_paying_pubkey,
account_data_size_large,
rent_exempt_minimum_large,
mock_program_id,
recent_blockhash,
);
let result = bank.process_transaction(&tx);
assert!(result.is_ok());
assert_eq!(
rent_exempt_minimum_large,
bank.get_account(&rent_paying_pubkey).unwrap().lamports()
);
let created_keypair = Keypair::new();
// create account, not rent exempt
let tx = system_transaction::create_account(
&mint_keypair,
&created_keypair,
recent_blockhash,
rent_exempt_minimum_small - 1,
account_data_size_small as u64,
&system_program::id(),
);
assert_eq!(
bank.process_transaction(&tx).unwrap_err(),
TransactionError::InvalidRentPayingAccount,
);
// create account, rent exempt
let tx = system_transaction::create_account(
&mint_keypair,
&created_keypair,
recent_blockhash,
rent_exempt_minimum_small,
account_data_size_small as u64,
&system_program::id(),
);
let result = bank.process_transaction(&tx);
assert!(result.is_ok());
assert_eq!(
rent_exempt_minimum_small,
bank.get_account(&created_keypair.pubkey())
.unwrap()
.lamports()
);
let created_keypair = Keypair::new();
// create account, no data
let tx = system_transaction::create_account(
&mint_keypair,
&created_keypair,
recent_blockhash,
rent_exempt_minimum_small - 1,
0,
&system_program::id(),
);
let result = bank.process_transaction(&tx);
assert!(result.is_ok());
assert_eq!(
rent_exempt_minimum_small - 1,
bank.get_account(&created_keypair.pubkey())
.unwrap()
.lamports()
);
// alloc but not rent exempt
let tx = system_transaction::allocate(
&mint_keypair,
&created_keypair,
recent_blockhash,
(account_data_size_small + 1) as u64,
);
assert_eq!(
bank.process_transaction(&tx).unwrap_err(),
TransactionError::InvalidRentPayingAccount,
);
// bring balance of account up to rent exemption
let tx = system_transaction::transfer(
&mint_keypair,
&created_keypair.pubkey(),
1,
recent_blockhash,
);
let result = bank.process_transaction(&tx);
assert!(result.is_ok());
assert_eq!(
rent_exempt_minimum_small,
bank.get_account(&created_keypair.pubkey())
.unwrap()
.lamports()
);
// allocate as rent exempt
let tx = system_transaction::allocate(
&mint_keypair,
&created_keypair,
recent_blockhash,
account_data_size_small as u64,
);
let result = bank.process_transaction(&tx);
assert!(result.is_ok());
assert_eq!(
rent_exempt_minimum_small,
bank.get_account(&created_keypair.pubkey())
.unwrap()
.lamports()
);
}
} }

View File

@ -58,6 +58,9 @@ impl Bank {
let require_rent_exempt_accounts = self let require_rent_exempt_accounts = self
.feature_set .feature_set
.is_active(&feature_set::require_rent_exempt_accounts::id()); .is_active(&feature_set::require_rent_exempt_accounts::id());
let do_support_realloc = self
.feature_set
.is_active(&feature_set::do_support_realloc::id());
for (i, (pre_state_info, post_state_info)) in for (i, (pre_state_info, post_state_info)) in
pre_state_infos.iter().zip(post_state_infos).enumerate() pre_state_infos.iter().zip(post_state_infos).enumerate()
{ {
@ -66,6 +69,7 @@ impl Bank {
post_state_info.rent_state.as_ref(), post_state_info.rent_state.as_ref(),
transaction_context, transaction_context,
i, i,
do_support_realloc,
) { ) {
// Feature gate only wraps the actual error return so that the metrics and debug // Feature gate only wraps the actual error return so that the metrics and debug
// logging generated by `check_rent_state()` can be examined before feature // logging generated by `check_rent_state()` can be examined before feature

View File

@ -326,6 +326,21 @@ fn shared_new<T: WritableAccount>(lamports: u64, space: usize, owner: &Pubkey) -
) )
} }
fn shared_new_rent_epoch<T: WritableAccount>(
lamports: u64,
space: usize,
owner: &Pubkey,
rent_epoch: Epoch,
) -> T {
T::create(
lamports,
vec![0u8; space],
*owner,
bool::default(),
rent_epoch,
)
}
fn shared_new_ref<T: WritableAccount>( fn shared_new_ref<T: WritableAccount>(
lamports: u64, lamports: u64,
space: usize, space: usize,
@ -434,6 +449,9 @@ impl Account {
) -> Result<RefCell<Self>, bincode::Error> { ) -> Result<RefCell<Self>, bincode::Error> {
shared_new_ref_data_with_space(lamports, state, space, owner) shared_new_ref_data_with_space(lamports, state, space, owner)
} }
pub fn new_rent_epoch(lamports: u64, space: usize, owner: &Pubkey, rent_epoch: Epoch) -> Self {
shared_new_rent_epoch(lamports, space, owner, rent_epoch)
}
pub fn deserialize_data<T: serde::de::DeserializeOwned>(&self) -> Result<T, bincode::Error> { pub fn deserialize_data<T: serde::de::DeserializeOwned>(&self) -> Result<T, bincode::Error> {
shared_deserialize_data(self) shared_deserialize_data(self)
} }
@ -490,6 +508,9 @@ impl AccountSharedData {
) -> Result<RefCell<Self>, bincode::Error> { ) -> Result<RefCell<Self>, bincode::Error> {
shared_new_ref_data_with_space(lamports, state, space, owner) shared_new_ref_data_with_space(lamports, state, space, owner)
} }
pub fn new_rent_epoch(lamports: u64, space: usize, owner: &Pubkey, rent_epoch: Epoch) -> Self {
shared_new_rent_epoch(lamports, space, owner, rent_epoch)
}
pub fn deserialize_data<T: serde::de::DeserializeOwned>(&self) -> Result<T, bincode::Error> { pub fn deserialize_data<T: serde::de::DeserializeOwned>(&self) -> Result<T, bincode::Error> {
shared_deserialize_data(self) shared_deserialize_data(self)
} }

View File

@ -27,6 +27,20 @@ pub fn create_account(
Transaction::new(&[from_keypair, to_keypair], message, recent_blockhash) Transaction::new(&[from_keypair, to_keypair], message, recent_blockhash)
} }
/// Create and sign new SystemInstruction::Allocate transaction
pub fn allocate(
payer_keypair: &Keypair,
account_keypair: &Keypair,
recent_blockhash: Hash,
space: u64,
) -> Transaction {
let payer_pubkey = payer_keypair.pubkey();
let account_pubkey = account_keypair.pubkey();
let instruction = system_instruction::allocate(&account_pubkey, space);
let message = Message::new(&[instruction], Some(&payer_pubkey));
Transaction::new(&[payer_keypair, account_keypair], message, recent_blockhash)
}
/// Create and sign new system_instruction::Assign transaction /// Create and sign new system_instruction::Assign transaction
pub fn assign(from_keypair: &Keypair, recent_blockhash: Hash, program_id: &Pubkey) -> Transaction { pub fn assign(from_keypair: &Keypair, recent_blockhash: Hash, program_id: &Pubkey) -> Transaction {
let from_pubkey = from_keypair.pubkey(); let from_pubkey = from_keypair.pubkey();