diff --git a/Cargo.lock b/Cargo.lock index d6e98b61c054af..96d77513b936d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -385,6 +385,7 @@ dependencies = [ "solana-timings", "solana-transaction-context", "solana-type-overrides", + "static_assertions", "test-case", "thiserror 2.0.12", ] @@ -9622,7 +9623,6 @@ dependencies = [ "assert_matches", "base64 0.22.1", "bincode", - "enum-iterator", "itertools 0.12.1", "log", "percentage", @@ -9638,6 +9638,7 @@ dependencies = [ "solana-frozen-abi-macro", "solana-hash", "solana-instruction", + "solana-keypair", "solana-last-restart-slot", "solana-log-collector", "solana-measure", @@ -9648,8 +9649,8 @@ dependencies = [ "solana-rent", "solana-sbpf", "solana-sdk-ids", + "solana-signer", "solana-slot-hashes", - "solana-stable-layout", "solana-svm-callback", "solana-svm-feature-set", "solana-svm-transaction", @@ -9657,10 +9658,10 @@ dependencies = [ "solana-sysvar", "solana-sysvar-id", "solana-timings", + "solana-transaction", "solana-transaction-context", "solana-type-overrides", "test-case", - "thiserror 2.0.12", ] [[package]] diff --git a/ledger-tool/src/program.rs b/ledger-tool/src/program.rs index 47040df6b5b2fb..67b78a392a5a2c 100644 --- a/ledger-tool/src/program.rs +++ b/ledger-tool/src/program.rs @@ -408,7 +408,7 @@ pub fn program(ledger_path: &Path, matches: &ArgMatches<'_>) { pubkey, AccountSharedData::new(0, allocation_size, &Pubkey::new_unique()), )); - instruction_accounts.push(InstructionAccount::new(0, 0, false, true)); + instruction_accounts.push(InstructionAccount::new(0, false, true)); vec![] } Err(_) => { @@ -480,7 +480,6 @@ pub fn program(ledger_path: &Path, matches: &ArgMatches<'_>) { idx }; InstructionAccount::new( - txn_acct_index as IndexOfAccount, txn_acct_index as IndexOfAccount, account_info.is_signer.unwrap_or(false), account_info.is_writable.unwrap_or(false), @@ -523,7 +522,7 @@ pub fn program(ledger_path: &Path, matches: &ArgMatches<'_>) { .transaction_context .get_next_instruction_context_mut() .unwrap() - .configure( + .configure_for_tests( vec![program_index, program_index.saturating_add(1)], instruction_accounts, &instruction_data, diff --git a/program-runtime/Cargo.toml b/program-runtime/Cargo.toml index a74e780b573078..62b3cf9781c258 100644 --- a/program-runtime/Cargo.toml +++ b/program-runtime/Cargo.toml @@ -26,7 +26,6 @@ shuttle-test = ["solana-type-overrides/shuttle-test", "solana-sbpf/shuttle-test" [dependencies] base64 = { workspace = true } bincode = { workspace = true } -enum-iterator = { workspace = true } itertools = { workspace = true } log = { workspace = true } percentage = { workspace = true } @@ -55,7 +54,6 @@ solana-rent = { workspace = true } solana-sbpf = { workspace = true } solana-sdk-ids = { workspace = true } solana-slot-hashes = { workspace = true } -solana-stable-layout = { workspace = true } solana-svm-callback = { workspace = true } solana-svm-feature-set = { workspace = true } solana-svm-transaction = { workspace = true } @@ -65,14 +63,16 @@ solana-sysvar-id = { workspace = true } solana-timings = { workspace = true } solana-transaction-context = { workspace = true } solana-type-overrides = { workspace = true } -thiserror = { workspace = true } [dev-dependencies] assert_matches = { workspace = true } solana-account-info = { workspace = true } solana-instruction = { workspace = true, features = ["bincode"] } +solana-keypair = { workspace = true } solana-program-runtime = { path = ".", features = ["dev-context-only-utils"] } solana-pubkey = { workspace = true, features = ["rand"] } +solana-signer = { workspace = true } +solana-transaction = { workspace = true, features = ["dev-context-only-utils"] } solana-transaction-context = { workspace = true, features = [ "dev-context-only-utils", ] } diff --git a/program-runtime/src/invoke_context.rs b/program-runtime/src/invoke_context.rs index b4ca250f84ed14..3aebb398cf33ee 100644 --- a/program-runtime/src/invoke_context.rs +++ b/program-runtime/src/invoke_context.rs @@ -32,6 +32,7 @@ use { solana_timings::{ExecuteDetailsTimings, ExecuteTimings}, solana_transaction_context::{ IndexOfAccount, InstructionAccount, TransactionAccount, TransactionContext, + MAX_ACCOUNTS_PER_TRANSACTION, }, solana_type_overrides::sync::{atomic::Ordering, Arc}, std::{ @@ -323,7 +324,7 @@ impl<'a> InvokeContext<'a> { // We reference accounts by an u8 index, so we have a total of 256 accounts. // This algorithm allocates the array on the stack for speed. // On AArch64 in release mode, this function only consumes 640 bytes of stack. - let mut transaction_callee_map: [u8; 256] = [u8::MAX; 256]; + let mut transaction_callee_map: Vec = vec![u8::MAX; MAX_ACCOUNTS_PER_TRANSACTION]; let mut instruction_accounts: Vec = Vec::with_capacity(instruction.accounts.len()); @@ -332,10 +333,9 @@ impl<'a> InvokeContext<'a> { // function, we must borrow it again as mutable. let program_account_index = { let instruction_context = self.transaction_context.get_current_instruction_context()?; - debug_assert!(instruction.accounts.len() <= u8::MAX as usize); + debug_assert!(instruction.accounts.len() <= transaction_callee_map.len()); - for (instruction_account_index, account_meta) in instruction.accounts.iter().enumerate() - { + for account_meta in instruction.accounts.iter() { let index_in_transaction = self .transaction_context .find_index_of_account(&account_meta.pubkey) @@ -371,7 +371,6 @@ impl<'a> InvokeContext<'a> { *index_in_callee = instruction_accounts.len() as u8; instruction_accounts.push(InstructionAccount::new( index_in_transaction, - instruction_account_index as IndexOfAccount, account_meta.is_signer, account_meta.is_writable, )); @@ -380,11 +379,14 @@ impl<'a> InvokeContext<'a> { for current_index in 0..instruction_accounts.len() { let instruction_account = instruction_accounts.get(current_index).unwrap(); + let index_in_callee = *transaction_callee_map + .get(instruction_account.index_in_transaction as usize) + .unwrap() as usize; - if current_index != instruction_account.index_in_callee as usize { + if current_index != index_in_callee { let (is_signer, is_writable) = { let reference_account = instruction_accounts - .get(instruction_account.index_in_callee as usize) + .get(index_in_callee) .ok_or(InstructionError::NotEnoughAccountKeys)?; ( reference_account.is_signer(), @@ -458,6 +460,7 @@ impl<'a> InvokeContext<'a> { .configure( vec![program_account_index], instruction_accounts, + transaction_callee_map, &instruction.data, ); Ok(()) @@ -475,8 +478,8 @@ impl<'a> InvokeContext<'a> { // This algorithm allocates the array on the stack for speed. // On AArch64 in release mode, this function only consumes 464 bytes of stack (when it is // not inlined). - let mut transaction_callee_map: [u8; 256] = [u8::MAX; 256]; - debug_assert!(instruction.accounts.len() <= u8::MAX as usize); + let mut transaction_callee_map: Vec = vec![u8::MAX; MAX_ACCOUNTS_PER_TRANSACTION]; + debug_assert!(instruction.accounts.len() <= transaction_callee_map.len()); let mut instruction_accounts: Vec = Vec::with_capacity(instruction.accounts.len()); @@ -494,7 +497,6 @@ impl<'a> InvokeContext<'a> { let index_in_transaction = *index_in_transaction as usize; instruction_accounts.push(InstructionAccount::new( index_in_transaction as IndexOfAccount, - *index_in_callee as IndexOfAccount, message.is_signer(index_in_transaction), message.is_writable(index_in_transaction), )); @@ -502,7 +504,12 @@ impl<'a> InvokeContext<'a> { self.transaction_context .get_next_instruction_context_mut()? - .configure(program_indices, instruction_accounts, instruction.data); + .configure( + program_indices, + instruction_accounts, + transaction_callee_map, + instruction.data, + ); Ok(()) } @@ -874,23 +881,14 @@ pub fn mock_process_instruction_with_feature_set< ) -> Vec { let mut instruction_accounts: Vec = Vec::with_capacity(instruction_account_metas.len()); - for (instruction_account_index, account_meta) in instruction_account_metas.iter().enumerate() { + for account_meta in instruction_account_metas.iter() { let index_in_transaction = transaction_accounts .iter() .position(|(key, _account)| *key == account_meta.pubkey) .unwrap_or(transaction_accounts.len()) as IndexOfAccount; - let index_in_callee = instruction_accounts - .get(0..instruction_account_index) - .unwrap() - .iter() - .position(|instruction_account| { - instruction_account.index_in_transaction == index_in_transaction - }) - .unwrap_or(instruction_account_index) as IndexOfAccount; instruction_accounts.push(InstructionAccount::new( index_in_transaction, - index_in_callee, account_meta.is_signer, account_meta.is_writable, )); @@ -929,7 +927,7 @@ pub fn mock_process_instruction_with_feature_set< .transaction_context .get_next_instruction_context_mut() .unwrap() - .configure(program_indices, instruction_accounts, instruction_data); + .configure_for_tests(program_indices, instruction_accounts, instruction_data); let result = invoke_context.process_instruction(&mut 0, &mut ExecuteTimings::default()); assert_eq!(result, expected_result); post_adjustments(&mut invoke_context); @@ -974,7 +972,11 @@ mod tests { serde::{Deserialize, Serialize}, solana_account::WritableAccount, solana_instruction::Instruction, + solana_keypair::Keypair, solana_rent::Rent, + solana_signer::Signer, + solana_transaction::{sanitized::SanitizedTransaction, Transaction}, + std::collections::HashSet, test_case::test_case, }; @@ -1008,12 +1010,7 @@ mod tests { let program_id = instruction_context.get_last_program_key(transaction_context)?; let instruction_accounts = (0..4) .map(|instruction_account_index| { - InstructionAccount::new( - instruction_account_index, - instruction_account_index, - false, - false, - ) + InstructionAccount::new(instruction_account_index, false, false) }) .collect::>(); assert_eq!( @@ -1068,7 +1065,7 @@ mod tests { .transaction_context .get_next_instruction_context_mut() .unwrap() - .configure(vec![3], instruction_accounts, &[]); + .configure_for_tests(vec![3], instruction_accounts, &[]); let result = invoke_context.push(); assert_eq!(result, Err(InstructionError::UnbalancedInstruction)); result?; @@ -1117,7 +1114,6 @@ mod tests { )); instruction_accounts.push(InstructionAccount::new( index as IndexOfAccount, - instruction_accounts.len() as IndexOfAccount, false, true, )); @@ -1128,7 +1124,6 @@ mod tests { AccountSharedData::new(1, 1, &solana_pubkey::Pubkey::default()), )); instruction_accounts.push(InstructionAccount::new( - index as IndexOfAccount, index as IndexOfAccount, false, false, @@ -1143,7 +1138,7 @@ mod tests { .transaction_context .get_next_instruction_context_mut() .unwrap() - .configure( + .configure_for_tests( vec![one_more_than_max_depth.saturating_add(depth_reached) as IndexOfAccount], instruction_accounts.clone(), &[], @@ -1205,7 +1200,6 @@ mod tests { let instruction_accounts = (0..4) .map(|instruction_account_index| { InstructionAccount::new( - instruction_account_index, instruction_account_index, false, instruction_account_index < 2, @@ -1225,7 +1219,7 @@ mod tests { .transaction_context .get_next_instruction_context_mut() .unwrap() - .configure(vec![4], instruction_accounts, &[]); + .configure_for_tests(vec![4], instruction_accounts, &[]); invoke_context.push().unwrap(); let inner_instruction = Instruction::new_with_bincode(callee_program_id, &instruction, metas.clone()); @@ -1262,7 +1256,6 @@ mod tests { let instruction_accounts = (0..4) .map(|instruction_account_index| { InstructionAccount::new( - instruction_account_index, instruction_account_index, false, instruction_account_index < 2, @@ -1283,7 +1276,7 @@ mod tests { .transaction_context .get_next_instruction_context_mut() .unwrap() - .configure(vec![4], instruction_accounts, &[]); + .configure_for_tests(vec![4], instruction_accounts, &[]); invoke_context.push().unwrap(); let inner_instruction = Instruction::new_with_bincode( callee_program_id, @@ -1329,7 +1322,7 @@ mod tests { .transaction_context .get_next_instruction_context_mut() .unwrap() - .configure(vec![0], vec![], &[]); + .configure_for_tests(vec![0], vec![], &[]); invoke_context.push().unwrap(); assert_eq!(*invoke_context.get_compute_budget(), execution_budget); invoke_context.pop().unwrap(); @@ -1352,8 +1345,8 @@ mod tests { (program_key, program_account), ]; let instruction_accounts = vec![ - InstructionAccount::new(0, 0, false, true), - InstructionAccount::new(1, 1, false, false), + InstructionAccount::new(0, false, true), + InstructionAccount::new(1, false, false), ]; with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts); let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::default(); @@ -1370,7 +1363,7 @@ mod tests { .transaction_context .get_next_instruction_context_mut() .unwrap() - .configure(vec![2], instruction_accounts, &instruction_data); + .configure_for_tests(vec![2], instruction_accounts, &instruction_data); let result = invoke_context.process_instruction(&mut 0, &mut ExecuteTimings::default()); assert!(result.is_ok()); @@ -1382,4 +1375,208 @@ mod tests { resize_delta ); } + + #[test] + fn test_prepare_instruction_maximum_accounts() { + let mut transaction_accounts: Vec = + Vec::with_capacity(MAX_ACCOUNTS_PER_TRANSACTION); + let mut account_metas: Vec = Vec::with_capacity(MAX_ACCOUNTS_PER_TRANSACTION); + + // Fee-payer + let fee_payer = Keypair::new(); + transaction_accounts.push(( + fee_payer.pubkey(), + AccountSharedData::new(1, 1, &Pubkey::new_unique()), + )); + account_metas.push(AccountMeta::new(fee_payer.pubkey(), true)); + + let program_id = Pubkey::new_unique(); + let mut program_account = AccountSharedData::new(1, 1, &Pubkey::new_unique()); + program_account.set_executable(true); + transaction_accounts.push((program_id, program_account)); + account_metas.push(AccountMeta::new_readonly(program_id, false)); + + for _ in 2..256 { + let key = Pubkey::new_unique(); + transaction_accounts.push((key, AccountSharedData::new(1, 1, &Pubkey::new_unique()))); + account_metas.push(AccountMeta::new_readonly(key, false)); + } + + with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts); + + let instruction_1 = Instruction::new_with_bytes(program_id, &[20], account_metas.clone()); + + let instruction_2 = Instruction::new_with_bytes( + program_id, + &[20], + account_metas.iter().rev().cloned().collect(), + ); + + let transaction = Transaction::new_with_payer( + &[instruction_1.clone(), instruction_2.clone()], + Some(&fee_payer.pubkey()), + ); + + let sanitized = + SanitizedTransaction::try_from_legacy_transaction(transaction, &HashSet::new()) + .unwrap(); + + fn test_case_1(invoke_context: &InvokeContext) { + let instruction_context = invoke_context + .transaction_context + .get_next_instruction_context() + .unwrap(); + for index_in_transaction in 0..256 { + let index_in_instruction = instruction_context + .get_index_of_account_in_instruction(index_in_transaction as IndexOfAccount) + .unwrap(); + let other_transaction = instruction_context + .get_index_of_instruction_account_in_transaction(index_in_instruction) + .unwrap(); + assert_eq!(index_in_transaction, other_transaction); + assert_eq!(index_in_transaction, index_in_instruction); + } + } + + fn test_case_2(invoke_context: &InvokeContext) { + let instruction_context = invoke_context + .transaction_context + .get_next_instruction_context() + .unwrap(); + for index_in_transaction in 0..256u16 { + let index_in_instruction = instruction_context + .get_index_of_account_in_instruction(index_in_transaction as IndexOfAccount) + .unwrap(); + let other_transaction = instruction_context + .get_index_of_instruction_account_in_transaction(index_in_instruction) + .unwrap(); + assert_eq!( + index_in_instruction, + 255u16.saturating_sub(index_in_transaction) + ); + assert_eq!(index_in_transaction, other_transaction); + } + } + + let svm_instruction = + SVMInstruction::from(sanitized.message().instructions().first().unwrap()); + invoke_context + .prepare_next_top_level_instruction(&sanitized, &svm_instruction, vec![90]) + .unwrap(); + + test_case_1(&invoke_context); + + invoke_context.transaction_context.push().unwrap(); + let svm_instruction = + SVMInstruction::from(sanitized.message().instructions().get(1).unwrap()); + invoke_context + .prepare_next_top_level_instruction(&sanitized, &svm_instruction, vec![90]) + .unwrap(); + + test_case_2(&invoke_context); + + invoke_context.transaction_context.push().unwrap(); + invoke_context + .prepare_next_instruction(&instruction_1, &[fee_payer.pubkey()]) + .unwrap(); + test_case_1(&invoke_context); + + invoke_context.transaction_context.push().unwrap(); + invoke_context + .prepare_next_instruction(&instruction_2, &[fee_payer.pubkey()]) + .unwrap(); + test_case_2(&invoke_context); + } + + #[test] + fn test_duplicated_accounts() { + let mut transaction_accounts: Vec = + Vec::with_capacity(MAX_ACCOUNTS_PER_TRANSACTION); + let mut account_metas: Vec = Vec::with_capacity(MAX_ACCOUNTS_PER_TRANSACTION); + + // Fee-payer + let fee_payer = Keypair::new(); + transaction_accounts.push(( + fee_payer.pubkey(), + AccountSharedData::new(1, 1, &Pubkey::new_unique()), + )); + account_metas.push(AccountMeta::new(fee_payer.pubkey(), true)); + + let program_id = Pubkey::new_unique(); + let mut program_account = AccountSharedData::new(1, 1, &Pubkey::new_unique()); + program_account.set_executable(true); + transaction_accounts.push((program_id, program_account)); + account_metas.push(AccountMeta::new_readonly(program_id, false)); + + for i in 2..256 { + if i % 2 == 0 { + let key = Pubkey::new_unique(); + transaction_accounts + .push((key, AccountSharedData::new(1, 1, &Pubkey::new_unique()))); + account_metas.push(AccountMeta::new_readonly(key, false)); + } else { + let last_key = transaction_accounts.last().unwrap().0; + account_metas.push(AccountMeta::new_readonly(last_key, false)); + } + } + + with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts); + + let instruction = Instruction::new_with_bytes(program_id, &[20], account_metas.clone()); + + let transaction = Transaction::new_with_payer(&[instruction], Some(&fee_payer.pubkey())); + + let sanitized = + SanitizedTransaction::try_from_legacy_transaction(transaction, &HashSet::new()) + .unwrap(); + let svm_instruction = + SVMInstruction::from(sanitized.message().instructions().first().unwrap()); + + invoke_context + .prepare_next_top_level_instruction(&sanitized, &svm_instruction, vec![90]) + .unwrap(); + + { + let instruction_context = invoke_context + .transaction_context + .get_next_instruction_context() + .unwrap(); + for index_in_instruction in 2..256u16 { + let is_duplicate = instruction_context + .is_instruction_account_duplicate(index_in_instruction) + .unwrap(); + if index_in_instruction % 2 == 0 { + assert!(is_duplicate.is_none()); + } else { + assert_eq!(is_duplicate, Some(index_in_instruction.saturating_sub(1))); + } + } + } + + invoke_context.transaction_context.push().unwrap(); + + let instruction = Instruction::new_with_bytes( + program_id, + &[20], + account_metas.iter().cloned().rev().collect(), + ); + + invoke_context + .prepare_next_instruction(&instruction, &[fee_payer.pubkey()]) + .unwrap(); + let instruction_context = invoke_context + .transaction_context + .get_next_instruction_context() + .unwrap(); + for index_in_instruction in 1..254u16 { + let is_duplicate = instruction_context + .is_instruction_account_duplicate(index_in_instruction) + .unwrap(); + if index_in_instruction % 2 == 0 { + assert!(is_duplicate.is_none()); + } else { + assert_eq!(is_duplicate, Some(index_in_instruction.saturating_sub(1))); + } + } + } } diff --git a/program-runtime/src/serialization.rs b/program-runtime/src/serialization.rs index c1e263ef51ed5d..e0da4d3c3a5640 100644 --- a/program-runtime/src/serialization.rs +++ b/program-runtime/src/serialization.rs @@ -624,15 +624,8 @@ mod tests { .iter() .enumerate() .map(|(index_in_instruction, index_in_transaction)| { - let index_in_callee = transaction_indexes - .get(0..index_in_instruction) - .unwrap() - .iter() - .position(|account_index| account_index == index_in_transaction) - .unwrap_or(index_in_instruction); InstructionAccount::new( *index_in_transaction, - index_in_callee as IndexOfAccount, false, is_writable(index_in_instruction), ) @@ -700,7 +693,7 @@ mod tests { } let transaction_accounts_indexes: Vec = - (1..(num_ix_accounts + 1) as u16).collect(); + (0..num_ix_accounts as u16).collect(); let mut instruction_accounts = deduplicated_instruction_accounts(&transaction_accounts_indexes, |_| false); if append_dup_account { @@ -718,7 +711,7 @@ mod tests { .transaction_context .get_next_instruction_context_mut() .unwrap() - .configure(program_indices, instruction_accounts, &instruction_data); + .configure_for_tests(program_indices, instruction_accounts, &instruction_data); invoke_context.push().unwrap(); let instruction_context = invoke_context .transaction_context @@ -861,7 +854,7 @@ mod tests { .transaction_context .get_next_instruction_context_mut() .unwrap() - .configure(program_indices, instruction_accounts, &instruction_data); + .configure_for_tests(program_indices, instruction_accounts, &instruction_data); invoke_context.push().unwrap(); let instruction_context = invoke_context .transaction_context @@ -1107,7 +1100,7 @@ mod tests { .transaction_context .get_next_instruction_context_mut() .unwrap() - .configure(program_indices, instruction_accounts, &instruction_data); + .configure_for_tests(program_indices, instruction_accounts, &instruction_data); invoke_context.push().unwrap(); let instruction_context = invoke_context .transaction_context @@ -1376,7 +1369,7 @@ mod tests { transaction_context .get_next_instruction_context_mut() .unwrap() - .configure(program_indices, instruction_accounts, &instruction_data); + .configure_for_tests(program_indices, instruction_accounts, &instruction_data); transaction_context.push().unwrap(); let instruction_context = transaction_context .get_current_instruction_context() diff --git a/programs/bpf_loader/benches/serialization.rs b/programs/bpf_loader/benches/serialization.rs index 917a530eb18e35..781172f97613d6 100644 --- a/programs/bpf_loader/benches/serialization.rs +++ b/programs/bpf_loader/benches/serialization.rs @@ -5,7 +5,7 @@ use { solana_pubkey::Pubkey, solana_rent::Rent, solana_sdk_ids::{bpf_loader, bpf_loader_deprecated}, - solana_transaction_context::{IndexOfAccount, InstructionAccount, TransactionContext}, + solana_transaction_context::{InstructionAccount, TransactionContext}, }; fn create_inputs(owner: Pubkey, num_instruction_accounts: usize) -> TransactionContext { @@ -89,13 +89,8 @@ fn create_inputs(owner: Pubkey, num_instruction_accounts: usize) -> TransactionC .take(num_instruction_accounts) .enumerate() { - let index_in_callee = instruction_accounts - .iter() - .position(|account| account.index_in_transaction == index_in_transaction) - .unwrap_or(instruction_account_index) as IndexOfAccount; instruction_accounts.push(InstructionAccount::new( index_in_transaction, - index_in_callee, false, instruction_account_index >= 4, )); @@ -107,7 +102,7 @@ fn create_inputs(owner: Pubkey, num_instruction_accounts: usize) -> TransactionC transaction_context .get_next_instruction_context_mut() .unwrap() - .configure(vec![0], instruction_accounts, &instruction_data); + .configure_for_tests(vec![0], instruction_accounts, &instruction_data); transaction_context.push().unwrap(); transaction_context } diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index be7ceb9cc06faf..5c83d569aec351 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -7382,7 +7382,6 @@ version = "3.0.0" dependencies = [ "base64 0.22.1", "bincode", - "enum-iterator", "itertools 0.12.1", "log", "percentage", @@ -7405,7 +7404,6 @@ dependencies = [ "solana-sbpf", "solana-sdk-ids", "solana-slot-hashes", - "solana-stable-layout", "solana-svm-callback", "solana-svm-feature-set", "solana-svm-transaction", @@ -7415,7 +7413,6 @@ dependencies = [ "solana-timings", "solana-transaction-context", "solana-type-overrides", - "thiserror 2.0.12", ] [[package]] diff --git a/programs/sbf/benches/bpf_loader.rs b/programs/sbf/benches/bpf_loader.rs index 2ede3d5e7207dd..d01b045be16a79 100644 --- a/programs/sbf/benches/bpf_loader.rs +++ b/programs/sbf/benches/bpf_loader.rs @@ -61,7 +61,7 @@ macro_rules! with_mock_invoke_context { AccountSharedData::new(2, $account_size, &program_key), ), ]; - let instruction_accounts = vec![InstructionAccount::new(2, 0, false, true)]; + let instruction_accounts = vec![InstructionAccount::new(2, false, true)]; solana_program_runtime::with_mock_invoke_context!( $invoke_context, transaction_context, @@ -71,7 +71,7 @@ macro_rules! with_mock_invoke_context { .transaction_context .get_next_instruction_context_mut() .unwrap() - .configure(vec![0, 1], instruction_accounts, &[]); + .configure_for_tests(vec![0, 1], instruction_accounts, &[]); $invoke_context.push().unwrap(); }; } diff --git a/programs/system/src/system_instruction.rs b/programs/system/src/system_instruction.rs index fc2fea06cb4e27..19c31684857314 100644 --- a/programs/system/src/system_instruction.rs +++ b/programs/system/src/system_instruction.rs @@ -269,7 +269,7 @@ mod test { .transaction_context .get_next_instruction_context_mut() .unwrap() - .configure(vec![2], $instruction_accounts, &[]); + .configure_for_tests(vec![2], $instruction_accounts, &[]); $invoke_context.push().unwrap(); let $transaction_context = &$invoke_context.transaction_context; let $instruction_context = $transaction_context @@ -294,8 +294,8 @@ mod test { (system_program::id(), AccountSharedData::default()), ]; let $instruction_accounts = vec![ - InstructionAccount::new(0, 0, true, true), - InstructionAccount::new(1, 1, false, true), + InstructionAccount::new(0, true, true), + InstructionAccount::new(1, false, true), ]; with_mock_invoke_context!($invoke_context, transaction_context, transaction_accounts); }; diff --git a/programs/vote/src/vote_state/mod.rs b/programs/vote/src/vote_state/mod.rs index 685f23d46f234a..d9bbbd186bd86f 100644 --- a/programs/vote/src/vote_state/mod.rs +++ b/programs/vote/src/vote_state/mod.rs @@ -1167,9 +1167,9 @@ mod tests { 0, ); let mut instruction_context = InstructionContext::default(); - instruction_context.configure( + instruction_context.configure_for_tests( vec![0], - vec![InstructionAccount::new(1, 0, false, true)], + vec![InstructionAccount::new(1, false, true)], &[], ); @@ -1316,9 +1316,9 @@ mod tests { 0, ); let mut instruction_context = InstructionContext::default(); - instruction_context.configure( + instruction_context.configure_for_tests( vec![0], - vec![InstructionAccount::new(1, 0, false, true)], + vec![InstructionAccount::new(1, false, true)], &[], ); diff --git a/svm/examples/Cargo.lock b/svm/examples/Cargo.lock index df10f970422b49..e8a893b4db29ed 100644 --- a/svm/examples/Cargo.lock +++ b/svm/examples/Cargo.lock @@ -7182,7 +7182,6 @@ version = "3.0.0" dependencies = [ "base64 0.22.1", "bincode", - "enum-iterator", "itertools 0.12.1", "log", "percentage", @@ -7205,7 +7204,6 @@ dependencies = [ "solana-sbpf", "solana-sdk-ids", "solana-slot-hashes", - "solana-stable-layout", "solana-svm-callback", "solana-svm-feature-set", "solana-svm-transaction", @@ -7215,7 +7213,6 @@ dependencies = [ "solana-timings", "solana-transaction-context", "solana-type-overrides", - "thiserror 2.0.12", ] [[package]] diff --git a/svm/src/transaction_processor.rs b/svm/src/transaction_processor.rs index 4a98648858bfbb..10f6b33d5c8a55 100644 --- a/svm/src/transaction_processor.rs +++ b/svm/src/transaction_processor.rs @@ -1279,7 +1279,7 @@ mod tests { transaction_context .get_next_instruction_context_mut() .unwrap() - .configure(vec![], vec![], &[index_in_trace as u8]); + .configure_for_tests(vec![], vec![], &[index_in_trace as u8]); transaction_context.push().unwrap(); } } diff --git a/syscalls/Cargo.toml b/syscalls/Cargo.toml index d870434d930b1a..563a2efef42aff 100644 --- a/syscalls/Cargo.toml +++ b/syscalls/Cargo.toml @@ -70,6 +70,7 @@ solana-pubkey = { workspace = true, features = ["rand"] } solana-rent = { workspace = true } solana-slot-hashes = { workspace = true } solana-transaction-context = { workspace = true, features = ["dev-context-only-utils"] } +static_assertions = { workspace = true } test-case = { workspace = true } [lints] diff --git a/syscalls/src/cpi.rs b/syscalls/src/cpi.rs index e1e6a895b2062e..64e0de195896fe 100644 --- a/syscalls/src/cpi.rs +++ b/syscalls/src/cpi.rs @@ -15,6 +15,13 @@ use { const MAX_CPI_INSTRUCTION_DATA_LEN: u64 = 10 * 1024; const MAX_CPI_INSTRUCTION_ACCOUNTS: u8 = u8::MAX; + +#[cfg(test)] +static_assertions::const_assert_eq!( + (MAX_CPI_INSTRUCTION_ACCOUNTS as usize).saturating_add(1), + solana_transaction_context::MAX_ACCOUNTS_PER_TRANSACTION, +); + const MAX_CPI_ACCOUNT_INFOS: usize = 128; fn check_account_info_pointer( @@ -773,9 +780,8 @@ where ) -> Result, Error>, { let transaction_context = &invoke_context.transaction_context; - let next_instruction_accounts = transaction_context - .get_next_instruction_context()? - .instruction_accounts(); + let next_instruction_context = transaction_context.get_next_instruction_context()?; + let next_instruction_accounts = next_instruction_context.instruction_accounts(); let instruction_context = transaction_context.get_current_instruction_context()?; let mut accounts = Vec::with_capacity(next_instruction_accounts.len()); @@ -793,7 +799,10 @@ where for (instruction_account_index, instruction_account) in next_instruction_accounts.iter().enumerate() { - if instruction_account_index as IndexOfAccount != instruction_account.index_in_callee { + if next_instruction_context + .is_instruction_account_duplicate(instruction_account_index as IndexOfAccount)? + .is_some() + { continue; // Skip duplicate account } @@ -1307,11 +1316,9 @@ mod tests { let instruction_data = $instruction_data; let instruction_accounts = $instruction_accounts .iter() - .enumerate() - .map(|(index_in_callee, index_in_transaction)| { + .map(|index_in_transaction| { InstructionAccount::new( *index_in_transaction as IndexOfAccount, - index_in_callee as IndexOfAccount, false, $transaction_accounts[*index_in_transaction as usize].2, ) @@ -1334,7 +1341,7 @@ mod tests { .transaction_context .get_next_instruction_context_mut() .unwrap() - .configure($program_accounts, instruction_accounts, instruction_data); + .configure_for_tests($program_accounts, instruction_accounts, instruction_data); $invoke_context.push().unwrap(); }; } @@ -1852,11 +1859,11 @@ mod tests { .transaction_context .get_next_instruction_context_mut() .unwrap() - .configure( + .configure_for_tests( vec![0], vec![ - InstructionAccount::new(1, 0, false, true), - InstructionAccount::new(1, 0, false, true), + InstructionAccount::new(1, false, true), + InstructionAccount::new(1, false, true), ], &[], ); diff --git a/syscalls/src/lib.rs b/syscalls/src/lib.rs index 70f32de4ae7d4e..8550436a3a4eec 100644 --- a/syscalls/src/lib.rs +++ b/syscalls/src/lib.rs @@ -2223,7 +2223,7 @@ mod tests { .transaction_context .get_next_instruction_context_mut() .unwrap() - .configure(vec![0, 1], vec![], &[]); + .configure_for_tests(vec![0, 1], vec![], &[]); $invoke_context.push().unwrap(); }; } @@ -4448,7 +4448,6 @@ mod tests { { let instruction_accounts = vec![InstructionAccount::new( index_in_trace.saturating_add(1) as IndexOfAccount, - 0, false, false, )]; @@ -4456,7 +4455,7 @@ mod tests { .transaction_context .get_next_instruction_context_mut() .unwrap() - .configure(vec![0], instruction_accounts, &[index_in_trace as u8]); + .configure_for_tests(vec![0], instruction_accounts, &[index_in_trace as u8]); invoke_context.transaction_context.push().unwrap(); } } diff --git a/transaction-context/src/lib.rs b/transaction-context/src/lib.rs index 623f0c845d83ef..f0e251e35d567f 100644 --- a/transaction-context/src/lib.rs +++ b/transaction-context/src/lib.rs @@ -49,21 +49,22 @@ static_assertions::const_assert_eq!( solana_account_info::MAX_PERMITTED_DATA_INCREASE ); +pub const MAX_ACCOUNTS_PER_TRANSACTION: usize = 256; + /// Index of an account inside of the TransactionContext or an InstructionContext. pub type IndexOfAccount = u16; /// Contains account meta data which varies between instruction. /// /// It also contains indices to other structures for faster lookup. +/// +/// This data structure is supposed to be shared with programs in ABIv2, so do not modify it +/// without consulting SIMD-0177. #[repr(C)] #[derive(Clone, Debug, Eq, PartialEq)] pub struct InstructionAccount { /// Points to the account and its key in the `TransactionContext` pub index_in_transaction: IndexOfAccount, - /// Points to the first occurrence in the current `InstructionContext` - /// - /// This excludes the program accounts. - pub index_in_callee: IndexOfAccount, /// Is this account supposed to sign is_signer: u8, /// Is this account allowed to become writable @@ -73,13 +74,11 @@ pub struct InstructionAccount { impl InstructionAccount { pub fn new( index_in_transaction: IndexOfAccount, - index_in_callee: IndexOfAccount, is_signer: bool, is_writable: bool, ) -> InstructionAccount { InstructionAccount { index_in_transaction, - index_in_callee, is_signer: is_signer as u8, is_writable: is_writable as u8, } @@ -622,6 +621,10 @@ pub struct InstructionContext { instruction_accounts_lamport_sum: u128, program_accounts: Vec, instruction_accounts: Vec, + /// This is an account deduplication map that maps index_in_transaction to index_in_instruction + /// Usage: dedup_map[index_in_transaction] = index_in_instruction + /// This is a vector of u8s to save memory, since many entries may be unused. + dedup_map: Vec, instruction_data: Vec, } @@ -632,11 +635,41 @@ impl InstructionContext { &mut self, program_accounts: Vec, instruction_accounts: Vec, + deduplication_map: Vec, instruction_data: &[u8], ) { + debug_assert_eq!(deduplication_map.len(), MAX_ACCOUNTS_PER_TRANSACTION); self.program_accounts = program_accounts; self.instruction_accounts = instruction_accounts; self.instruction_data = instruction_data.to_vec(); + self.dedup_map = deduplication_map; + } + + /// A version of `fn configure` to help creating the deduplication map in tests + #[cfg(not(target_os = "solana"))] + pub fn configure_for_tests( + &mut self, + program_accounts: Vec, + instruction_accounts: Vec, + instruction_data: &[u8], + ) { + debug_assert!(instruction_accounts.len() <= MAX_ACCOUNTS_PER_TRANSACTION); + let mut dedup_map = vec![u8::MAX; MAX_ACCOUNTS_PER_TRANSACTION]; + for (idx, account) in instruction_accounts.iter().enumerate() { + let index_in_instruction = dedup_map + .get_mut(account.index_in_transaction as usize) + .unwrap(); + if *index_in_instruction == u8::MAX { + *index_in_instruction = idx as u8; + } + } + + self.configure( + program_accounts, + instruction_accounts, + dedup_map, + instruction_data, + ); } /// How many Instructions were on the stack after this one was pushed @@ -735,10 +768,15 @@ impl InstructionContext { &self, index_in_transaction: IndexOfAccount, ) -> Result { - self.instruction_accounts - .iter() - .position(|account| account.index_in_transaction == index_in_transaction) - .map(|idx| idx as IndexOfAccount) + self.dedup_map + .get(index_in_transaction as usize) + .and_then(|idx| { + if *idx as usize >= self.instruction_accounts.len() { + None + } else { + Some(*idx as IndexOfAccount) + } + }) .ok_or(InstructionError::MissingAccount) } @@ -748,16 +786,18 @@ impl InstructionContext { &self, instruction_account_index: IndexOfAccount, ) -> Result, InstructionError> { - let index_in_callee = self - .instruction_accounts - .get(instruction_account_index as usize) - .ok_or(InstructionError::NotEnoughAccountKeys)? - .index_in_callee; - Ok(if index_in_callee == instruction_account_index { - None - } else { - Some(index_in_callee) - }) + let index_in_transaction = + self.get_index_of_instruction_account_in_transaction(instruction_account_index)?; + let first_instruction_account_index = + self.get_index_of_account_in_instruction(index_in_transaction)?; + + Ok( + if first_instruction_account_index == instruction_account_index { + None + } else { + Some(first_instruction_account_index) + }, + ) } /// Gets the key of the last program account of this Instruction