Feature: Early verification of account modifications in `BorrowedAccount` (#25899)

* Adjusts test cases for stricter requirements.

* Removes account reset in deserialization test.

* Removes verify related test cases.

* Replicates account modification verification logic of PreAccount in BorrowedAccount.

* Adds TransactionContext::account_touched_flags.

* Adds account modification verification to the BPF ABIv0 and ABIv1 deserialization, CPI syscall and program-test.

* Replicates the total sum of all lamports verification of PreAccounts in InstructionContext

* Check that the callers instruction balance is maintained during a call / push.

* Replicates PreAccount statistics in TransactionContext.

* Disable verify() and verify_and_update() if the feature enable_early_verification_of_account_modifications is enabled.

* Moves Option<Rent> of enable_early_verification_of_account_modifications into TransactionContext::new().

* Relaxes AccountDataMeter related test cases.

* Don't touch the account if nothing changes.

* Adds two tests to trigger InstructionError::UnbalancedInstruction.

Co-authored-by: Justin Starry <justin@solana.com>
This commit is contained in:
Alexander Meißner 2022-07-15 09:31:34 +02:00 committed by GitHub
parent f13b5c832d
commit 038da82b6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 713 additions and 357 deletions

View File

@ -46,6 +46,7 @@ use {
signature::{keypair_from_seed, read_keypair_file, Keypair, Signature, Signer}, signature::{keypair_from_seed, read_keypair_file, Keypair, Signature, Signer},
system_instruction::{self, SystemError}, system_instruction::{self, SystemError},
system_program, system_program,
sysvar::rent::Rent,
transaction::{Transaction, TransactionError}, transaction::{Transaction, TransactionError},
transaction_context::TransactionContext, transaction_context::TransactionContext,
}, },
@ -2060,7 +2061,7 @@ fn read_and_verify_elf(program_location: &str) -> Result<Vec<u8>, Box<dyn std::e
let mut program_data = Vec::new(); let mut program_data = Vec::new();
file.read_to_end(&mut program_data) file.read_to_end(&mut program_data)
.map_err(|err| format!("Unable to read program file: {}", err))?; .map_err(|err| format!("Unable to read program file: {}", err))?;
let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1); let mut transaction_context = TransactionContext::new(Vec::new(), Some(Rent::default()), 1, 1);
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
// Verify the program // Verify the program

View File

@ -4516,7 +4516,10 @@ pub mod tests {
mint_keypair, mint_keypair,
.. ..
} = create_genesis_config((1_000_000 + NUM_ACCOUNTS + 1) * LAMPORTS_PER_SOL); } = create_genesis_config((1_000_000 + NUM_ACCOUNTS + 1) * LAMPORTS_PER_SOL);
let bank = Bank::new_for_tests(&genesis_config); let mut bank = Bank::new_for_tests(&genesis_config);
bank.deactivate_feature(
&feature_set::enable_early_verification_of_account_modifications::id(),
);
let bank = Arc::new(bank); let bank = Arc::new(bank);
assert!(bank assert!(bank
.feature_set .feature_set
@ -4579,6 +4582,9 @@ pub mod tests {
mock_realloc::process_instruction, mock_realloc::process_instruction,
); );
bank.set_accounts_data_size_initial_for_tests(INITIAL_ACCOUNTS_DATA_SIZE); bank.set_accounts_data_size_initial_for_tests(INITIAL_ACCOUNTS_DATA_SIZE);
bank.deactivate_feature(
&feature_set::enable_early_verification_of_account_modifications::id(),
);
let bank = Arc::new(bank); let bank = Arc::new(bank);
let bank = Arc::new(Bank::new_from_parent(&bank, &Pubkey::new_unique(), 1)); let bank = Arc::new(Bank::new_from_parent(&bank, &Pubkey::new_unique(), 1));
assert!(bank assert!(bank

View File

@ -16,7 +16,8 @@ use {
account::{AccountSharedData, ReadableAccount}, account::{AccountSharedData, ReadableAccount},
bpf_loader_upgradeable::{self, UpgradeableLoaderState}, bpf_loader_upgradeable::{self, UpgradeableLoaderState},
feature_set::{ feature_set::{
cap_accounts_data_len, record_instruction_in_transaction_context_push, FeatureSet, cap_accounts_data_len, enable_early_verification_of_account_modifications,
record_instruction_in_transaction_context_push, FeatureSet,
}, },
hash::Hash, hash::Hash,
instruction::{AccountMeta, Instruction, InstructionError}, instruction::{AccountMeta, Instruction, InstructionError},
@ -344,29 +345,34 @@ impl<'a> InvokeContext<'a> {
{ {
self.current_compute_budget = self.compute_budget; self.current_compute_budget = self.compute_budget;
self.pre_accounts = Vec::with_capacity(instruction_accounts.len()); if !self
.feature_set
for (instruction_account_index, instruction_account) in .is_active(&enable_early_verification_of_account_modifications::id())
instruction_accounts.iter().enumerate()
{ {
if instruction_account_index != instruction_account.index_in_callee { self.pre_accounts = Vec::with_capacity(instruction_accounts.len());
continue; // Skip duplicate account for (instruction_account_index, instruction_account) in
} instruction_accounts.iter().enumerate()
if instruction_account.index_in_transaction
>= self.transaction_context.get_number_of_accounts()
{ {
return Err(InstructionError::MissingAccount); if instruction_account_index != instruction_account.index_in_callee {
continue; // Skip duplicate account
}
if instruction_account.index_in_transaction
>= self.transaction_context.get_number_of_accounts()
{
return Err(InstructionError::MissingAccount);
}
let account = self
.transaction_context
.get_account_at_index(instruction_account.index_in_transaction)?
.borrow()
.clone();
self.pre_accounts.push(PreAccount::new(
self.transaction_context.get_key_of_account_at_index(
instruction_account.index_in_transaction,
)?,
account,
));
} }
let account = self
.transaction_context
.get_account_at_index(instruction_account.index_in_transaction)?
.borrow()
.clone();
self.pre_accounts.push(PreAccount::new(
self.transaction_context
.get_key_of_account_at_index(instruction_account.index_in_transaction)?,
account,
));
} }
} else { } else {
let contains = (0..self let contains = (0..self
@ -831,26 +837,35 @@ impl<'a> InvokeContext<'a> {
.get_instruction_context_stack_height(); .get_instruction_context_stack_height();
let is_top_level_instruction = nesting_level == 0; let is_top_level_instruction = nesting_level == 0;
if !is_top_level_instruction { if !is_top_level_instruction {
// Verify the calling program hasn't misbehaved if !self
let mut verify_caller_time = Measure::start("verify_caller_time"); .feature_set
let verify_caller_result = self.verify_and_update(instruction_accounts, true); .is_active(&enable_early_verification_of_account_modifications::id())
verify_caller_time.stop(); {
saturating_add_assign!( // Verify the calling program hasn't misbehaved
timings let mut verify_caller_time = Measure::start("verify_caller_time");
.execute_accessories let verify_caller_result = self.verify_and_update(instruction_accounts, true);
.process_instructions verify_caller_time.stop();
.verify_caller_us, saturating_add_assign!(
verify_caller_time.as_us() timings
); .execute_accessories
verify_caller_result?; .process_instructions
.verify_caller_us,
verify_caller_time.as_us()
);
verify_caller_result?;
}
if !self if !self
.feature_set .feature_set
.is_active(&record_instruction_in_transaction_context_push::id()) .is_active(&record_instruction_in_transaction_context_push::id())
{ {
let instruction_accounts_lamport_sum = self
.transaction_context
.instruction_accounts_lamport_sum(instruction_accounts)?;
self.transaction_context self.transaction_context
.record_instruction(InstructionContext::new( .record_instruction(InstructionContext::new(
nesting_level, nesting_level,
instruction_accounts_lamport_sum,
program_indices, program_indices,
instruction_accounts, instruction_accounts,
instruction_data, instruction_data,
@ -861,22 +876,29 @@ impl<'a> InvokeContext<'a> {
self.push(instruction_accounts, program_indices, instruction_data)?; self.push(instruction_accounts, program_indices, instruction_data)?;
self.process_executable_chain(compute_units_consumed, timings) self.process_executable_chain(compute_units_consumed, timings)
.and_then(|_| { .and_then(|_| {
// Verify the called program has not misbehaved if self
let mut verify_callee_time = Measure::start("verify_callee_time"); .feature_set
let result = if is_top_level_instruction { .is_active(&enable_early_verification_of_account_modifications::id())
self.verify(instruction_accounts, program_indices) {
Ok(())
} else { } else {
self.verify_and_update(instruction_accounts, false) // Verify the called program has not misbehaved
}; let mut verify_callee_time = Measure::start("verify_callee_time");
verify_callee_time.stop(); let result = if is_top_level_instruction {
saturating_add_assign!( self.verify(instruction_accounts, program_indices)
timings } else {
.execute_accessories self.verify_and_update(instruction_accounts, false)
.process_instructions };
.verify_callee_us, verify_callee_time.stop();
verify_callee_time.as_us() saturating_add_assign!(
); timings
result .execute_accessories
.process_instructions
.verify_callee_us,
verify_callee_time.as_us()
);
result
}
}) })
// MUST pop if and only if `push` succeeded, independent of `result`. // MUST pop if and only if `push` succeeded, independent of `result`.
// Thus, the `.and()` instead of an `.and_then()`. // Thus, the `.and()` instead of an `.and_then()`.
@ -1138,6 +1160,7 @@ pub fn with_mock_invoke_context<R, F: FnMut(&mut InvokeContext) -> R>(
prepare_mock_invoke_context(transaction_accounts, instruction_accounts, &program_indices); prepare_mock_invoke_context(transaction_accounts, instruction_accounts, &program_indices);
let mut transaction_context = TransactionContext::new( let mut transaction_context = TransactionContext::new(
preparation.transaction_accounts, preparation.transaction_accounts,
Some(Rent::default()),
ComputeBudget::default().max_invoke_depth.saturating_add(1), ComputeBudget::default().max_invoke_depth.saturating_add(1),
1, 1,
); );
@ -1168,6 +1191,7 @@ pub fn mock_process_instruction(
.push((*loader_id, processor_account)); .push((*loader_id, processor_account));
let mut transaction_context = TransactionContext::new( let mut transaction_context = TransactionContext::new(
preparation.transaction_accounts, preparation.transaction_accounts,
Some(Rent::default()),
ComputeBudget::default().max_invoke_depth.saturating_add(1), ComputeBudget::default().max_invoke_depth.saturating_add(1),
1, 1,
); );
@ -1184,10 +1208,9 @@ pub fn mock_process_instruction(
&program_indices, &program_indices,
instruction_data, instruction_data,
) )
.and_then(|_| process_instruction(1, &mut invoke_context)) .and_then(|_| process_instruction(1, &mut invoke_context));
.and_then(|_| invoke_context.verify(&preparation.instruction_accounts, &program_indices)); let pop_result = invoke_context.pop();
invoke_context.pop().unwrap(); assert_eq!(result.and(pop_result), expected_result);
assert_eq!(result, expected_result);
let mut transaction_accounts = transaction_context.deconstruct_without_keys().unwrap(); let mut transaction_accounts = transaction_context.deconstruct_without_keys().unwrap();
transaction_accounts.pop(); transaction_accounts.pop();
transaction_accounts transaction_accounts
@ -1199,7 +1222,7 @@ mod tests {
super::*, super::*,
crate::compute_budget, crate::compute_budget,
serde::{Deserialize, Serialize}, serde::{Deserialize, Serialize},
solana_sdk::account::{ReadableAccount, WritableAccount}, solana_sdk::account::WritableAccount,
}; };
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@ -1209,6 +1232,8 @@ mod tests {
ModifyOwned, ModifyOwned,
ModifyNotOwned, ModifyNotOwned,
ModifyReadonly, ModifyReadonly,
UnbalancedPush,
UnbalancedPop,
ConsumeComputeUnits { ConsumeComputeUnits {
compute_units_to_consume: u64, compute_units_to_consume: u64,
desired_result: Result<(), InstructionError>, desired_result: Result<(), InstructionError>,
@ -1256,6 +1281,15 @@ mod tests {
let instruction_context = transaction_context.get_current_instruction_context()?; let instruction_context = transaction_context.get_current_instruction_context()?;
let instruction_data = instruction_context.get_instruction_data(); let instruction_data = instruction_context.get_instruction_data();
let program_id = instruction_context.get_last_program_key(transaction_context)?; let program_id = instruction_context.get_last_program_key(transaction_context)?;
let instruction_accounts = (0..4)
.map(|instruction_account_index| InstructionAccount {
index_in_transaction: instruction_account_index,
index_in_caller: instruction_account_index,
index_in_callee: instruction_account_index,
is_signer: false,
is_writable: false,
})
.collect::<Vec<_>>();
assert_eq!( assert_eq!(
program_id, program_id,
instruction_context instruction_context
@ -1277,16 +1311,43 @@ mod tests {
MockInstruction::NoopFail => return Err(InstructionError::GenericError), MockInstruction::NoopFail => return Err(InstructionError::GenericError),
MockInstruction::ModifyOwned => instruction_context MockInstruction::ModifyOwned => instruction_context
.try_borrow_instruction_account(transaction_context, 0)? .try_borrow_instruction_account(transaction_context, 0)?
.set_data(&[1]) .set_data(&[1])?,
.unwrap(),
MockInstruction::ModifyNotOwned => instruction_context MockInstruction::ModifyNotOwned => instruction_context
.try_borrow_instruction_account(transaction_context, 1)? .try_borrow_instruction_account(transaction_context, 1)?
.set_data(&[1]) .set_data(&[1])?,
.unwrap(),
MockInstruction::ModifyReadonly => instruction_context MockInstruction::ModifyReadonly => instruction_context
.try_borrow_instruction_account(transaction_context, 2)? .try_borrow_instruction_account(transaction_context, 2)?
.set_data(&[1]) .set_data(&[1])?,
.unwrap(), MockInstruction::UnbalancedPush => {
instruction_context
.try_borrow_instruction_account(transaction_context, 0)?
.checked_add_lamports(1)?;
let program_id = *transaction_context.get_key_of_account_at_index(3)?;
let metas = vec![
AccountMeta::new_readonly(
*transaction_context.get_key_of_account_at_index(0)?,
false,
),
AccountMeta::new_readonly(
*transaction_context.get_key_of_account_at_index(1)?,
false,
),
];
let inner_instruction = Instruction::new_with_bincode(
program_id,
&MockInstruction::NoopSuccess,
metas,
);
let result = invoke_context.push(&instruction_accounts, &[3], &[]);
assert_eq!(result, Err(InstructionError::UnbalancedInstruction));
result?;
invoke_context
.native_invoke(inner_instruction, &[])
.and(invoke_context.pop())?;
}
MockInstruction::UnbalancedPop => instruction_context
.try_borrow_instruction_account(transaction_context, 0)?
.checked_add_lamports(1)?,
MockInstruction::ConsumeComputeUnits { MockInstruction::ConsumeComputeUnits {
compute_units_to_consume, compute_units_to_consume,
desired_result, desired_result,
@ -1294,14 +1355,12 @@ mod tests {
invoke_context invoke_context
.get_compute_meter() .get_compute_meter()
.borrow_mut() .borrow_mut()
.consume(compute_units_to_consume) .consume(compute_units_to_consume)?;
.unwrap();
return desired_result; return desired_result;
} }
MockInstruction::Resize { new_len } => instruction_context MockInstruction::Resize { new_len } => instruction_context
.try_borrow_instruction_account(transaction_context, 0)? .try_borrow_instruction_account(transaction_context, 0)?
.set_data(&vec![0; new_len as usize]) .set_data(&vec![0; new_len as usize])?,
.unwrap(),
} }
} else { } else {
return Err(InstructionError::InvalidInstructionData); return Err(InstructionError::InvalidInstructionData);
@ -1310,7 +1369,7 @@ mod tests {
} }
#[test] #[test]
fn test_invoke_context() { fn test_instruction_stack_height() {
const MAX_DEPTH: usize = 10; const MAX_DEPTH: usize = 10;
let mut invoke_stack = vec![]; let mut invoke_stack = vec![];
let mut accounts = vec![]; let mut accounts = vec![];
@ -1342,8 +1401,12 @@ mod tests {
is_writable: false, is_writable: false,
}); });
} }
let mut transaction_context = let mut transaction_context = TransactionContext::new(
TransactionContext::new(accounts, ComputeBudget::default().max_invoke_depth, 1); accounts,
Some(Rent::default()),
ComputeBudget::default().max_invoke_depth,
1,
);
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
// Check call depth increases and has a limit // Check call depth increases and has a limit
@ -1358,107 +1421,6 @@ mod tests {
} }
assert_ne!(depth_reached, 0); assert_ne!(depth_reached, 0);
assert!(depth_reached < MAX_DEPTH); assert!(depth_reached < MAX_DEPTH);
// Mock each invocation
for owned_index in (1..depth_reached).rev() {
let not_owned_index = owned_index - 1;
let instruction_accounts = vec![
InstructionAccount {
index_in_transaction: not_owned_index,
index_in_caller: not_owned_index,
index_in_callee: 0,
is_signer: false,
is_writable: true,
},
InstructionAccount {
index_in_transaction: owned_index,
index_in_caller: owned_index,
index_in_callee: 1,
is_signer: false,
is_writable: true,
},
];
// modify account owned by the program
*invoke_context
.transaction_context
.get_account_at_index(owned_index)
.unwrap()
.borrow_mut()
.data_as_mut_slice()
.get_mut(0)
.unwrap() = (MAX_DEPTH + owned_index) as u8;
invoke_context
.verify_and_update(&instruction_accounts, false)
.unwrap();
assert_eq!(
*invoke_context
.pre_accounts
.get(owned_index)
.unwrap()
.data()
.first()
.unwrap(),
(MAX_DEPTH + owned_index) as u8
);
// modify account not owned by the program
let data = *invoke_context
.transaction_context
.get_account_at_index(not_owned_index)
.unwrap()
.borrow_mut()
.data()
.first()
.unwrap();
*invoke_context
.transaction_context
.get_account_at_index(not_owned_index)
.unwrap()
.borrow_mut()
.data_as_mut_slice()
.get_mut(0)
.unwrap() = (MAX_DEPTH + not_owned_index) as u8;
assert_eq!(
invoke_context.verify_and_update(&instruction_accounts, false),
Err(InstructionError::ExternalAccountDataModified)
);
assert_eq!(
*invoke_context
.pre_accounts
.get(not_owned_index)
.unwrap()
.data()
.first()
.unwrap(),
data
);
*invoke_context
.transaction_context
.get_account_at_index(not_owned_index)
.unwrap()
.borrow_mut()
.data_as_mut_slice()
.get_mut(0)
.unwrap() = data;
invoke_context.pop().unwrap();
}
}
#[test]
fn test_invoke_context_verify() {
let accounts = vec![(solana_sdk::pubkey::new_rand(), AccountSharedData::default())];
let instruction_accounts = vec![];
let program_indices = vec![0];
let mut transaction_context = TransactionContext::new(accounts, 1, 1);
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
invoke_context
.push(&instruction_accounts, &program_indices, &[])
.unwrap();
assert!(invoke_context
.verify(&instruction_accounts, &program_indices)
.is_ok());
} }
#[test] #[test]
@ -1496,69 +1458,12 @@ mod tests {
is_writable: instruction_account_index < 2, is_writable: instruction_account_index < 2,
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mut transaction_context = TransactionContext::new(accounts, 2, 8); let mut transaction_context =
TransactionContext::new(accounts, Some(Rent::default()), 2, 9);
let mut invoke_context = let mut invoke_context =
InvokeContext::new_mock(&mut transaction_context, builtin_programs); InvokeContext::new_mock(&mut transaction_context, builtin_programs);
// External modification tests // Account modification tests
{
invoke_context
.push(&instruction_accounts, &[4], &[])
.unwrap();
let inner_instruction = Instruction::new_with_bincode(
callee_program_id,
&MockInstruction::NoopSuccess,
metas.clone(),
);
// not owned account
*invoke_context
.transaction_context
.get_account_at_index(1)
.unwrap()
.borrow_mut()
.data_as_mut_slice()
.get_mut(0)
.unwrap() = 1;
assert_eq!(
invoke_context.native_invoke(inner_instruction.clone(), &[]),
Err(InstructionError::ExternalAccountDataModified)
);
*invoke_context
.transaction_context
.get_account_at_index(1)
.unwrap()
.borrow_mut()
.data_as_mut_slice()
.get_mut(0)
.unwrap() = 0;
// readonly account
*invoke_context
.transaction_context
.get_account_at_index(2)
.unwrap()
.borrow_mut()
.data_as_mut_slice()
.get_mut(0)
.unwrap() = 1;
assert_eq!(
invoke_context.native_invoke(inner_instruction, &[]),
Err(InstructionError::ReadonlyDataModified)
);
*invoke_context
.transaction_context
.get_account_at_index(2)
.unwrap()
.borrow_mut()
.data_as_mut_slice()
.get_mut(0)
.unwrap() = 0;
invoke_context.pop().unwrap();
}
// Internal modification tests
let cases = vec![ let cases = vec![
(MockInstruction::NoopSuccess, Ok(())), (MockInstruction::NoopSuccess, Ok(())),
( (
@ -1574,6 +1479,14 @@ mod tests {
MockInstruction::ModifyReadonly, MockInstruction::ModifyReadonly,
Err(InstructionError::ReadonlyDataModified), Err(InstructionError::ReadonlyDataModified),
), ),
(
MockInstruction::UnbalancedPush,
Err(InstructionError::UnbalancedInstruction),
),
(
MockInstruction::UnbalancedPop,
Err(InstructionError::UnbalancedInstruction),
),
]; ];
for case in cases { for case in cases {
invoke_context invoke_context
@ -1581,8 +1494,10 @@ mod tests {
.unwrap(); .unwrap();
let inner_instruction = let inner_instruction =
Instruction::new_with_bincode(callee_program_id, &case.0, metas.clone()); Instruction::new_with_bincode(callee_program_id, &case.0, metas.clone());
assert_eq!(invoke_context.native_invoke(inner_instruction, &[]), case.1); let result = invoke_context
invoke_context.pop().unwrap(); .native_invoke(inner_instruction, &[])
.and(invoke_context.pop());
assert_eq!(result, case.1);
} }
// Compute unit consumption tests // Compute unit consumption tests
@ -1628,7 +1543,8 @@ mod tests {
fn test_invoke_context_compute_budget() { fn test_invoke_context_compute_budget() {
let accounts = vec![(solana_sdk::pubkey::new_rand(), AccountSharedData::default())]; let accounts = vec![(solana_sdk::pubkey::new_rand(), AccountSharedData::default())];
let mut transaction_context = TransactionContext::new(accounts, 1, 3); let mut transaction_context =
TransactionContext::new(accounts, Some(Rent::default()), 1, 3);
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
invoke_context.compute_budget = invoke_context.compute_budget =
ComputeBudget::new(compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64); ComputeBudget::new(compute_budget::DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64);
@ -1663,7 +1579,8 @@ mod tests {
process_instruction: mock_process_instruction, process_instruction: mock_process_instruction,
}]; }];
let mut transaction_context = TransactionContext::new(accounts, 1, 3); let mut transaction_context =
TransactionContext::new(accounts, Some(Rent::default()), 1, 3);
let mut invoke_context = let mut invoke_context =
InvokeContext::new_mock(&mut transaction_context, &builtin_programs); InvokeContext::new_mock(&mut transaction_context, &builtin_programs);
@ -1707,7 +1624,13 @@ mod tests {
); );
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!(invoke_context.accounts_data_meter.remaining(), 0); assert_eq!(
invoke_context
.transaction_context
.accounts_resize_delta()
.unwrap(),
user_account_data_len as i64 * 2
);
} }
// Test 2: Resize the account to *the same size*, so not consuming any additional size; this must succeed // Test 2: Resize the account to *the same size*, so not consuming any additional size; this must succeed
@ -1725,10 +1648,16 @@ mod tests {
); );
assert!(result.is_ok()); assert!(result.is_ok());
assert_eq!(invoke_context.accounts_data_meter.remaining(), 0); assert_eq!(
invoke_context
.transaction_context
.accounts_resize_delta()
.unwrap(),
user_account_data_len as i64 * 2
);
} }
// Test 3: Resize the account to exceed the budget; this must fail // Test 3: Resize the account to exceed the budget; this must succeed
{ {
let new_len = user_account_data_len + remaining_account_data_len + 1; let new_len = user_account_data_len + remaining_account_data_len + 1;
let instruction_data = let instruction_data =
@ -1742,12 +1671,14 @@ mod tests {
&mut ExecuteTimings::default(), &mut ExecuteTimings::default(),
); );
assert!(result.is_err()); assert!(result.is_ok());
assert!(matches!( assert_eq!(
result, invoke_context
Err(solana_sdk::instruction::InstructionError::MaxAccountsDataSizeExceeded) .transaction_context
)); .accounts_resize_delta()
assert_eq!(invoke_context.accounts_data_meter.remaining(), 0); .unwrap(),
user_account_data_len as i64 * 2 + 1
);
} }
} }
} }

View File

@ -183,8 +183,14 @@ pub fn builtin_process_instruction(
if borrowed_account.get_lamports() != lamports { if borrowed_account.get_lamports() != lamports {
borrowed_account.set_lamports(lamports)?; borrowed_account.set_lamports(lamports)?;
} }
if borrowed_account.get_data() != data { // The redundant check helps to avoid the expensive data comparison if we can
borrowed_account.set_data(&data)?; match borrowed_account
.can_data_be_resized(data.len())
.and_then(|_| borrowed_account.can_data_be_changed())
{
Ok(()) => borrowed_account.set_data(&data)?,
Err(err) if borrowed_account.get_data() != data => return Err(err),
_ => {}
} }
if borrowed_account.get_owner() != &owner { if borrowed_account.get_owner() != &owner {
borrowed_account.set_owner(owner.as_ref())?; borrowed_account.set_owner(owner.as_ref())?;
@ -302,14 +308,23 @@ impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs {
.unwrap(); .unwrap();
} }
let account_info_data = account_info.try_borrow_data().unwrap(); let account_info_data = account_info.try_borrow_data().unwrap();
if borrowed_account.get_data() != *account_info_data { // The redundant check helps to avoid the expensive data comparison if we can
borrowed_account.set_data(&account_info_data).unwrap(); match borrowed_account
.can_data_be_resized(account_info_data.len())
.and_then(|_| borrowed_account.can_data_be_changed())
{
Ok(()) => borrowed_account.set_data(&account_info_data).unwrap(),
Err(err) if borrowed_account.get_data() != *account_info_data => {
panic!("{:?}", err);
}
_ => {}
} }
if borrowed_account.is_executable() != account_info.executable { if borrowed_account.is_executable() != account_info.executable {
borrowed_account borrowed_account
.set_executable(account_info.executable) .set_executable(account_info.executable)
.unwrap(); .unwrap();
} }
// Change the owner at the end so that we are allowed to change the lamports and data before
if borrowed_account.get_owner() != account_info.owner { if borrowed_account.get_owner() != account_info.owner {
borrowed_account borrowed_account
.set_owner(account_info.owner.as_ref()) .set_owner(account_info.owner.as_ref())

View File

@ -305,7 +305,7 @@ fn run_program(name: &str) -> u64 {
tracer = Some(vm.get_tracer().clone()); tracer = Some(vm.get_tracer().clone());
} }
} }
deserialize_parameters( assert!(match deserialize_parameters(
invoke_context.transaction_context, invoke_context.transaction_context,
invoke_context invoke_context
.transaction_context .transaction_context
@ -313,8 +313,21 @@ fn run_program(name: &str) -> u64 {
.unwrap(), .unwrap(),
parameter_bytes.as_slice(), parameter_bytes.as_slice(),
&account_lengths, &account_lengths,
) ) {
.unwrap(); Ok(()) => true,
Err(InstructionError::ModifiedProgramId) => true,
Err(InstructionError::ExternalAccountLamportSpend) => true,
Err(InstructionError::ReadonlyLamportChange) => true,
Err(InstructionError::ExecutableLamportChange) => true,
Err(InstructionError::ExecutableAccountNotRentExempt) => true,
Err(InstructionError::ExecutableModified) => true,
Err(InstructionError::AccountDataSizeChanged) => true,
Err(InstructionError::InvalidRealloc) => true,
Err(InstructionError::ExecutableDataModified) => true,
Err(InstructionError::ReadonlyDataModified) => true,
Err(InstructionError::ExternalAccountDataModified) => true,
_ => false,
});
} }
instruction_count instruction_count
}) })

View File

@ -9,6 +9,7 @@ use {
solana_sdk::{ solana_sdk::{
account::{Account, AccountSharedData}, account::{Account, AccountSharedData},
bpf_loader, bpf_loader,
sysvar::rent::Rent,
transaction_context::{InstructionAccount, TransactionContext}, transaction_context::{InstructionAccount, TransactionContext},
}, },
test::Bencher, test::Bencher,
@ -101,7 +102,8 @@ fn create_inputs() -> TransactionContext {
}, },
) )
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mut transaction_context = TransactionContext::new(transaction_accounts, 1, 1); let mut transaction_context =
TransactionContext::new(transaction_accounts, Some(Rent::default()), 1, 1);
let instruction_data = vec![1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; let instruction_data = vec![1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
transaction_context transaction_context
.push(&[0], &instruction_accounts, &instruction_data, true) .push(&[0], &instruction_accounts, &instruction_data, true)

View File

@ -164,18 +164,28 @@ pub fn deserialize_parameters_unaligned(
start += size_of::<u8>(); // is_signer start += size_of::<u8>(); // is_signer
start += size_of::<u8>(); // is_writable start += size_of::<u8>(); // is_writable
start += size_of::<Pubkey>(); // key start += size_of::<Pubkey>(); // key
let _ = borrowed_account.set_lamports(LittleEndian::read_u64( let lamports = LittleEndian::read_u64(
buffer buffer
.get(start..) .get(start..)
.ok_or(InstructionError::InvalidArgument)?, .ok_or(InstructionError::InvalidArgument)?,
)); );
if borrowed_account.get_lamports() != lamports {
borrowed_account.set_lamports(lamports)?;
}
start += size_of::<u64>() // lamports start += size_of::<u64>() // lamports
+ size_of::<u64>(); // data length + size_of::<u64>(); // data length
let _ = borrowed_account.set_data( let data = buffer
buffer .get(start..start + pre_len)
.get(start..start + pre_len) .ok_or(InstructionError::InvalidArgument)?;
.ok_or(InstructionError::InvalidArgument)?, // The redundant check helps to avoid the expensive data comparison if we can
); match borrowed_account
.can_data_be_resized(data.len())
.and_then(|_| borrowed_account.can_data_be_changed())
{
Ok(()) => borrowed_account.set_data(data)?,
Err(err) if borrowed_account.get_data() != data => return Err(err),
_ => {}
}
start += pre_len // data start += pre_len // data
+ size_of::<Pubkey>() // owner + size_of::<Pubkey>() // owner
+ size_of::<u8>() // executable + size_of::<u8>() // executable
@ -302,17 +312,18 @@ pub fn deserialize_parameters_aligned(
+ size_of::<u8>() // executable + size_of::<u8>() // executable
+ size_of::<u32>() // original_data_len + size_of::<u32>() // original_data_len
+ size_of::<Pubkey>(); // key + size_of::<Pubkey>(); // key
let _ = borrowed_account.set_owner( let owner = buffer
buffer .get(start..start + size_of::<Pubkey>())
.get(start..start + size_of::<Pubkey>()) .ok_or(InstructionError::InvalidArgument)?;
.ok_or(InstructionError::InvalidArgument)?,
);
start += size_of::<Pubkey>(); // owner start += size_of::<Pubkey>(); // owner
let _ = borrowed_account.set_lamports(LittleEndian::read_u64( let lamports = LittleEndian::read_u64(
buffer buffer
.get(start..) .get(start..)
.ok_or(InstructionError::InvalidArgument)?, .ok_or(InstructionError::InvalidArgument)?,
)); );
if borrowed_account.get_lamports() != lamports {
borrowed_account.set_lamports(lamports)?;
}
start += size_of::<u64>(); // lamports start += size_of::<u64>(); // lamports
let post_len = LittleEndian::read_u64( let post_len = LittleEndian::read_u64(
buffer buffer
@ -320,21 +331,31 @@ pub fn deserialize_parameters_aligned(
.ok_or(InstructionError::InvalidArgument)?, .ok_or(InstructionError::InvalidArgument)?,
) as usize; ) as usize;
start += size_of::<u64>(); // data length start += size_of::<u64>(); // data length
if post_len.saturating_sub(*pre_len) > MAX_PERMITTED_DATA_INCREASE if post_len.saturating_sub(*pre_len) > MAX_PERMITTED_DATA_INCREASE
|| post_len > MAX_PERMITTED_DATA_LENGTH as usize || post_len > MAX_PERMITTED_DATA_LENGTH as usize
{ {
return Err(InstructionError::InvalidRealloc); return Err(InstructionError::InvalidRealloc);
} }
let data_end = start + post_len; let data_end = start + post_len;
let _ = borrowed_account.set_data( let data = buffer
buffer .get(start..data_end)
.get(start..data_end) .ok_or(InstructionError::InvalidArgument)?;
.ok_or(InstructionError::InvalidArgument)?, // The redundant check helps to avoid the expensive data comparison if we can
); match borrowed_account
.can_data_be_resized(data.len())
.and_then(|_| borrowed_account.can_data_be_changed())
{
Ok(()) => borrowed_account.set_data(data)?,
Err(err) if borrowed_account.get_data() != data => return Err(err),
_ => {}
}
start += *pre_len + MAX_PERMITTED_DATA_INCREASE; // data start += *pre_len + MAX_PERMITTED_DATA_INCREASE; // data
start += (start as *const u8).align_offset(BPF_ALIGN_OF_U128); start += (start as *const u8).align_offset(BPF_ALIGN_OF_U128);
start += size_of::<u64>(); // rent_epoch start += size_of::<u64>(); // rent_epoch
if borrowed_account.get_owner().to_bytes() != owner {
// Change the owner at the end so that we are allowed to change the lamports and data before
borrowed_account.set_owner(owner)?;
}
} }
} }
Ok(()) Ok(())
@ -351,6 +372,7 @@ mod tests {
bpf_loader, bpf_loader,
entrypoint::deserialize, entrypoint::deserialize,
instruction::AccountMeta, instruction::AccountMeta,
sysvar::rent::Rent,
}, },
std::{ std::{
cell::RefCell, cell::RefCell,
@ -453,8 +475,12 @@ mod tests {
instruction_accounts, instruction_accounts,
&program_indices, &program_indices,
); );
let mut transaction_context = let mut transaction_context = TransactionContext::new(
TransactionContext::new(preparation.transaction_accounts, 1, 1); preparation.transaction_accounts,
Some(Rent::default()),
1,
1,
);
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
invoke_context invoke_context
.push( .push(
@ -512,16 +538,6 @@ mod tests {
); );
} }
for index_in_transaction in 1..original_accounts.len() {
let mut account = invoke_context
.transaction_context
.get_account_at_index(index_in_transaction)
.unwrap()
.borrow_mut();
account.set_lamports(0);
account.set_data(vec![0; 0]);
account.set_owner(Pubkey::default());
}
deserialize_parameters( deserialize_parameters(
invoke_context.transaction_context, invoke_context.transaction_context,
instruction_context, instruction_context,
@ -545,13 +561,12 @@ mod tests {
.unwrap() .unwrap()
.1 .1
.set_owner(bpf_loader_deprecated::id()); .set_owner(bpf_loader_deprecated::id());
let _ = invoke_context invoke_context
.transaction_context .transaction_context
.get_current_instruction_context() .get_account_at_index(0)
.unwrap() .unwrap()
.try_borrow_program_account(invoke_context.transaction_context, 0) .borrow_mut()
.unwrap() .set_owner(bpf_loader_deprecated::id());
.set_owner(bpf_loader_deprecated::id().as_ref());
let (mut serialized, account_lengths) = let (mut serialized, account_lengths) =
serialize_parameters(invoke_context.transaction_context, instruction_context).unwrap(); serialize_parameters(invoke_context.transaction_context, instruction_context).unwrap();
@ -578,15 +593,6 @@ mod tests {
assert_eq!(account.rent_epoch(), account_info.rent_epoch); assert_eq!(account.rent_epoch(), account_info.rent_epoch);
} }
for index_in_transaction in 1..original_accounts.len() {
let mut account = invoke_context
.transaction_context
.get_account_at_index(index_in_transaction)
.unwrap()
.borrow_mut();
account.set_lamports(0);
account.set_data(vec![0; 0]);
}
deserialize_parameters( deserialize_parameters(
invoke_context.transaction_context, invoke_context.transaction_context,
instruction_context, instruction_context,

View File

@ -17,13 +17,14 @@ use {
vm::{EbpfVm, SyscallObject, SyscallRegistry}, vm::{EbpfVm, SyscallObject, SyscallRegistry},
}, },
solana_sdk::{ solana_sdk::{
account::WritableAccount, account::{ReadableAccount, WritableAccount},
account_info::AccountInfo, account_info::AccountInfo,
blake3, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, blake3, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable,
entrypoint::{BPF_ALIGN_OF_U128, MAX_PERMITTED_DATA_INCREASE, SUCCESS}, entrypoint::{BPF_ALIGN_OF_U128, MAX_PERMITTED_DATA_INCREASE, SUCCESS},
feature_set::{ feature_set::{
blake3_syscall_enabled, check_physical_overlapping, check_slice_translation_size, blake3_syscall_enabled, check_physical_overlapping, check_slice_translation_size,
curve25519_syscall_enabled, disable_fees_sysvar, libsecp256k1_0_5_upgrade_enabled, curve25519_syscall_enabled, disable_fees_sysvar,
enable_early_verification_of_account_modifications, libsecp256k1_0_5_upgrade_enabled,
limit_secp256k1_recovery_id, prevent_calling_precompiles_as_programs, limit_secp256k1_recovery_id, prevent_calling_precompiles_as_programs,
syscall_saturated_math, syscall_saturated_math,
}, },
@ -2663,26 +2664,56 @@ where
invoke_context, invoke_context,
)?; )?;
{ {
callee_account if callee_account.get_lamports() != *caller_account.lamports {
.set_lamports(*caller_account.lamports) callee_account
.map_err(SyscallError::InstructionError)?; .set_lamports(*caller_account.lamports)
callee_account .map_err(SyscallError::InstructionError)?;
.set_data(caller_account.data) }
.map_err(SyscallError::InstructionError)?; // The redundant check helps to avoid the expensive data comparison if we can
callee_account match callee_account
.set_executable(caller_account.executable) .can_data_be_resized(caller_account.data.len())
.map_err(SyscallError::InstructionError)?; .and_then(|_| callee_account.can_data_be_changed())
callee_account {
.set_owner(caller_account.owner.as_ref()) Ok(()) => callee_account
.map_err(SyscallError::InstructionError)?; .set_data(caller_account.data)
.map_err(SyscallError::InstructionError)?,
Err(err) if callee_account.get_data() != caller_account.data => {
return Err(EbpfError::UserError(BpfError::SyscallError(
SyscallError::InstructionError(err),
)));
}
_ => {}
}
if callee_account.is_executable() != caller_account.executable {
callee_account
.set_executable(caller_account.executable)
.map_err(SyscallError::InstructionError)?;
}
// Change the owner at the end so that we are allowed to change the lamports and data before
if callee_account.get_owner() != caller_account.owner {
callee_account
.set_owner(caller_account.owner.as_ref())
.map_err(SyscallError::InstructionError)?;
}
drop(callee_account); drop(callee_account);
let callee_account = invoke_context let callee_account = invoke_context
.transaction_context .transaction_context
.get_account_at_index(instruction_account.index_in_transaction) .get_account_at_index(instruction_account.index_in_transaction)
.map_err(SyscallError::InstructionError)?; .map_err(SyscallError::InstructionError)?;
callee_account if callee_account.borrow().rent_epoch() != caller_account.rent_epoch {
.borrow_mut() if invoke_context
.set_rent_epoch(caller_account.rent_epoch); .feature_set
.is_active(&enable_early_verification_of_account_modifications::id())
{
Err(SyscallError::InstructionError(
InstructionError::RentEpochModified,
))?;
} else {
callee_account
.borrow_mut()
.set_rent_epoch(caller_account.rent_epoch);
}
}
} }
let caller_account = if instruction_account.is_writable { let caller_account = if instruction_account.is_writable {
let orig_data_lens = invoke_context let orig_data_lens = invoke_context
@ -3364,7 +3395,8 @@ mod tests {
), ),
($program_key, AccountSharedData::new(0, 0, &$loader_key)), ($program_key, AccountSharedData::new(0, 0, &$loader_key)),
]; ];
let mut $transaction_context = TransactionContext::new(transaction_accounts, 1, 1); let mut $transaction_context =
TransactionContext::new(transaction_accounts, Some(Rent::default()), 1, 1);
let mut $invoke_context = InvokeContext::new_mock(&mut $transaction_context, &[]); let mut $invoke_context = InvokeContext::new_mock(&mut $transaction_context, &[]);
$invoke_context.push(&[], &[0, 1], &[]).unwrap(); $invoke_context.push(&[], &[0, 1], &[]).unwrap();
}; };

View File

@ -2783,6 +2783,7 @@ mod tests {
Rent::id(), Rent::id(),
create_account_shared_data_for_test(&Rent::default()), create_account_shared_data_for_test(&Rent::default()),
)], )],
Some(Rent::default()),
1, 1,
1, 1,
) )
@ -2894,7 +2895,8 @@ mod tests {
#[test] #[test]
fn test_things_can_merge() { fn test_things_can_merge() {
let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1); let mut transaction_context =
TransactionContext::new(Vec::new(), Some(Rent::default()), 1, 1);
let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
let good_stake = Stake { let good_stake = Stake {
credits_observed: 4242, credits_observed: 4242,
@ -2993,7 +2995,8 @@ mod tests {
#[test] #[test]
fn test_metas_can_merge() { fn test_metas_can_merge() {
let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1); let mut transaction_context =
TransactionContext::new(Vec::new(), Some(Rent::default()), 1, 1);
let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
// Identical Metas can merge // Identical Metas can merge
assert!(MergeKind::metas_can_merge( assert!(MergeKind::metas_can_merge(
@ -3140,7 +3143,8 @@ mod tests {
#[test] #[test]
fn test_merge_kind_get_if_mergeable() { fn test_merge_kind_get_if_mergeable() {
let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1); let mut transaction_context =
TransactionContext::new(Vec::new(), Some(Rent::default()), 1, 1);
let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
let authority_pubkey = Pubkey::new_unique(); let authority_pubkey = Pubkey::new_unique();
let initial_lamports = 4242424242; let initial_lamports = 4242424242;
@ -3379,7 +3383,8 @@ mod tests {
#[test] #[test]
fn test_merge_kind_merge() { fn test_merge_kind_merge() {
let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1); let mut transaction_context =
TransactionContext::new(Vec::new(), Some(Rent::default()), 1, 1);
let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
let clock = Clock::default(); let clock = Clock::default();
let lamports = 424242; let lamports = 424242;

View File

@ -107,7 +107,12 @@ fn bench_process_vote_instruction(
instruction_data: Vec<u8>, instruction_data: Vec<u8>,
) { ) {
bencher.iter(|| { bencher.iter(|| {
let mut transaction_context = TransactionContext::new(transaction_accounts.clone(), 1, 1); let mut transaction_context = TransactionContext::new(
transaction_accounts.clone(),
Some(sysvar::rent::Rent::default()),
1,
1,
);
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
invoke_context invoke_context
.push(&instruction_accounts, &[0], &instruction_data) .push(&instruction_accounts, &[0], &instruction_data)

View File

@ -16,7 +16,7 @@ use {
}, },
solana_sdk::{ solana_sdk::{
account::AccountSharedData, bpf_loader, instruction::AccountMeta, pubkey::Pubkey, account::AccountSharedData, bpf_loader, instruction::AccountMeta, pubkey::Pubkey,
transaction_context::TransactionContext, sysvar::rent::Rent, transaction_context::TransactionContext,
}, },
std::{ std::{
fmt::{Debug, Formatter}, fmt::{Debug, Formatter},
@ -216,7 +216,12 @@ native machine code before execting it in the virtual machine.",
let program_indices = [0, 1]; let program_indices = [0, 1];
let preparation = let preparation =
prepare_mock_invoke_context(transaction_accounts, instruction_accounts, &program_indices); prepare_mock_invoke_context(transaction_accounts, instruction_accounts, &program_indices);
let mut transaction_context = TransactionContext::new(preparation.transaction_accounts, 1, 1); let mut transaction_context = TransactionContext::new(
preparation.transaction_accounts,
Some(Rent::default()),
1,
1,
);
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
invoke_context invoke_context
.push( .push(

View File

@ -109,7 +109,7 @@ use {
feature, feature,
feature_set::{ feature_set::{
self, add_set_compute_unit_price_ix, default_units_per_instruction, self, add_set_compute_unit_price_ix, default_units_per_instruction,
disable_fee_calculator, FeatureSet, disable_fee_calculator, enable_early_verification_of_account_modifications, FeatureSet,
}, },
fee::FeeStructure, fee::FeeStructure,
fee_calculator::{FeeCalculator, FeeRateGovernor}, fee_calculator::{FeeCalculator, FeeRateGovernor},
@ -4274,6 +4274,14 @@ impl Bank {
let transaction_accounts = std::mem::take(&mut loaded_transaction.accounts); let transaction_accounts = std::mem::take(&mut loaded_transaction.accounts);
let mut transaction_context = TransactionContext::new( let mut transaction_context = TransactionContext::new(
transaction_accounts, transaction_accounts,
if self
.feature_set
.is_active(&enable_early_verification_of_account_modifications::id())
{
Some(self.rent_collector.rent)
} else {
None
},
compute_budget.max_invoke_depth.saturating_add(1), compute_budget.max_invoke_depth.saturating_add(1),
tx.message().instructions().len(), tx.message().instructions().len(),
); );
@ -4352,7 +4360,7 @@ impl Bank {
} }
err err
}); });
let accounts_data_len_delta = status let mut accounts_data_len_delta = status
.as_ref() .as_ref()
.map_or(0, |info| info.accounts_data_len_delta); .map_or(0, |info| info.accounts_data_len_delta);
let status = status.map(|_| ()); let status = status.map(|_| ());
@ -4368,9 +4376,31 @@ impl Bank {
accounts, accounts,
instruction_trace, instruction_trace,
mut return_data, mut return_data,
.. changed_account_count,
total_size_of_all_accounts,
total_size_of_touched_accounts,
accounts_resize_delta,
} = transaction_context.into(); } = transaction_context.into();
loaded_transaction.accounts = accounts; loaded_transaction.accounts = accounts;
if self
.feature_set
.is_active(&enable_early_verification_of_account_modifications::id())
{
saturating_add_assign!(
timings.details.total_account_count,
loaded_transaction.accounts.len() as u64
);
saturating_add_assign!(timings.details.changed_account_count, changed_account_count);
saturating_add_assign!(
timings.details.total_data_size,
total_size_of_all_accounts as usize
);
saturating_add_assign!(
timings.details.data_size_changed,
total_size_of_touched_accounts as usize
);
accounts_data_len_delta = status.as_ref().map_or(0, |_| accounts_resize_delta);
}
let inner_instructions = if enable_cpi_recording { let inner_instructions = if enable_cpi_recording {
Some(inner_instructions_list_from_instruction_trace( Some(inner_instructions_list_from_instruction_trace(
@ -18668,6 +18698,7 @@ pub(crate) mod tests {
}); });
let transaction_context = TransactionContext::new( let transaction_context = TransactionContext::new(
loaded_txs[0].0.as_ref().unwrap().accounts.clone(), loaded_txs[0].0.as_ref().unwrap().accounts.clone(),
Some(Rent::default()),
compute_budget.max_invoke_depth.saturating_add(1), compute_budget.max_invoke_depth.saturating_add(1),
number_of_instructions_at_transaction_level, number_of_instructions_at_transaction_level,
); );
@ -18819,15 +18850,15 @@ pub(crate) mod tests {
fn test_inner_instructions_list_from_instruction_trace() { fn test_inner_instructions_list_from_instruction_trace() {
let instruction_trace = vec![ let instruction_trace = vec![
vec![ vec![
InstructionContext::new(0, &[], &[], &[1]), InstructionContext::new(0, 0, &[], &[], &[1]),
InstructionContext::new(1, &[], &[], &[2]), InstructionContext::new(1, 0, &[], &[], &[2]),
], ],
vec![], vec![],
vec![ vec![
InstructionContext::new(0, &[], &[], &[3]), InstructionContext::new(0, 0, &[], &[], &[3]),
InstructionContext::new(1, &[], &[], &[4]), InstructionContext::new(1, 0, &[], &[], &[4]),
InstructionContext::new(2, &[], &[], &[5]), InstructionContext::new(2, 0, &[], &[], &[5]),
InstructionContext::new(1, &[], &[], &[6]), InstructionContext::new(1, 0, &[], &[], &[6]),
], ],
]; ];

View File

@ -282,7 +282,8 @@ mod tests {
create_loadable_account_for_test("mock_system_program"), create_loadable_account_for_test("mock_system_program"),
), ),
]; ];
let mut transaction_context = TransactionContext::new(accounts, 1, 3); let mut transaction_context =
TransactionContext::new(accounts, Some(Rent::default()), 1, 3);
let program_indices = vec![vec![2]]; let program_indices = vec![vec![2]];
let executors = Rc::new(RefCell::new(Executors::default())); let executors = Rc::new(RefCell::new(Executors::default()));
let account_keys = transaction_context.get_keys_of_accounts().to_vec(); let account_keys = transaction_context.get_keys_of_accounts().to_vec();
@ -502,7 +503,8 @@ mod tests {
create_loadable_account_for_test("mock_system_program"), create_loadable_account_for_test("mock_system_program"),
), ),
]; ];
let mut transaction_context = TransactionContext::new(accounts, 1, 3); let mut transaction_context =
TransactionContext::new(accounts, Some(Rent::default()), 1, 3);
let program_indices = vec![vec![2]]; let program_indices = vec![vec![2]];
let executors = Rc::new(RefCell::new(Executors::default())); let executors = Rc::new(RefCell::new(Executors::default()));
let account_metas = vec![ let account_metas = vec![
@ -661,7 +663,8 @@ mod tests {
(secp256k1_program::id(), secp256k1_account), (secp256k1_program::id(), secp256k1_account),
(mock_program_id, mock_program_account), (mock_program_id, mock_program_account),
]; ];
let mut transaction_context = TransactionContext::new(accounts, 1, 2); let mut transaction_context =
TransactionContext::new(accounts, Some(Rent::default()), 1, 2);
let message = SanitizedMessage::Legacy(Message::new( let message = SanitizedMessage::Legacy(Message::new(
&[ &[

View File

@ -328,7 +328,8 @@ mod test {
is_writable: true, is_writable: true,
}, },
]; ];
let mut transaction_context = TransactionContext::new(accounts, 1, 2); let mut transaction_context =
TransactionContext::new(accounts, Some(Rent::default()), 1, 2);
let mut $invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let mut $invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
}; };
} }

View File

@ -786,7 +786,8 @@ mod tests {
#[test] #[test]
fn test_address_create_with_seed_mismatch() { fn test_address_create_with_seed_mismatch() {
let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1); let mut transaction_context =
TransactionContext::new(Vec::new(), Some(Rent::default()), 1, 1);
let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]); let invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
let from = Pubkey::new_unique(); let from = Pubkey::new_unique();
let seed = "dull boy"; let seed = "dull boy";

View File

@ -456,6 +456,10 @@ pub mod enable_bpf_loader_extend_program_data_ix {
solana_sdk::declare_id!("8Zs9W7D9MpSEtUWSQdGniZk2cNmV22y6FLJwCx53asme"); solana_sdk::declare_id!("8Zs9W7D9MpSEtUWSQdGniZk2cNmV22y6FLJwCx53asme");
} }
pub mod enable_early_verification_of_account_modifications {
solana_sdk::declare_id!("7Vced912WrRnfjaiKRiNBcbuFw7RrnLv3E3z95Y4GTNc");
}
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> = [
@ -564,6 +568,7 @@ lazy_static! {
(cap_accounts_data_size_per_block::id(), "cap the accounts data size per block #25517"), (cap_accounts_data_size_per_block::id(), "cap the accounts data size per block #25517"),
(preserve_rent_epoch_for_rent_exempt_accounts::id(), "preserve rent epoch for rent exempt accounts #26479"), (preserve_rent_epoch_for_rent_exempt_accounts::id(), "preserve rent epoch for rent exempt accounts #26479"),
(enable_bpf_loader_extend_program_data_ix::id(), "enable bpf upgradeable loader ExtendProgramData instruction #25234"), (enable_bpf_loader_extend_program_data_ix::id(), "enable bpf upgradeable loader ExtendProgramData instruction #25234"),
(enable_early_verification_of_account_modifications::id(), "enable early verification of account modifications #25899"),
/*************** ADD NEW FEATURES HERE ***************/ /*************** ADD NEW FEATURES HERE ***************/
] ]
.iter() .iter()

View File

@ -5,6 +5,8 @@ use {
account::{AccountSharedData, ReadableAccount, WritableAccount}, account::{AccountSharedData, ReadableAccount, WritableAccount},
instruction::InstructionError, instruction::InstructionError,
pubkey::Pubkey, pubkey::Pubkey,
rent::Rent,
system_instruction::MAX_PERMITTED_DATA_LENGTH,
}, },
std::{ std::{
cell::{RefCell, RefMut}, cell::{RefCell, RefMut},
@ -43,18 +45,21 @@ pub struct InstructionAccount {
pub struct TransactionContext { pub struct TransactionContext {
account_keys: Pin<Box<[Pubkey]>>, account_keys: Pin<Box<[Pubkey]>>,
accounts: Pin<Box<[RefCell<AccountSharedData>]>>, accounts: Pin<Box<[RefCell<AccountSharedData>]>>,
account_touched_flags: RefCell<Pin<Box<[bool]>>>,
instruction_context_capacity: usize, instruction_context_capacity: usize,
instruction_stack: Vec<usize>, instruction_stack: Vec<usize>,
number_of_instructions_at_transaction_level: usize, number_of_instructions_at_transaction_level: usize,
instruction_trace: InstructionTrace, instruction_trace: InstructionTrace,
return_data: TransactionReturnData, return_data: TransactionReturnData,
total_resize_delta: RefCell<i64>, accounts_resize_delta: RefCell<i64>,
rent: Option<Rent>,
} }
impl TransactionContext { impl TransactionContext {
/// Constructs a new TransactionContext /// Constructs a new TransactionContext
pub fn new( pub fn new(
transaction_accounts: Vec<TransactionAccount>, transaction_accounts: Vec<TransactionAccount>,
rent: Option<Rent>,
instruction_context_capacity: usize, instruction_context_capacity: usize,
number_of_instructions_at_transaction_level: usize, number_of_instructions_at_transaction_level: usize,
) -> Self { ) -> Self {
@ -63,15 +68,18 @@ impl TransactionContext {
.into_iter() .into_iter()
.map(|(key, account)| (key, RefCell::new(account))) .map(|(key, account)| (key, RefCell::new(account)))
.unzip(); .unzip();
let account_touched_flags = vec![false; accounts.len()];
Self { Self {
account_keys: Pin::new(account_keys.into_boxed_slice()), account_keys: Pin::new(account_keys.into_boxed_slice()),
accounts: Pin::new(accounts.into_boxed_slice()), accounts: Pin::new(accounts.into_boxed_slice()),
account_touched_flags: RefCell::new(Pin::new(account_touched_flags.into_boxed_slice())),
instruction_context_capacity, instruction_context_capacity,
instruction_stack: Vec::with_capacity(instruction_context_capacity), instruction_stack: Vec::with_capacity(instruction_context_capacity),
number_of_instructions_at_transaction_level, number_of_instructions_at_transaction_level,
instruction_trace: Vec::with_capacity(number_of_instructions_at_transaction_level), instruction_trace: Vec::with_capacity(number_of_instructions_at_transaction_level),
return_data: TransactionReturnData::default(), return_data: TransactionReturnData::default(),
total_resize_delta: RefCell::new(0), accounts_resize_delta: RefCell::new(0),
rent,
} }
} }
@ -86,6 +94,11 @@ impl TransactionContext {
.collect()) .collect())
} }
/// Returns true if `enable_early_verification_of_account_modifications` is active
pub fn is_early_verification_of_account_modifications_enabled(&self) -> bool {
self.rent.is_some()
}
/// Returns the total number of accounts loaded in this Transaction /// Returns the total number of accounts loaded in this Transaction
pub fn get_number_of_accounts(&self) -> usize { pub fn get_number_of_accounts(&self) -> usize {
self.accounts.len() self.accounts.len()
@ -180,31 +193,51 @@ impl TransactionContext {
instruction_data: &[u8], instruction_data: &[u8],
record_instruction_in_transaction_context_push: bool, record_instruction_in_transaction_context_push: bool,
) -> Result<(), InstructionError> { ) -> Result<(), InstructionError> {
let callee_instruction_accounts_lamport_sum =
self.instruction_accounts_lamport_sum(instruction_accounts)?;
let index_in_trace = if self.instruction_stack.is_empty() { let index_in_trace = if self.instruction_stack.is_empty() {
debug_assert!( debug_assert!(
self.instruction_trace.len() < self.number_of_instructions_at_transaction_level self.instruction_trace.len() < self.number_of_instructions_at_transaction_level
); );
let instruction_context = InstructionContext { let instruction_context = InstructionContext {
nesting_level: self.instruction_stack.len(), nesting_level: self.instruction_stack.len(),
instruction_accounts_lamport_sum: callee_instruction_accounts_lamport_sum,
program_accounts: program_accounts.to_vec(), program_accounts: program_accounts.to_vec(),
instruction_accounts: instruction_accounts.to_vec(), instruction_accounts: instruction_accounts.to_vec(),
instruction_data: instruction_data.to_vec(), instruction_data: instruction_data.to_vec(),
}; };
self.instruction_trace.push(vec![instruction_context]); self.instruction_trace.push(vec![instruction_context]);
self.instruction_trace.len().saturating_sub(1) self.instruction_trace.len().saturating_sub(1)
} else if let Some(instruction_trace) = self.instruction_trace.last_mut() {
if record_instruction_in_transaction_context_push {
let instruction_context = InstructionContext {
nesting_level: self.instruction_stack.len(),
program_accounts: program_accounts.to_vec(),
instruction_accounts: instruction_accounts.to_vec(),
instruction_data: instruction_data.to_vec(),
};
instruction_trace.push(instruction_context);
}
instruction_trace.len().saturating_sub(1)
} else { } else {
return Err(InstructionError::CallDepth); if self.is_early_verification_of_account_modifications_enabled() {
let caller_instruction_context = self.get_current_instruction_context()?;
let original_caller_instruction_accounts_lamport_sum =
caller_instruction_context.instruction_accounts_lamport_sum;
let current_caller_instruction_accounts_lamport_sum = self
.instruction_accounts_lamport_sum(
&caller_instruction_context.instruction_accounts,
)?;
if original_caller_instruction_accounts_lamport_sum
!= current_caller_instruction_accounts_lamport_sum
{
return Err(InstructionError::UnbalancedInstruction);
}
}
if let Some(instruction_trace) = self.instruction_trace.last_mut() {
if record_instruction_in_transaction_context_push {
let instruction_context = InstructionContext {
nesting_level: self.instruction_stack.len(),
instruction_accounts_lamport_sum: callee_instruction_accounts_lamport_sum,
program_accounts: program_accounts.to_vec(),
instruction_accounts: instruction_accounts.to_vec(),
instruction_data: instruction_data.to_vec(),
};
instruction_trace.push(instruction_context);
}
instruction_trace.len().saturating_sub(1)
} else {
return Err(InstructionError::CallDepth);
}
}; };
if self.instruction_stack.len() >= self.instruction_context_capacity { if self.instruction_stack.len() >= self.instruction_context_capacity {
return Err(InstructionError::CallDepth); return Err(InstructionError::CallDepth);
@ -218,8 +251,34 @@ impl TransactionContext {
if self.instruction_stack.is_empty() { if self.instruction_stack.is_empty() {
return Err(InstructionError::CallDepth); return Err(InstructionError::CallDepth);
} }
// Verify (before we pop) that the total sum of all lamports in this instruction did not change
let detected_an_unbalanced_instruction = if self
.is_early_verification_of_account_modifications_enabled()
{
self.get_current_instruction_context()
.and_then(|instruction_context| {
// Verify all executable accounts have no outstanding refs
for account_index in instruction_context.program_accounts.iter() {
self.get_account_at_index(*account_index)?
.try_borrow_mut()
.map_err(|_| InstructionError::AccountBorrowOutstanding)?;
}
self.instruction_accounts_lamport_sum(&instruction_context.instruction_accounts)
.map(|instruction_accounts_lamport_sum| {
instruction_context.instruction_accounts_lamport_sum
!= instruction_accounts_lamport_sum
})
})
} else {
Ok(false)
};
// Always pop, even if we `detected_an_unbalanced_instruction`
self.instruction_stack.pop(); self.instruction_stack.pop();
Ok(()) if detected_an_unbalanced_instruction? {
Err(InstructionError::UnbalancedInstruction)
} else {
Ok(())
}
} }
/// Gets the return data of the current InstructionContext or any above /// Gets the return data of the current InstructionContext or any above
@ -251,6 +310,40 @@ impl TransactionContext {
pub fn get_instruction_trace(&self) -> &InstructionTrace { pub fn get_instruction_trace(&self) -> &InstructionTrace {
&self.instruction_trace &self.instruction_trace
} }
/// Calculates the sum of all lamports within an instruction
pub fn instruction_accounts_lamport_sum(
&self,
instruction_accounts: &[InstructionAccount],
) -> Result<u128, InstructionError> {
if !self.is_early_verification_of_account_modifications_enabled() {
return Ok(0);
}
let mut instruction_accounts_lamport_sum: u128 = 0;
for (instruction_account_index, instruction_account) in
instruction_accounts.iter().enumerate()
{
if instruction_account_index != instruction_account.index_in_callee {
continue; // Skip duplicate account
}
instruction_accounts_lamport_sum = (self
.get_account_at_index(instruction_account.index_in_transaction)?
.try_borrow()
.map_err(|_| InstructionError::AccountBorrowOutstanding)?
.lamports() as u128)
.checked_add(instruction_accounts_lamport_sum)
.ok_or(InstructionError::ArithmeticOverflow)?;
}
Ok(instruction_accounts_lamport_sum)
}
/// Returns the accounts resize delta
pub fn accounts_resize_delta(&self) -> Result<i64, InstructionError> {
self.accounts_resize_delta
.try_borrow()
.map_err(|_| InstructionError::GenericError)
.map(|value_ref| *value_ref)
}
} }
/// Return data at the end of a transaction /// Return data at the end of a transaction
@ -269,6 +362,7 @@ pub type InstructionTrace = Vec<Vec<InstructionContext>>;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct InstructionContext { pub struct InstructionContext {
nesting_level: usize, nesting_level: usize,
instruction_accounts_lamport_sum: u128,
program_accounts: Vec<usize>, program_accounts: Vec<usize>,
instruction_accounts: Vec<InstructionAccount>, instruction_accounts: Vec<InstructionAccount>,
instruction_data: Vec<u8>, instruction_data: Vec<u8>,
@ -278,12 +372,14 @@ impl InstructionContext {
/// New /// New
pub fn new( pub fn new(
nesting_level: usize, nesting_level: usize,
instruction_accounts_lamport_sum: u128,
program_accounts: &[usize], program_accounts: &[usize],
instruction_accounts: &[InstructionAccount], instruction_accounts: &[InstructionAccount],
instruction_data: &[u8], instruction_data: &[u8],
) -> Self { ) -> Self {
InstructionContext { InstructionContext {
nesting_level, nesting_level,
instruction_accounts_lamport_sum,
program_accounts: program_accounts.to_vec(), program_accounts: program_accounts.to_vec(),
instruction_accounts: instruction_accounts.to_vec(), instruction_accounts: instruction_accounts.to_vec(),
instruction_data: instruction_data.to_vec(), instruction_data: instruction_data.to_vec(),
@ -540,6 +636,32 @@ impl<'a> BorrowedAccount<'a> {
/// Assignes the owner of this account (transaction wide) /// Assignes the owner of this account (transaction wide)
pub fn set_owner(&mut self, pubkey: &[u8]) -> Result<(), InstructionError> { pub fn set_owner(&mut self, pubkey: &[u8]) -> Result<(), InstructionError> {
if self
.transaction_context
.is_early_verification_of_account_modifications_enabled()
{
// Only the owner can assign a new owner
if !self.is_owned_by_current_program() {
return Err(InstructionError::ModifiedProgramId);
}
// and only if the account is writable
if !self.is_writable() {
return Err(InstructionError::ModifiedProgramId);
}
// and only if the account is not executable
if self.is_executable() {
return Err(InstructionError::ModifiedProgramId);
}
// and only if the data is zero-initialized or empty
if !is_zeroed(self.get_data()) {
return Err(InstructionError::ModifiedProgramId);
}
// don't touch the account if the owner does not change
if self.get_owner().to_bytes() == pubkey {
return Ok(());
}
self.touch()?;
}
self.account.copy_into_owner_from_slice(pubkey); self.account.copy_into_owner_from_slice(pubkey);
Ok(()) Ok(())
} }
@ -551,6 +673,28 @@ impl<'a> BorrowedAccount<'a> {
/// Overwrites the number of lamports of this account (transaction wide) /// Overwrites the number of lamports of this account (transaction wide)
pub fn set_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> { pub fn set_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> {
if self
.transaction_context
.is_early_verification_of_account_modifications_enabled()
{
// An account not owned by the program cannot have its balance decrease
if !self.is_owned_by_current_program() && lamports < self.get_lamports() {
return Err(InstructionError::ExternalAccountLamportSpend);
}
// The balance of read-only may not change
if !self.is_writable() {
return Err(InstructionError::ReadonlyLamportChange);
}
// The balance of executable accounts may not change
if self.is_executable() {
return Err(InstructionError::ExecutableLamportChange);
}
// don't touch the account if the lamports do not change
if self.get_lamports() == lamports {
return Ok(());
}
self.touch()?;
}
self.account.set_lamports(lamports); self.account.set_lamports(lamports);
Ok(()) Ok(())
} }
@ -580,16 +724,25 @@ impl<'a> BorrowedAccount<'a> {
/// Returns a writable slice of the account data (transaction wide) /// Returns a writable slice of the account data (transaction wide)
pub fn get_data_mut(&mut self) -> Result<&mut [u8], InstructionError> { pub fn get_data_mut(&mut self) -> Result<&mut [u8], InstructionError> {
self.can_data_be_changed()?;
self.touch()?;
Ok(self.account.data_as_mut_slice()) Ok(self.account.data_as_mut_slice())
} }
/// Overwrites the account data and size (transaction wide) /// Overwrites the account data and size (transaction wide)
pub fn set_data(&mut self, data: &[u8]) -> Result<(), InstructionError> { pub fn set_data(&mut self, data: &[u8]) -> Result<(), InstructionError> {
self.can_data_be_resized(data.len())?;
self.can_data_be_changed()?;
self.touch()?;
if data.len() == self.account.data().len() { if data.len() == self.account.data().len() {
self.account.data_as_mut_slice().copy_from_slice(data); self.account.data_as_mut_slice().copy_from_slice(data);
} else { } else {
let mut total_resize_delta = self.transaction_context.total_resize_delta.borrow_mut(); let mut accounts_resize_delta = self
*total_resize_delta = total_resize_delta .transaction_context
.accounts_resize_delta
.try_borrow_mut()
.map_err(|_| InstructionError::GenericError)?;
*accounts_resize_delta = accounts_resize_delta
.saturating_add((data.len() as i64).saturating_sub(self.get_data().len() as i64)); .saturating_add((data.len() as i64).saturating_sub(self.get_data().len() as i64));
self.account.set_data_from_slice(data); self.account.set_data_from_slice(data);
} }
@ -600,8 +753,19 @@ impl<'a> BorrowedAccount<'a> {
/// ///
/// Fills it with zeros at the end if is extended or truncates at the end otherwise. /// Fills it with zeros at the end if is extended or truncates at the end otherwise.
pub fn set_data_length(&mut self, new_length: usize) -> Result<(), InstructionError> { pub fn set_data_length(&mut self, new_length: usize) -> Result<(), InstructionError> {
let mut total_resize_delta = self.transaction_context.total_resize_delta.borrow_mut(); self.can_data_be_resized(new_length)?;
*total_resize_delta = total_resize_delta self.can_data_be_changed()?;
// don't touch the account if the length does not change
if self.get_data().len() == new_length {
return Ok(());
}
self.touch()?;
let mut accounts_resize_delta = self
.transaction_context
.accounts_resize_delta
.try_borrow_mut()
.map_err(|_| InstructionError::GenericError)?;
*accounts_resize_delta = accounts_resize_delta
.saturating_add((new_length as i64).saturating_sub(self.get_data().len() as i64)); .saturating_add((new_length as i64).saturating_sub(self.get_data().len() as i64));
self.account.data_mut().resize(new_length, 0); self.account.data_mut().resize(new_length, 0);
Ok(()) Ok(())
@ -616,7 +780,7 @@ impl<'a> BorrowedAccount<'a> {
/// Serializes a state into the account data /// Serializes a state into the account data
pub fn set_state<T: serde::Serialize>(&mut self, state: &T) -> Result<(), InstructionError> { pub fn set_state<T: serde::Serialize>(&mut self, state: &T) -> Result<(), InstructionError> {
let data = self.account.data_as_mut_slice(); let data = self.get_data_mut()?;
let serialized_size = let serialized_size =
bincode::serialized_size(state).map_err(|_| InstructionError::GenericError)?; bincode::serialized_size(state).map_err(|_| InstructionError::GenericError)?;
if serialized_size > data.len() as u64 { if serialized_size > data.len() as u64 {
@ -633,6 +797,29 @@ impl<'a> BorrowedAccount<'a> {
/// Configures whether this account is executable (transaction wide) /// Configures whether this account is executable (transaction wide)
pub fn set_executable(&mut self, is_executable: bool) -> Result<(), InstructionError> { pub fn set_executable(&mut self, is_executable: bool) -> Result<(), InstructionError> {
if let Some(rent) = self.transaction_context.rent {
// To become executable an account must be rent exempt
if !rent.is_exempt(self.get_lamports(), self.get_data().len()) {
return Err(InstructionError::ExecutableAccountNotRentExempt);
}
// Only the owner can set the executable flag
if !self.is_owned_by_current_program() {
return Err(InstructionError::ExecutableModified);
}
// and only if the account is writable
if !self.is_writable() {
return Err(InstructionError::ExecutableModified);
}
// one can not clear the executable flag
if self.is_executable() && !is_executable {
return Err(InstructionError::ExecutableModified);
}
// don't touch the account if the executable flag does not change
if self.is_executable() == is_executable {
return Ok(());
}
self.touch()?;
}
self.account.set_executable(is_executable); self.account.set_executable(is_executable);
Ok(()) Ok(())
} }
@ -667,6 +854,72 @@ impl<'a> BorrowedAccount<'a> {
) )
.unwrap_or_default() .unwrap_or_default()
} }
/// Returns true if the owner of this account is the current `InstructionContext`s last program (instruction wide)
pub fn is_owned_by_current_program(&self) -> bool {
self.instruction_context
.get_last_program_key(self.transaction_context)
.map(|key| key == self.get_owner())
.unwrap_or_default()
}
/// Returns an error if the account data can not be mutated by the current program
pub fn can_data_be_changed(&self) -> Result<(), InstructionError> {
if !self
.transaction_context
.is_early_verification_of_account_modifications_enabled()
{
return Ok(());
}
// Only non-executable accounts data can be changed
if self.is_executable() {
return Err(InstructionError::ExecutableDataModified);
}
// and only if the account is writable
if !self.is_writable() {
return Err(InstructionError::ReadonlyDataModified);
}
// and only if we are the owner
if !self.is_owned_by_current_program() {
return Err(InstructionError::ExternalAccountDataModified);
}
Ok(())
}
/// Returns an error if the account data can not be resized to the given length
pub fn can_data_be_resized(&self, new_length: usize) -> Result<(), InstructionError> {
if !self
.transaction_context
.is_early_verification_of_account_modifications_enabled()
{
return Ok(());
}
// Only the owner can change the length of the data
if new_length != self.get_data().len() && !self.is_owned_by_current_program() {
return Err(InstructionError::AccountDataSizeChanged);
}
// The new length can not exceed the maximum permitted length
if new_length > MAX_PERMITTED_DATA_LENGTH as usize {
return Err(InstructionError::InvalidRealloc);
}
Ok(())
}
fn touch(&self) -> Result<(), InstructionError> {
if self
.transaction_context
.is_early_verification_of_account_modifications_enabled()
{
*self
.transaction_context
.account_touched_flags
.try_borrow_mut()
.map_err(|_| InstructionError::GenericError)?
.get_mut(self.index_in_transaction)
.ok_or(InstructionError::NotEnoughAccountKeys)? = true;
}
Ok(())
}
} }
/// Everything that needs to be recorded from a TransactionContext after execution /// Everything that needs to be recorded from a TransactionContext after execution
@ -674,12 +927,38 @@ pub struct ExecutionRecord {
pub accounts: Vec<TransactionAccount>, pub accounts: Vec<TransactionAccount>,
pub instruction_trace: InstructionTrace, pub instruction_trace: InstructionTrace,
pub return_data: TransactionReturnData, pub return_data: TransactionReturnData,
pub total_resize_delta: i64, pub changed_account_count: u64,
pub total_size_of_all_accounts: u64,
pub total_size_of_touched_accounts: u64,
pub accounts_resize_delta: i64,
} }
/// Used by the bank in the runtime to write back the processed accounts and recorded instructions /// Used by the bank in the runtime to write back the processed accounts and recorded instructions
impl From<TransactionContext> for ExecutionRecord { impl From<TransactionContext> for ExecutionRecord {
fn from(context: TransactionContext) -> Self { fn from(context: TransactionContext) -> Self {
let mut changed_account_count = 0u64;
let mut total_size_of_all_accounts = 0u64;
let mut total_size_of_touched_accounts = 0u64;
let account_touched_flags = context
.account_touched_flags
.try_borrow()
.expect("borrowing transaction_context.account_touched_flags failed");
for (index_in_transaction, was_touched) in account_touched_flags.iter().enumerate() {
let account_data_size = context
.get_account_at_index(index_in_transaction)
.expect("index_in_transaction out of bounds")
.try_borrow()
.expect("borrowing a transaction_context.account failed")
.data()
.len() as u64;
total_size_of_all_accounts =
total_size_of_all_accounts.saturating_add(account_data_size);
if *was_touched {
changed_account_count = changed_account_count.saturating_add(1);
total_size_of_touched_accounts =
total_size_of_touched_accounts.saturating_add(account_data_size);
}
}
Self { Self {
accounts: Vec::from(Pin::into_inner(context.account_keys)) accounts: Vec::from(Pin::into_inner(context.account_keys))
.into_iter() .into_iter()
@ -691,7 +970,22 @@ impl From<TransactionContext> for ExecutionRecord {
.collect(), .collect(),
instruction_trace: context.instruction_trace, instruction_trace: context.instruction_trace,
return_data: context.return_data, return_data: context.return_data,
total_resize_delta: RefCell::into_inner(context.total_resize_delta), changed_account_count,
total_size_of_all_accounts,
total_size_of_touched_accounts,
accounts_resize_delta: RefCell::into_inner(context.accounts_resize_delta),
} }
} }
} }
fn is_zeroed(buf: &[u8]) -> bool {
const ZEROS_LEN: usize = 1024;
const ZEROS: [u8; ZEROS_LEN] = [0; ZEROS_LEN];
let mut chunks = buf.chunks_exact(ZEROS_LEN);
#[allow(clippy::indexing_slicing)]
{
chunks.all(|chunk| chunk == &ZEROS[..])
&& chunks.remainder() == &ZEROS[..chunks.remainder().len()]
}
}