diff --git a/accountsdb-plugin-postgres/scripts/create_schema.sql b/accountsdb-plugin-postgres/scripts/create_schema.sql index 994a2176cfb178..e4d8d87be813a0 100644 --- a/accountsdb-plugin-postgres/scripts/create_schema.sql +++ b/accountsdb-plugin-postgres/scripts/create_schema.sql @@ -48,7 +48,13 @@ Create TYPE "TransactionErrorCode" AS ENUM ( 'WouldExceedMaxBlockCostLimit', 'UnsupportedVersion', 'InvalidWritableAccount', - 'WouldExceedMaxAccountDataCostLimit' + 'WouldExceedMaxAccountDataCostLimit', + 'TooManyAccountLocks', + 'AddressLookupError' + 'AddressLookupTableNotFound', + 'InvalidAddressLookupTableOwner', + 'InvalidAddressLookupTableData', + 'InvalidAddressLookupTableIndex' ); CREATE TYPE "TransactionError" AS ( diff --git a/accountsdb-plugin-postgres/src/postgres_client/postgres_client_transaction.rs b/accountsdb-plugin-postgres/src/postgres_client/postgres_client_transaction.rs index 37112b0b10f1f0..ea095a0b2f9e90 100644 --- a/accountsdb-plugin-postgres/src/postgres_client/postgres_client_transaction.rs +++ b/accountsdb-plugin-postgres/src/postgres_client/postgres_client_transaction.rs @@ -332,6 +332,10 @@ pub enum DbTransactionErrorCode { InvalidWritableAccount, WouldExceedMaxAccountDataCostLimit, TooManyAccountLocks, + AddressLookupTableNotFound, + InvalidAddressLookupTableOwner, + InvalidAddressLookupTableData, + InvalidAddressLookupTableIndex, } impl From<&TransactionError> for DbTransactionErrorCode { @@ -364,6 +368,14 @@ impl From<&TransactionError> for DbTransactionErrorCode { Self::WouldExceedMaxAccountDataCostLimit } TransactionError::TooManyAccountLocks => Self::TooManyAccountLocks, + TransactionError::AddressLookupTableNotFound => Self::AddressLookupTableNotFound, + TransactionError::InvalidAddressLookupTableOwner => { + Self::InvalidAddressLookupTableOwner + } + TransactionError::InvalidAddressLookupTableData => Self::InvalidAddressLookupTableData, + TransactionError::InvalidAddressLookupTableIndex => { + Self::InvalidAddressLookupTableIndex + } } } } diff --git a/core/src/banking_stage.rs b/core/src/banking_stage.rs index 2fd74d6a8b1656..8f95454bc30f8d 100644 --- a/core/src/banking_stage.rs +++ b/core/src/banking_stage.rs @@ -36,7 +36,10 @@ use { MAX_TRANSACTION_FORWARDING_DELAY_GPU, }, feature_set, - message::Message, + message::{ + v0::{LoadedAddresses, MessageAddressTableLookup}, + Message, + }, pubkey::Pubkey, short_vec::decode_shortu16_len, signature::Signature, @@ -1123,6 +1126,7 @@ impl BankingStage { transaction_indexes: &[usize], feature_set: &Arc, votes_only: bool, + address_loader: impl Fn(&[MessageAddressTableLookup]) -> transaction::Result, ) -> (Vec, Vec) { transaction_indexes .iter() @@ -1139,7 +1143,7 @@ impl BankingStage { tx, message_hash, Some(p.meta.is_simple_vote_tx()), - |_| Err(TransactionError::UnsupportedVersion), + &address_loader, ) .ok()?; tx.verify_precompiles(feature_set).ok()?; @@ -1205,6 +1209,7 @@ impl BankingStage { &packet_indexes, &bank.feature_set, bank.vote_only_bank(), + |lookup| bank.load_lookup_table_addresses(lookup), ); packet_conversion_time.stop(); inc_new_counter_info!("banking_stage-packet_conversion", 1); @@ -1279,6 +1284,7 @@ impl BankingStage { transaction_indexes, &bank.feature_set, bank.vote_only_bank(), + |lookup| bank.load_lookup_table_addresses(lookup), ); unprocessed_packet_conversion_time.stop(); @@ -3228,6 +3234,7 @@ mod tests { &packet_indexes, &Arc::new(FeatureSet::default()), votes_only, + |_| Err(TransactionError::UnsupportedVersion), ); assert_eq!(2, txs.len()); assert_eq!(vec![0, 1], tx_packet_index); @@ -3238,6 +3245,7 @@ mod tests { &packet_indexes, &Arc::new(FeatureSet::default()), votes_only, + |_| Err(TransactionError::UnsupportedVersion), ); assert_eq!(0, txs.len()); assert_eq!(0, tx_packet_index.len()); @@ -3257,6 +3265,7 @@ mod tests { &packet_indexes, &Arc::new(FeatureSet::default()), votes_only, + |_| Err(TransactionError::UnsupportedVersion), ); assert_eq!(3, txs.len()); assert_eq!(vec![0, 1, 2], tx_packet_index); @@ -3267,6 +3276,7 @@ mod tests { &packet_indexes, &Arc::new(FeatureSet::default()), votes_only, + |_| Err(TransactionError::UnsupportedVersion), ); assert_eq!(2, txs.len()); assert_eq!(vec![0, 2], tx_packet_index); @@ -3286,6 +3296,7 @@ mod tests { &packet_indexes, &Arc::new(FeatureSet::default()), votes_only, + |_| Err(TransactionError::UnsupportedVersion), ); assert_eq!(3, txs.len()); assert_eq!(vec![0, 1, 2], tx_packet_index); @@ -3296,6 +3307,7 @@ mod tests { &packet_indexes, &Arc::new(FeatureSet::default()), votes_only, + |_| Err(TransactionError::UnsupportedVersion), ); assert_eq!(3, txs.len()); assert_eq!(vec![0, 1, 2], tx_packet_index); diff --git a/ledger/src/blockstore/blockstore_purge.rs b/ledger/src/blockstore/blockstore_purge.rs index 4daefc53ba3b54..66ec4583e4adb8 100644 --- a/ledger/src/blockstore/blockstore_purge.rs +++ b/ledger/src/blockstore/blockstore_purge.rs @@ -335,8 +335,8 @@ impl Blockstore { if let Some(&signature) = transaction.signatures.get(0) { batch.delete::((0, signature, slot))?; batch.delete::((1, signature, slot))?; - // TODO: support purging mapped addresses from versioned transactions - for pubkey in transaction.message.unmapped_keys() { + // TODO: support purging dynamically loaded addresses from versioned transactions + for pubkey in transaction.message.into_static_account_keys() { batch.delete::((0, pubkey, slot, signature))?; batch.delete::((1, pubkey, slot, signature))?; } diff --git a/program-runtime/src/invoke_context.rs b/program-runtime/src/invoke_context.rs index 7b4247e4c49631..472f545897880b 100644 --- a/program-runtime/src/invoke_context.rs +++ b/program-runtime/src/invoke_context.rs @@ -17,7 +17,7 @@ use { hash::Hash, instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError}, keyed_account::{create_keyed_accounts_unified, keyed_account_at_index, KeyedAccount}, - message::Message, + message::{Message, SanitizedMessage}, pubkey::Pubkey, rent::Rent, sysvar::Sysvar, @@ -262,7 +262,7 @@ impl<'a> InvokeContext<'a> { /// Push a stack frame onto the invocation stack pub fn push( &mut self, - message: &Message, + message: &SanitizedMessage, instruction: &CompiledInstruction, program_indices: &[usize], account_indices: &[usize], @@ -377,11 +377,13 @@ impl<'a> InvokeContext<'a> { /// Verify the results of an instruction fn verify( &mut self, - message: &Message, + message: &SanitizedMessage, instruction: &CompiledInstruction, program_indices: &[usize], ) -> Result<(), InstructionError> { - let program_id = instruction.program_id(&message.account_keys); + let program_id = message + .get_account_key(instruction.program_id_index as usize) + .expect("invalid program id index"); let do_support_realloc = self.feature_set.is_active(&do_support_realloc::id()); let cap_accounts_data_len = self.feature_set.is_active(&cap_accounts_data_len::id()); @@ -561,9 +563,11 @@ impl<'a> InvokeContext<'a> { if let Some(instruction_recorder) = &self.instruction_recorder { instruction_recorder.record_instruction(instruction); } + + let message = SanitizedMessage::Legacy(message); self.process_instruction( &message, - &message.instructions[0], + &message.instructions()[0], &program_indices, &account_indices, &caller_write_privileges, @@ -704,7 +708,7 @@ impl<'a> InvokeContext<'a> { /// Processes a cross-program instruction and returns how many compute units were used pub fn process_instruction( &mut self, - message: &Message, + message: &SanitizedMessage, instruction: &CompiledInstruction, program_indices: &[usize], account_indices: &[usize], @@ -727,7 +731,10 @@ impl<'a> InvokeContext<'a> { let result = self .push(message, instruction, program_indices, account_indices) .and_then(|_| { - self.return_data = (*instruction.program_id(&message.account_keys), Vec::new()); + let program_id = message + .get_account_key(instruction.program_id_index as usize) + .expect("invalid program id index"); + self.return_data = (*program_id, Vec::new()); let pre_remaining_units = self.compute_meter.borrow().get_remaining(); let execution_result = self.process_executable_chain(&instruction.data); let post_remaining_units = self.compute_meter.borrow().get_remaining(); @@ -738,7 +745,7 @@ impl<'a> InvokeContext<'a> { if is_lowest_invocation_level { self.verify(message, instruction, program_indices) } else { - let write_privileges: Vec = (0..message.account_keys.len()) + let write_privileges: Vec = (0..message.account_keys_len()) .map(|i| message.is_writable(i)) .collect(); self.verify_and_update(instruction, account_indices, &write_privileges) @@ -923,7 +930,7 @@ impl<'a> InvokeContext<'a> { pub struct MockInvokeContextPreparation { pub accounts: TransactionAccountRefCells, - pub message: Message, + pub message: SanitizedMessage, pub account_indices: Vec, } @@ -954,17 +961,16 @@ pub fn prepare_mock_invoke_context( for program_index in program_indices.iter().rev() { metas.remove(*program_index); } - let message = Message::new( + let message = SanitizedMessage::Legacy(Message::new( &[Instruction::new_with_bytes( program_id, instruction_data, metas, )], None, - ); + )); let account_indices: Vec = message - .account_keys - .iter() + .account_keys_iter() .map(|search_key| { accounts .iter() @@ -1010,7 +1016,7 @@ pub fn with_mock_invoke_context R>( invoke_context .push( &preparation.message, - &preparation.message.instructions[0], + &preparation.message.instructions()[0], &program_indices, &preparation.account_indices, ) @@ -1035,7 +1041,7 @@ pub fn mock_process_instruction_with_sysvars( invoke_context.sysvars = sysvars; invoke_context.push( &preparation.message, - &preparation.message.instructions[0], + &preparation.message.instructions()[0], &program_indices, &preparation.account_indices, )?; @@ -1210,10 +1216,10 @@ mod tests { } let account_indices = (0..accounts.len()).collect::>(); - let message = Message::new( + let message = SanitizedMessage::Legacy(Message::new( &[Instruction::new_with_bytes(invoke_stack[0], &[0], metas)], None, - ); + )); let mut invoke_context = InvokeContext::new_mock(&accounts, &[]); // Check call depth increases and has a limit @@ -1222,7 +1228,7 @@ mod tests { if Err(InstructionError::CallDepth) == invoke_context.push( &message, - &message.instructions[0], + &message.instructions()[0], &[MAX_DEPTH + depth_reached], &[], ) @@ -1293,25 +1299,25 @@ mod tests { solana_sdk::pubkey::new_rand(), Rc::new(RefCell::new(AccountSharedData::default())), )]; - let message = Message::new( + let message = SanitizedMessage::Legacy(Message::new( &[Instruction::new_with_bincode( accounts[0].0, &MockInstruction::NoopSuccess, vec![AccountMeta::new_readonly(accounts[0].0, false)], )], None, - ); + )); let mut invoke_context = InvokeContext::new_mock(&accounts, &[]); invoke_context - .push(&message, &message.instructions[0], &[0], &[]) + .push(&message, &message.instructions()[0], &[0], &[]) .unwrap(); assert!(invoke_context - .verify(&message, &message.instructions[0], &[0]) + .verify(&message, &message.instructions()[0], &[0]) .is_ok()); let mut _borrowed = accounts[0].1.borrow(); assert_eq!( - invoke_context.verify(&message, &message.instructions[0], &[0]), + invoke_context.verify(&message, &message.instructions()[0], &[0]), Err(InstructionError::AccountBorrowOutstanding) ); } @@ -1360,7 +1366,7 @@ mod tests { &MockInstruction::NoopSuccess, metas.clone(), ); - let message = Message::new(&[callee_instruction], None); + let message = SanitizedMessage::Legacy(Message::new(&[callee_instruction], None)); let builtin_programs = &[BuiltinProgram { program_id: callee_program_id, @@ -1373,8 +1379,7 @@ mod tests { // not owned account modified by the caller (before the invoke) let caller_write_privileges = message - .account_keys - .iter() + .account_keys_iter() .enumerate() .map(|(i, _)| message.is_writable(i)) .collect::>(); @@ -1383,7 +1388,7 @@ mod tests { invoke_context .process_instruction( &message, - &message.instructions[0], + &message.instructions()[0], &program_indices[1..], &account_indices, &caller_write_privileges, @@ -1399,7 +1404,7 @@ mod tests { invoke_context .process_instruction( &message, - &message.instructions[0], + &message.instructions()[0], &program_indices[1..], &account_indices, &caller_write_privileges, @@ -1444,20 +1449,19 @@ mod tests { for case in cases { let callee_instruction = Instruction::new_with_bincode(callee_program_id, &case.0, metas.clone()); - let message = Message::new(&[callee_instruction], None); + let message = SanitizedMessage::Legacy(Message::new(&[callee_instruction], None)); invoke_context .push(&message, &caller_instruction, &program_indices[..1], &[]) .unwrap(); let caller_write_privileges = message - .account_keys - .iter() + .account_keys_iter() .enumerate() .map(|(i, _)| message.is_writable(i)) .collect::>(); assert_eq!( invoke_context.process_instruction( &message, - &message.instructions[0], + &message.instructions()[0], &program_indices[1..], &account_indices, &caller_write_privileges, @@ -1510,7 +1514,7 @@ mod tests { &MockInstruction::NoopSuccess, metas.clone(), ); - let message = Message::new(&[callee_instruction.clone()], None); + let message = SanitizedMessage::Legacy(Message::new(&[callee_instruction.clone()], None)); let builtin_programs = &[BuiltinProgram { program_id: callee_program_id, @@ -1559,7 +1563,8 @@ mod tests { for case in cases { let callee_instruction = Instruction::new_with_bincode(callee_program_id, &case.0, metas.clone()); - let message = Message::new(&[callee_instruction.clone()], None); + let message = + SanitizedMessage::Legacy(Message::new(&[callee_instruction.clone()], None)); invoke_context .push(&message, &caller_instruction, &program_indices, &[]) .unwrap(); @@ -1584,22 +1589,22 @@ mod tests { ), ]; - let noop_message = Message::new( + let noop_message = SanitizedMessage::Legacy(Message::new( &[Instruction::new_with_bincode( accounts[0].0, &MockInstruction::NoopSuccess, vec![AccountMeta::new_readonly(accounts[0].0, false)], )], None, - ); - let neon_message = Message::new( + )); + let neon_message = SanitizedMessage::Legacy(Message::new( &[Instruction::new_with_bincode( crate::neon_evm_program::id(), &MockInstruction::NoopSuccess, vec![AccountMeta::new_readonly(accounts[0].0, false)], )], None, - ); + )); let mut feature_set = FeatureSet::all_enabled(); feature_set.deactivate(&tx_wide_compute_cap::id()); @@ -1608,7 +1613,7 @@ mod tests { invoke_context.feature_set = Arc::new(feature_set); invoke_context - .push(&noop_message, &noop_message.instructions[0], &[0], &[]) + .push(&noop_message, &noop_message.instructions()[0], &[0], &[]) .unwrap(); assert_eq!( *invoke_context.get_compute_budget(), @@ -1617,7 +1622,7 @@ mod tests { invoke_context.pop(); invoke_context - .push(&neon_message, &neon_message.instructions[0], &[1], &[]) + .push(&neon_message, &neon_message.instructions()[0], &[1], &[]) .unwrap(); let expected_compute_budget = ComputeBudget { max_units: 500_000, @@ -1631,7 +1636,7 @@ mod tests { invoke_context.pop(); invoke_context - .push(&noop_message, &noop_message.instructions[0], &[0], &[]) + .push(&noop_message, &noop_message.instructions()[0], &[0], &[]) .unwrap(); assert_eq!( *invoke_context.get_compute_budget(), @@ -1696,19 +1701,19 @@ mod tests { }, metas.clone(), ); - let message = Message::new(&[callee_instruction.clone()], None); + let message = + SanitizedMessage::Legacy(Message::new(&[callee_instruction.clone()], None)); invoke_context .push(&message, &caller_instruction, &program_indices[..1], &[]) .unwrap(); let caller_write_privileges = message - .account_keys - .iter() + .account_keys_iter() .enumerate() .map(|(i, _)| message.is_writable(i)) .collect::>(); let result = invoke_context.process_instruction( &message, - &message.instructions[0], + &message.instructions()[0], &program_indices[1..], &account_indices, &caller_write_privileges, @@ -1771,10 +1776,9 @@ mod tests { &MockInstruction::Resize { new_len }, metas.clone(), ); - let message = Message::new(&[instruction], None); + let message = SanitizedMessage::Legacy(Message::new(&[instruction], None)); let caller_write_privileges = message - .account_keys - .iter() + .account_keys_iter() .enumerate() .map(|(i, _)| message.is_writable(i)) .collect::>(); @@ -1782,7 +1786,7 @@ mod tests { let result = invoke_context .process_instruction( &message, - &message.instructions[0], + &message.instructions()[0], &program_indices, &account_indices, &caller_write_privileges, @@ -1801,10 +1805,9 @@ mod tests { &MockInstruction::Resize { new_len }, metas.clone(), ); - let message = Message::new(&[instruction], None); + let message = SanitizedMessage::Legacy(Message::new(&[instruction], None)); let caller_write_privileges = message - .account_keys - .iter() + .account_keys_iter() .enumerate() .map(|(i, _)| message.is_writable(i)) .collect::>(); @@ -1812,7 +1815,7 @@ mod tests { let result = invoke_context .process_instruction( &message, - &message.instructions[0], + &message.instructions()[0], &program_indices, &account_indices, &caller_write_privileges, @@ -1831,10 +1834,9 @@ mod tests { &MockInstruction::Resize { new_len }, metas, ); - let message = Message::new(&[instruction], None); + let message = SanitizedMessage::Legacy(Message::new(&[instruction], None)); let caller_write_privileges = message - .account_keys - .iter() + .account_keys_iter() .enumerate() .map(|(i, _)| message.is_writable(i)) .collect::>(); @@ -1842,7 +1844,7 @@ mod tests { let result = invoke_context .process_instruction( &message, - &message.instructions[0], + &message.instructions()[0], &program_indices, &account_indices, &caller_write_privileges, diff --git a/program-test/src/lib.rs b/program-test/src/lib.rs index 16338e20efe728..dd46862926a772 100644 --- a/program-test/src/lib.rs +++ b/program-test/src/lib.rs @@ -30,7 +30,7 @@ use { genesis_config::{ClusterType, GenesisConfig}, hash::Hash, instruction::{Instruction, InstructionError}, - message::Message, + message::{Message, SanitizedMessage}, native_token::sol_to_lamports, poh_config::PohConfig, program_error::{ProgramError, ACCOUNT_BORROW_FAILED, UNSUPPORTED_SYSVAR}, @@ -314,10 +314,11 @@ impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs { instruction_recorder.record_instruction(instruction.clone()); } + let message = SanitizedMessage::Legacy(message); invoke_context .process_instruction( &message, - &message.instructions[0], + &message.instructions()[0], &program_indices, &account_indices, &caller_privileges, diff --git a/programs/address-lookup-table/src/state.rs b/programs/address-lookup-table/src/state.rs index 8bf7fc3457a8ed..c2f50c6ab7874c 100644 --- a/programs/address-lookup-table/src/state.rs +++ b/programs/address-lookup-table/src/state.rs @@ -6,6 +6,7 @@ use { instruction::InstructionError, pubkey::Pubkey, slot_hashes::{SlotHashes, MAX_ENTRIES}, + transaction::AddressLookupError, }, std::borrow::Cow, }; @@ -133,6 +134,49 @@ impl<'a> AddressLookupTable<'a> { Ok(()) } + /// Get the length of addresses that are active for lookups + pub fn get_active_addresses_len( + &self, + current_slot: Slot, + slot_hashes: &SlotHashes, + ) -> Result { + if !self.meta.is_active(current_slot, slot_hashes) { + // Once a lookup table is no longer active, it can be closed + // at any point, so returning a specific error for deactivated + // lookup tables could result in a race condition. + return Err(AddressLookupError::LookupTableAccountNotFound); + } + + // If the address table was extended in the same slot in which it is used + // to lookup addresses for another transaction, the recently extended + // addresses are not considered active and won't be accessible. + let active_addresses_len = if current_slot > self.meta.last_extended_slot { + self.addresses.len() + } else { + self.meta.last_extended_slot_start_index as usize + }; + + Ok(active_addresses_len) + } + + /// Lookup addresses for provided table indexes. Since lookups are performed on + /// tables which are not read-locked, this implementation needs to be careful + /// about resolving addresses consistently. + pub fn lookup( + &self, + current_slot: Slot, + indexes: &[u8], + slot_hashes: &SlotHashes, + ) -> Result, AddressLookupError> { + let active_addresses_len = self.get_active_addresses_len(current_slot, slot_hashes)?; + let active_addresses = &self.addresses[0..active_addresses_len]; + indexes + .iter() + .map(|idx| active_addresses.get(*idx as usize).cloned()) + .collect::>() + .ok_or(AddressLookupError::InvalidLookupIndex) + } + /// Serialize an address table including its addresses pub fn serialize_for_tests(self, data: &mut Vec) -> Result<(), InstructionError> { data.resize(LOOKUP_TABLE_META_SIZE, 0); @@ -322,4 +366,117 @@ mod tests { test_case(case); } } + + #[test] + fn test_lookup_from_empty_table() { + let lookup_table = AddressLookupTable { + meta: LookupTableMeta::default(), + addresses: Cow::Owned(vec![]), + }; + + assert_eq!( + lookup_table.lookup(0, &[], &SlotHashes::default()), + Ok(vec![]) + ); + assert_eq!( + lookup_table.lookup(0, &[0], &SlotHashes::default()), + Err(AddressLookupError::InvalidLookupIndex) + ); + } + + #[test] + fn test_lookup_from_deactivating_table() { + let current_slot = 1; + let slot_hashes = SlotHashes::default(); + let addresses = vec![Pubkey::new_unique()]; + let lookup_table = AddressLookupTable { + meta: LookupTableMeta { + deactivation_slot: current_slot, + last_extended_slot: current_slot - 1, + ..LookupTableMeta::default() + }, + addresses: Cow::Owned(addresses.clone()), + }; + + assert_eq!( + lookup_table.meta.status(current_slot, &slot_hashes), + LookupTableStatus::Deactivating { + remaining_blocks: MAX_ENTRIES + 1 + } + ); + + assert_eq!( + lookup_table.lookup(current_slot, &[0], &slot_hashes), + Ok(vec![addresses[0]]), + ); + } + + #[test] + fn test_lookup_from_deactivated_table() { + let current_slot = 1; + let slot_hashes = SlotHashes::default(); + let lookup_table = AddressLookupTable { + meta: LookupTableMeta { + deactivation_slot: current_slot - 1, + last_extended_slot: current_slot - 1, + ..LookupTableMeta::default() + }, + addresses: Cow::Owned(vec![]), + }; + + assert_eq!( + lookup_table.meta.status(current_slot, &slot_hashes), + LookupTableStatus::Deactivated + ); + assert_eq!( + lookup_table.lookup(current_slot, &[0], &slot_hashes), + Err(AddressLookupError::LookupTableAccountNotFound) + ); + } + + #[test] + fn test_lookup_from_table_extended_in_current_slot() { + let current_slot = 0; + let addresses: Vec<_> = (0..2).map(|_| Pubkey::new_unique()).collect(); + let lookup_table = AddressLookupTable { + meta: LookupTableMeta { + last_extended_slot: current_slot, + last_extended_slot_start_index: 1, + ..LookupTableMeta::default() + }, + addresses: Cow::Owned(addresses.clone()), + }; + + assert_eq!( + lookup_table.lookup(current_slot, &[0], &SlotHashes::default()), + Ok(vec![addresses[0]]) + ); + assert_eq!( + lookup_table.lookup(current_slot, &[1], &SlotHashes::default()), + Err(AddressLookupError::InvalidLookupIndex), + ); + } + + #[test] + fn test_lookup_from_table_extended_in_previous_slot() { + let current_slot = 1; + let addresses: Vec<_> = (0..10).map(|_| Pubkey::new_unique()).collect(); + let lookup_table = AddressLookupTable { + meta: LookupTableMeta { + last_extended_slot: current_slot - 1, + last_extended_slot_start_index: 1, + ..LookupTableMeta::default() + }, + addresses: Cow::Owned(addresses.clone()), + }; + + assert_eq!( + lookup_table.lookup(current_slot, &[0, 3, 1, 5], &SlotHashes::default()), + Ok(vec![addresses[0], addresses[3], addresses[1], addresses[5]]) + ); + assert_eq!( + lookup_table.lookup(current_slot, &[10], &SlotHashes::default()), + Err(AddressLookupError::InvalidLookupIndex), + ); + } } diff --git a/programs/bpf_loader/src/serialization.rs b/programs/bpf_loader/src/serialization.rs index 4ae530e21fd4c7..fce15fe6a58ac4 100644 --- a/programs/bpf_loader/src/serialization.rs +++ b/programs/bpf_loader/src/serialization.rs @@ -453,7 +453,7 @@ mod tests { invoke_context .push( &preparation.message, - &preparation.message.instructions[0], + &preparation.message.instructions()[0], &program_indices, &preparation.account_indices, ) diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index ea4db313215205..e614e49ba7f89d 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -32,7 +32,7 @@ use { hash::{Hasher, HASH_BYTES}, instruction::{AccountMeta, Instruction, InstructionError}, keccak, - message::Message, + message::{Message, SanitizedMessage}, native_loader, precompiles::is_precompile, program::MAX_RETURN_DATA, @@ -2364,10 +2364,11 @@ fn call<'a, 'b: 'a>( } // Process instruction + let message = SanitizedMessage::Legacy(message); invoke_context .process_instruction( &message, - &message.instructions[0], + &message.instructions()[0], &program_indices, &account_indices, &caller_write_privileges, @@ -2960,13 +2961,13 @@ mod tests { let program_id = Pubkey::new_unique(); let program_account = AccountSharedData::new_ref(0, 0, &bpf_loader::id()); let accounts = [(program_id, program_account)]; - let message = Message::new( + let message = SanitizedMessage::Legacy(Message::new( &[Instruction::new_with_bytes(program_id, &[], vec![])], None, - ); + )); let mut invoke_context = InvokeContext::new_mock(&accounts, &[]); invoke_context - .push(&message, &message.instructions[0], &[0], &[]) + .push(&message, &message.instructions()[0], &[0], &[]) .unwrap(); let mut syscall_panic = SyscallPanic { invoke_context: Rc::new(RefCell::new(&mut invoke_context)), @@ -3037,13 +3038,13 @@ mod tests { let program_id = Pubkey::new_unique(); let program_account = AccountSharedData::new_ref(0, 0, &bpf_loader::id()); let accounts = [(program_id, program_account)]; - let message = Message::new( + let message = SanitizedMessage::Legacy(Message::new( &[Instruction::new_with_bytes(program_id, &[], vec![])], None, - ); + )); let mut invoke_context = InvokeContext::new_mock(&accounts, &[]); invoke_context - .push(&message, &message.instructions[0], &[0], &[]) + .push(&message, &message.instructions()[0], &[0], &[]) .unwrap(); let mut syscall_sol_log = SyscallLog { invoke_context: Rc::new(RefCell::new(&mut invoke_context)), @@ -3141,13 +3142,13 @@ mod tests { let program_id = Pubkey::new_unique(); let program_account = AccountSharedData::new_ref(0, 0, &bpf_loader::id()); let accounts = [(program_id, program_account)]; - let message = Message::new( + let message = SanitizedMessage::Legacy(Message::new( &[Instruction::new_with_bytes(program_id, &[], vec![])], None, - ); + )); let mut invoke_context = InvokeContext::new_mock(&accounts, &[]); invoke_context - .push(&message, &message.instructions[0], &[0], &[]) + .push(&message, &message.instructions()[0], &[0], &[]) .unwrap(); let cost = invoke_context.get_compute_budget().log_64_units; let mut syscall_sol_log_u64 = SyscallLogU64 { @@ -3183,13 +3184,13 @@ mod tests { let program_id = Pubkey::new_unique(); let program_account = AccountSharedData::new_ref(0, 0, &bpf_loader::id()); let accounts = [(program_id, program_account)]; - let message = Message::new( + let message = SanitizedMessage::Legacy(Message::new( &[Instruction::new_with_bytes(program_id, &[], vec![])], None, - ); + )); let mut invoke_context = InvokeContext::new_mock(&accounts, &[]); invoke_context - .push(&message, &message.instructions[0], &[0], &[]) + .push(&message, &message.instructions()[0], &[0], &[]) .unwrap(); let cost = invoke_context.get_compute_budget().log_pubkey_units; let mut syscall_sol_pubkey = SyscallLogPubkey { @@ -3395,10 +3396,10 @@ mod tests { let program_id = Pubkey::new_unique(); let program_account = AccountSharedData::new_ref(0, 0, &bpf_loader_deprecated::id()); let accounts = [(program_id, program_account)]; - let message = Message::new( + let message = SanitizedMessage::Legacy(Message::new( &[Instruction::new_with_bytes(program_id, &[], vec![])], None, - ); + )); let bytes1 = "Gaggablaghblagh!"; let bytes2 = "flurbos"; @@ -3463,7 +3464,7 @@ mod tests { * 4, ); invoke_context - .push(&message, &message.instructions[0], &[0], &[]) + .push(&message, &message.instructions()[0], &[0], &[]) .unwrap(); let mut syscall = SyscallSha256 { invoke_context: Rc::new(RefCell::new(&mut invoke_context)), @@ -3524,10 +3525,10 @@ mod tests { let program_id = Pubkey::new_unique(); let program_account = AccountSharedData::new_ref(0, 0, &bpf_loader::id()); let accounts = [(program_id, program_account)]; - let message = Message::new( + let message = SanitizedMessage::Legacy(Message::new( &[Instruction::new_with_bytes(program_id, &[], vec![])], None, - ); + )); // Test clock sysvar { @@ -3562,7 +3563,7 @@ mod tests { let sysvars = [(sysvar::clock::id(), data)]; invoke_context.sysvars = &sysvars; invoke_context - .push(&message, &message.instructions[0], &[0], &[]) + .push(&message, &message.instructions()[0], &[0], &[]) .unwrap(); let mut syscall = SyscallGetClockSysvar { invoke_context: Rc::new(RefCell::new(&mut invoke_context)), @@ -3607,7 +3608,7 @@ mod tests { let sysvars = [(sysvar::epoch_schedule::id(), data)]; invoke_context.sysvars = &sysvars; invoke_context - .push(&message, &message.instructions[0], &[0], &[]) + .push(&message, &message.instructions()[0], &[0], &[]) .unwrap(); let mut syscall = SyscallGetEpochScheduleSysvar { invoke_context: Rc::new(RefCell::new(&mut invoke_context)), @@ -3659,7 +3660,7 @@ mod tests { let sysvars = [(sysvar::fees::id(), data)]; invoke_context.sysvars = &sysvars; invoke_context - .push(&message, &message.instructions[0], &[0], &[]) + .push(&message, &message.instructions()[0], &[0], &[]) .unwrap(); let mut syscall = SyscallGetFeesSysvar { invoke_context: Rc::new(RefCell::new(&mut invoke_context)), @@ -3702,7 +3703,7 @@ mod tests { let sysvars = [(sysvar::rent::id(), data)]; invoke_context.sysvars = &sysvars; invoke_context - .push(&message, &message.instructions[0], &[0], &[]) + .push(&message, &message.instructions()[0], &[0], &[]) .unwrap(); let mut syscall = SyscallGetRentSysvar { invoke_context: Rc::new(RefCell::new(&mut invoke_context)), @@ -3834,13 +3835,13 @@ mod tests { let program_id = Pubkey::new_unique(); let program_account = AccountSharedData::new_ref(0, 0, &bpf_loader::id()); let accounts = [(program_id, program_account)]; - let message = Message::new( + let message = SanitizedMessage::Legacy(Message::new( &[Instruction::new_with_bytes(program_id, &[], vec![])], None, - ); + )); let mut invoke_context = InvokeContext::new_mock(&accounts, &[]); invoke_context - .push(&message, &message.instructions[0], &[0], &[]) + .push(&message, &message.instructions()[0], &[0], &[]) .unwrap(); let address = bpf_loader_upgradeable::id(); @@ -3950,13 +3951,13 @@ mod tests { let program_id = Pubkey::new_unique(); let program_account = AccountSharedData::new_ref(0, 0, &bpf_loader::id()); let accounts = [(program_id, program_account)]; - let message = Message::new( + let message = SanitizedMessage::Legacy(Message::new( &[Instruction::new_with_bytes(program_id, &[], vec![])], None, - ); + )); let mut invoke_context = InvokeContext::new_mock(&accounts, &[]); invoke_context - .push(&message, &message.instructions[0], &[0], &[]) + .push(&message, &message.instructions()[0], &[0], &[]) .unwrap(); let cost = invoke_context .get_compute_budget() diff --git a/programs/stake/src/stake_instruction.rs b/programs/stake/src/stake_instruction.rs index e9fee49396719f..4c3d5b67b92bdb 100644 --- a/programs/stake/src/stake_instruction.rs +++ b/programs/stake/src/stake_instruction.rs @@ -435,7 +435,7 @@ mod tests { invoke_context.sysvars = &sysvars; invoke_context.push( &preparation.message, - &preparation.message.instructions[0], + &preparation.message.instructions()[0], &program_indices, &preparation.account_indices, )?; @@ -1082,7 +1082,7 @@ mod tests { invoke_context .push( &preparation.message, - &preparation.message.instructions[0], + &preparation.message.instructions()[0], &program_indices, &preparation.account_indices, ) diff --git a/rbpf-cli/src/main.rs b/rbpf-cli/src/main.rs index ee40c6e5b2a4a6..c7d863ed1dd37d 100644 --- a/rbpf-cli/src/main.rs +++ b/rbpf-cli/src/main.rs @@ -211,7 +211,7 @@ native machine code before execting it in the virtual machine.", invoke_context .push( &preparation.message, - &preparation.message.instructions[0], + &preparation.message.instructions()[0], &program_indices, &preparation.account_indices, ) diff --git a/runtime/src/accounts.rs b/runtime/src/accounts.rs index e06819bf9370a8..78da9e95e44238 100644 --- a/runtime/src/accounts.rs +++ b/runtime/src/accounts.rs @@ -22,6 +22,7 @@ use { }, log::*, rand::{thread_rng, Rng}, + solana_address_lookup_table_program::state::AddressLookupTable, solana_sdk::{ account::{Account, AccountSharedData, ReadableAccount, WritableAccount}, account_utils::StateMut, @@ -30,13 +31,19 @@ use { feature_set::{self, FeatureSet}, genesis_config::ClusterType, hash::Hash, - message::SanitizedMessage, + message::{ + v0::{LoadedAddresses, MessageAddressTableLookup}, + SanitizedMessage, + }, native_loader, nonce::{state::Versions as NonceVersions, State as NonceState}, pubkey::Pubkey, system_program, - sysvar::{self, instructions::construct_instructions_data}, - transaction::{Result, SanitizedTransaction, TransactionAccountLocks, TransactionError}, + sysvar::{self, instructions::construct_instructions_data, slot_hashes::SlotHashes}, + transaction::{ + AddressLookupError, Result, SanitizedTransaction, TransactionAccountLocks, + TransactionError, + }, }, std::{ cmp::Reverse, @@ -223,6 +230,40 @@ impl Accounts { }) } + pub fn load_lookup_table_addresses( + &self, + ancestors: &Ancestors, + address_table_lookup: &MessageAddressTableLookup, + slot_hashes: &SlotHashes, + ) -> std::result::Result { + let table_account = self + .accounts_db + .load_with_fixed_root(ancestors, &address_table_lookup.account_key) + .map(|(account, _rent)| account) + .ok_or(AddressLookupError::LookupTableAccountNotFound)?; + + if table_account.owner() == &solana_address_lookup_table_program::id() { + let current_slot = ancestors.max_slot(); + let lookup_table = AddressLookupTable::deserialize(table_account.data()) + .map_err(|_ix_err| AddressLookupError::InvalidAccountData)?; + + Ok(LoadedAddresses { + writable: lookup_table.lookup( + current_slot, + &address_table_lookup.writable_indexes, + slot_hashes, + )?, + readonly: lookup_table.lookup( + current_slot, + &address_table_lookup.readonly_indexes, + slot_hashes, + )?, + }) + } else { + Err(AddressLookupError::InvalidAccountOwner) + } + } + fn load_transaction( &self, ancestors: &Ancestors, @@ -1242,6 +1283,7 @@ mod tests { bank::{DurableNonceFee, TransactionExecutionDetails}, rent_collector::RentCollector, }, + solana_address_lookup_table_program::state::LookupTableMeta, solana_sdk::{ account::{AccountSharedData, WritableAccount}, epoch_schedule::EpochSchedule, @@ -1256,6 +1298,7 @@ mod tests { transaction::{Transaction, MAX_TX_ACCOUNT_LOCKS}, }, std::{ + borrow::Cow, convert::TryFrom, sync::atomic::{AtomicBool, AtomicU64, Ordering}, thread, time, @@ -1836,6 +1879,149 @@ mod tests { } } + #[test] + fn test_load_lookup_table_addresses_account_not_found() { + let ancestors = vec![(0, 0)].into_iter().collect(); + let accounts = Accounts::new_with_config_for_tests( + Vec::new(), + &ClusterType::Development, + AccountSecondaryIndexes::default(), + false, + AccountShrinkThreshold::default(), + ); + + let invalid_table_key = Pubkey::new_unique(); + let address_table_lookup = MessageAddressTableLookup { + account_key: invalid_table_key, + writable_indexes: vec![], + readonly_indexes: vec![], + }; + + assert_eq!( + accounts.load_lookup_table_addresses( + &ancestors, + &address_table_lookup, + &SlotHashes::default(), + ), + Err(AddressLookupError::LookupTableAccountNotFound), + ); + } + + #[test] + fn test_load_lookup_table_addresses_invalid_account_owner() { + let ancestors = vec![(0, 0)].into_iter().collect(); + let accounts = Accounts::new_with_config_for_tests( + Vec::new(), + &ClusterType::Development, + AccountSecondaryIndexes::default(), + false, + AccountShrinkThreshold::default(), + ); + + let invalid_table_key = Pubkey::new_unique(); + let invalid_table_account = AccountSharedData::default(); + accounts.store_slow_uncached(0, &invalid_table_key, &invalid_table_account); + + let address_table_lookup = MessageAddressTableLookup { + account_key: invalid_table_key, + writable_indexes: vec![], + readonly_indexes: vec![], + }; + + assert_eq!( + accounts.load_lookup_table_addresses( + &ancestors, + &address_table_lookup, + &SlotHashes::default(), + ), + Err(AddressLookupError::InvalidAccountOwner), + ); + } + + #[test] + fn test_load_lookup_table_addresses_invalid_account_data() { + let ancestors = vec![(0, 0)].into_iter().collect(); + let accounts = Accounts::new_with_config_for_tests( + Vec::new(), + &ClusterType::Development, + AccountSecondaryIndexes::default(), + false, + AccountShrinkThreshold::default(), + ); + + let invalid_table_key = Pubkey::new_unique(); + let invalid_table_account = + AccountSharedData::new(1, 0, &solana_address_lookup_table_program::id()); + accounts.store_slow_uncached(0, &invalid_table_key, &invalid_table_account); + + let address_table_lookup = MessageAddressTableLookup { + account_key: invalid_table_key, + writable_indexes: vec![], + readonly_indexes: vec![], + }; + + assert_eq!( + accounts.load_lookup_table_addresses( + &ancestors, + &address_table_lookup, + &SlotHashes::default(), + ), + Err(AddressLookupError::InvalidAccountData), + ); + } + + #[test] + fn test_load_lookup_table_addresses() { + let ancestors = vec![(1, 1), (0, 0)].into_iter().collect(); + let accounts = Accounts::new_with_config_for_tests( + Vec::new(), + &ClusterType::Development, + AccountSecondaryIndexes::default(), + false, + AccountShrinkThreshold::default(), + ); + + let table_key = Pubkey::new_unique(); + let table_addresses = vec![Pubkey::new_unique(), Pubkey::new_unique()]; + let table_account = { + let table_state = AddressLookupTable { + meta: LookupTableMeta::default(), + addresses: Cow::Owned(table_addresses.clone()), + }; + let table_data = { + let mut data = vec![]; + table_state.serialize_for_tests(&mut data).unwrap(); + data + }; + AccountSharedData::create( + 1, + table_data, + solana_address_lookup_table_program::id(), + false, + 0, + ) + }; + accounts.store_slow_uncached(0, &table_key, &table_account); + + let address_table_lookup = MessageAddressTableLookup { + account_key: table_key, + writable_indexes: vec![0], + readonly_indexes: vec![1], + }; + + assert_eq!( + accounts.load_lookup_table_addresses( + &ancestors, + &address_table_lookup, + &SlotHashes::default(), + ), + Ok(LoadedAddresses { + writable: vec![table_addresses[0]], + readonly: vec![table_addresses[1]], + }), + ); + } + #[test] fn test_load_by_program_slot() { let accounts = Accounts::new_with_config_for_tests( diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 8f59afca130820..b6f6bc4526095b 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -111,7 +111,10 @@ use { inflation::Inflation, instruction::CompiledInstruction, lamports::LamportsError, - message::SanitizedMessage, + message::{ + v0::{LoadedAddresses, MessageAddressTableLookup}, + SanitizedMessage, + }, native_loader, native_token::sol_to_lamports, nonce, nonce_account, @@ -127,7 +130,7 @@ use { sysvar::{self, Sysvar, SysvarId}, timing::years_as_slots, transaction::{ - Result, SanitizedTransaction, Transaction, TransactionError, + AddressLookupError, Result, SanitizedTransaction, Transaction, TransactionError, TransactionVerificationMode, VersionedTransaction, }, }, @@ -243,7 +246,7 @@ impl ExecuteTimings { } type BankStatusCache = StatusCache>; -#[frozen_abi(digest = "6XG6H1FChrDdY39K62KFWj5XfDao4dd24WZgcJkdMu1E")] +#[frozen_abi(digest = "2r36f5cfgP7ABq7D3kRkRfQZWdggGFUnnhwTrVEWhoTC")] pub type BankSlotDelta = SlotDelta>; // Eager rent collection repeats in cyclic manner. @@ -3597,6 +3600,44 @@ impl Bank { cache.remove(pubkey); } + /// Get the value of a cached sysvar by its id + pub fn get_cached_sysvar(&self, id: &Pubkey) -> Option { + self.sysvar_cache + .read() + .unwrap() + .iter() + .find_map(|(key, data)| { + if id == key { + bincode::deserialize(data).ok() + } else { + None + } + }) + } + + pub fn load_lookup_table_addresses( + &self, + address_table_lookups: &[MessageAddressTableLookup], + ) -> Result { + if !self.versioned_tx_message_enabled() { + return Err(TransactionError::UnsupportedVersion); + } + + let slot_hashes: SlotHashes = self + .get_cached_sysvar(&sysvar::slot_hashes::id()) + .ok_or(TransactionError::AccountNotFound)?; + Ok(address_table_lookups + .iter() + .map(|address_table_lookup| { + self.rc.accounts.load_lookup_table_addresses( + &self.ancestors, + address_table_lookup, + &slot_hashes, + ) + }) + .collect::>()?) + } + /// Execute a transaction using the provided loaded accounts and update /// the executors cache if the transaction was successful. fn execute_loaded_transaction( @@ -3610,16 +3651,6 @@ impl Bank { execute_details_timings: &mut ExecuteDetailsTimings, error_counters: &mut ErrorCounters, ) -> TransactionExecutionResult { - let legacy_message = match tx.message().legacy_message() { - Some(message) => message, - None => { - // TODO: support versioned messages - return TransactionExecutionResult::NotExecuted( - TransactionError::UnsupportedVersion, - ); - } - }; - let executors = self.get_executors( tx.message(), &loaded_transaction.accounts, @@ -3646,7 +3677,7 @@ impl Bank { let (blockhash, lamports_per_signature) = self.last_blockhash_and_lamports_per_signature(); let process_result = MessageProcessor::process_message( &self.builtin_programs.vec, - legacy_message, + tx.message(), &loaded_transaction.program_indices, &account_refcells, self.rent_collector.rent, @@ -5375,8 +5406,8 @@ impl Bank { tx.message.hash() }; - SanitizedTransaction::try_create(tx, message_hash, None, |_| { - Err(TransactionError::UnsupportedVersion) + SanitizedTransaction::try_create(tx, message_hash, None, |lookups| { + self.load_lookup_table_addresses(lookups) }) }?; diff --git a/runtime/src/message_processor.rs b/runtime/src/message_processor.rs index 8667a7f3ccc3b6..1c828392618c1e 100644 --- a/runtime/src/message_processor.rs +++ b/runtime/src/message_processor.rs @@ -15,7 +15,7 @@ use { compute_budget::ComputeBudget, feature_set::{prevent_calling_precompiles_as_programs, FeatureSet}, hash::Hash, - message::Message, + message::SanitizedMessage, precompiles::is_precompile, pubkey::Pubkey, rent::Rent, @@ -53,7 +53,7 @@ impl MessageProcessor { #[allow(clippy::too_many_arguments)] pub fn process_message( builtin_programs: &[BuiltinProgram], - message: &Message, + message: &SanitizedMessage, program_indices: &[Vec], accounts: &[TransactionAccountRefCell], rent: Rent, @@ -82,14 +82,12 @@ impl MessageProcessor { current_accounts_data_len, ); - debug_assert_eq!(program_indices.len(), message.instructions.len()); - for (instruction_index, (instruction, program_indices)) in message - .instructions - .iter() + debug_assert_eq!(program_indices.len(), message.instructions().len()); + for (instruction_index, ((program_id, instruction), program_indices)) in message + .program_instructions_iter() .zip(program_indices.iter()) .enumerate() { - let program_id = instruction.program_id(&message.account_keys); if invoke_context .feature_set .is_active(&prevent_calling_precompiles_as_programs::id()) @@ -101,7 +99,7 @@ impl MessageProcessor { // Fixup the special instructions key if present // before the account pre-values are taken care of - for (pubkey, account) in accounts.iter().take(message.account_keys.len()) { + for (pubkey, account) in accounts.iter().take(message.account_keys_len()) { if instructions::check_id(pubkey) { let mut mut_account_ref = account.borrow_mut(); instructions::store_current_index( @@ -123,7 +121,7 @@ impl MessageProcessor { } = invoke_context.process_instruction(message, instruction, program_indices, &[], &[]); time.stop(); timings.accumulate_program( - instruction.program_id(&message.account_keys), + program_id, time.as_us(), compute_units_consumed, result.is_err(), @@ -235,14 +233,14 @@ mod tests { AccountMeta::new(accounts[0].0, true), AccountMeta::new_readonly(accounts[1].0, false), ]; - let message = Message::new( + let message = SanitizedMessage::Legacy(Message::new( &[Instruction::new_with_bincode( mock_system_program_id, &MockSystemInstruction::Correct, account_metas.clone(), )], Some(&accounts[0].0), - ); + )); let result = MessageProcessor::process_message( builtin_programs, @@ -265,14 +263,14 @@ mod tests { assert_eq!(accounts[0].1.borrow().lamports(), 100); assert_eq!(accounts[1].1.borrow().lamports(), 0); - let message = Message::new( + let message = SanitizedMessage::Legacy(Message::new( &[Instruction::new_with_bincode( mock_system_program_id, &MockSystemInstruction::AttemptCredit { lamports: 50 }, account_metas.clone(), )], Some(&accounts[0].0), - ); + )); let result = MessageProcessor::process_message( builtin_programs, @@ -299,14 +297,14 @@ mod tests { )) ); - let message = Message::new( + let message = SanitizedMessage::Legacy(Message::new( &[Instruction::new_with_bincode( mock_system_program_id, &MockSystemInstruction::AttemptDataChange { data: 50 }, account_metas, )], Some(&accounts[0].0), - ); + )); let result = MessageProcessor::process_message( builtin_programs, @@ -445,14 +443,14 @@ mod tests { ]; // Try to borrow mut the same account - let message = Message::new( + let message = SanitizedMessage::Legacy(Message::new( &[Instruction::new_with_bincode( mock_program_id, &MockSystemInstruction::BorrowFail, account_metas.clone(), )], Some(&accounts[0].0), - ); + )); let result = MessageProcessor::process_message( builtin_programs, &message, @@ -479,14 +477,14 @@ mod tests { ); // Try to borrow mut the same account in a safe way - let message = Message::new( + let message = SanitizedMessage::Legacy(Message::new( &[Instruction::new_with_bincode( mock_program_id, &MockSystemInstruction::MultiBorrowMut, account_metas.clone(), )], Some(&accounts[0].0), - ); + )); let result = MessageProcessor::process_message( builtin_programs, &message, @@ -507,7 +505,7 @@ mod tests { assert!(result.is_ok()); // Do work on the same account but at different location in keyed_accounts[] - let message = Message::new( + let message = SanitizedMessage::Legacy(Message::new( &[Instruction::new_with_bincode( mock_program_id, &MockSystemInstruction::DoWork { @@ -517,7 +515,7 @@ mod tests { account_metas, )], Some(&accounts[0].0), - ); + )); let result = MessageProcessor::process_message( builtin_programs, &message, @@ -565,7 +563,7 @@ mod tests { (mock_program_id, mock_program_account), ]; - let message = Message::new( + let message = SanitizedMessage::Legacy(Message::new( &[ new_secp256k1_instruction( &libsecp256k1::SecretKey::random(&mut rand::thread_rng()), @@ -574,7 +572,7 @@ mod tests { Instruction::new_with_bytes(mock_program_id, &[], vec![]), ], None, - ); + )); let result = MessageProcessor::process_message( builtin_programs, diff --git a/sdk/program/src/message/versions/mod.rs b/sdk/program/src/message/versions/mod.rs index 9242731af611bb..ffb571579b585b 100644 --- a/sdk/program/src/message/versions/mod.rs +++ b/sdk/program/src/message/versions/mod.rs @@ -43,21 +43,28 @@ impl VersionedMessage { } } - pub fn unmapped_keys(self) -> Vec { + pub fn static_account_keys(&self) -> &[Pubkey] { + match self { + Self::Legacy(message) => &message.account_keys, + Self::V0(message) => &message.account_keys, + } + } + + pub fn into_static_account_keys(self) -> Vec { match self { Self::Legacy(message) => message.account_keys, Self::V0(message) => message.account_keys, } } - pub fn unmapped_keys_iter(&self) -> impl Iterator { + pub fn static_account_keys_iter(&self) -> impl Iterator { match self { Self::Legacy(message) => message.account_keys.iter(), Self::V0(message) => message.account_keys.iter(), } } - pub fn unmapped_keys_len(&self) -> usize { + pub fn static_account_keys_len(&self) -> usize { match self { Self::Legacy(message) => message.account_keys.len(), Self::V0(message) => message.account_keys.len(), diff --git a/sdk/program/src/message/versions/v0/loaded.rs b/sdk/program/src/message/versions/v0/loaded.rs index 7bb62b8b0b3fe2..1c82f2960f4283 100644 --- a/sdk/program/src/message/versions/v0/loaded.rs +++ b/sdk/program/src/message/versions/v0/loaded.rs @@ -5,7 +5,7 @@ use { pubkey::Pubkey, sysvar, }, - std::{collections::HashSet, ops::Deref, convert::TryFrom}, + std::{collections::HashSet, ops::Deref}, }; /// Combination of a version #0 message and its loaded addresses @@ -34,6 +34,19 @@ pub struct LoadedAddresses { pub readonly: Vec, } +impl FromIterator for LoadedAddresses { + fn from_iter>(iter: T) -> Self { + let (writable, readonly): (Vec>, Vec>) = iter + .into_iter() + .map(|addresses| (addresses.writable, addresses.readonly)) + .unzip(); + LoadedAddresses { + writable: writable.into_iter().flatten().collect(), + readonly: readonly.into_iter().flatten().collect(), + } + } +} + impl LoadedMessage { /// Returns an iterator of account key segments. The ordering of segments /// affects how account indexes from compiled instructions are resolved and @@ -68,8 +81,9 @@ impl LoadedMessage { } /// Returns the address of the account at the specified index of the list of - /// message account keys constructed from unmapped keys, followed by mapped - /// writable addresses, and lastly the list of mapped readonly addresses. + /// message account keys constructed from static keys, followed by dynamically + /// loaded writable addresses, and lastly the list of dynamically loaded + /// readonly addresses. pub fn get_account_key(&self, mut index: usize) -> Option<&Pubkey> { for key_segment in self.account_keys_segment_iter() { if index < key_segment.len() { @@ -88,8 +102,8 @@ impl LoadedMessage { let num_account_keys = self.message.account_keys.len(); let num_signed_accounts = usize::from(header.num_required_signatures); if key_index >= num_account_keys { - let mapped_addresses_index = key_index.saturating_sub(num_account_keys); - mapped_addresses_index < self.loaded_addresses.writable.len() + let loaded_addresses_index = key_index.saturating_sub(num_account_keys); + loaded_addresses_index < self.loaded_addresses.writable.len() } else if key_index >= num_signed_accounts { let num_unsigned_accounts = num_account_keys.saturating_sub(num_signed_accounts); let num_writable_unsigned_accounts = num_unsigned_accounts diff --git a/sdk/src/transaction/error.rs b/sdk/src/transaction/error.rs index 075635c21d24c3..a207b91951b41e 100644 --- a/sdk/src/transaction/error.rs +++ b/sdk/src/transaction/error.rs @@ -109,6 +109,22 @@ pub enum TransactionError { /// Transaction locked too many accounts #[error("Transaction locked too many accounts")] TooManyAccountLocks, + + /// Address lookup table not found + #[error("Transaction loads an address table account that doesn't exist")] + AddressLookupTableNotFound, + + /// Attempted to lookup addresses from an account owned by the wrong program + #[error("Transaction loads an address table account with an invalid owner")] + InvalidAddressLookupTableOwner, + + /// Attempted to lookup addresses from an invalid account + #[error("Transaction loads an address table account with invalid data")] + InvalidAddressLookupTableData, + + /// Address table lookup uses an invalid index + #[error("Transaction address table lookup uses an invalid index")] + InvalidAddressLookupTableIndex, } impl From for TransactionError { @@ -122,3 +138,33 @@ impl From for TransactionError { Self::SanitizeFailure } } + +#[derive(Debug, Error, PartialEq, Eq, Clone)] +pub enum AddressLookupError { + /// Attempted to lookup addresses from a table that does not exist + #[error("Attempted to lookup addresses from a table that does not exist")] + LookupTableAccountNotFound, + + /// Attempted to lookup addresses from an account owned by the wrong program + #[error("Attempted to lookup addresses from an account owned by the wrong program")] + InvalidAccountOwner, + + /// Attempted to lookup addresses from an invalid account + #[error("Attempted to lookup addresses from an invalid account")] + InvalidAccountData, + + /// Address lookup contains an invalid index + #[error("Address lookup contains an invalid index")] + InvalidLookupIndex, +} + +impl From for TransactionError { + fn from(err: AddressLookupError) -> Self { + match err { + AddressLookupError::LookupTableAccountNotFound => Self::AddressLookupTableNotFound, + AddressLookupError::InvalidAccountOwner => Self::InvalidAddressLookupTableOwner, + AddressLookupError::InvalidAccountData => Self::InvalidAddressLookupTableData, + AddressLookupError::InvalidLookupIndex => Self::InvalidAddressLookupTableIndex, + } + } +} diff --git a/sdk/src/transaction/sanitized.rs b/sdk/src/transaction/sanitized.rs index ac0b1671805788..927426bc928cb4 100644 --- a/sdk/src/transaction/sanitized.rs +++ b/sdk/src/transaction/sanitized.rs @@ -1,10 +1,9 @@ #![cfg(feature = "full")] - use { crate::{ hash::Hash, message::{ - v0::{self, LoadedAddresses}, + v0::{self, LoadedAddresses, MessageAddressTableLookup}, SanitizedMessage, VersionedMessage, }, nonce::NONCED_TX_MARKER_IX_INDEX, @@ -51,7 +50,7 @@ impl SanitizedTransaction { tx: VersionedTransaction, message_hash: Hash, is_simple_vote_tx: Option, - address_loader: impl Fn(&v0::Message) -> Result, + address_loader: impl Fn(&[MessageAddressTableLookup]) -> Result, ) -> Result { tx.sanitize()?; @@ -59,7 +58,7 @@ impl SanitizedTransaction { let message = match tx.message { VersionedMessage::Legacy(message) => SanitizedMessage::Legacy(message), VersionedMessage::V0(message) => SanitizedMessage::V0(v0::LoadedMessage { - loaded_addresses: address_loader(&message)?, + loaded_addresses: address_loader(&message.address_table_lookups)?, message, }), }; diff --git a/sdk/src/transaction/versioned.rs b/sdk/src/transaction/versioned.rs index a3f284dcc3b943..c409f1b39e9118 100644 --- a/sdk/src/transaction/versioned.rs +++ b/sdk/src/transaction/versioned.rs @@ -35,9 +35,9 @@ impl Sanitize for VersionedTransaction { return Err(SanitizeError::IndexOutOfBounds); } - // Signatures are verified before message keys are mapped so all signers - // must correspond to unmapped keys. - if self.signatures.len() > self.message.unmapped_keys_len() { + // Signatures are verified before message keys are loaded so all signers + // must correspond to static account keys. + if self.signatures.len() > self.message.static_account_keys_len() { return Err(SanitizeError::IndexOutOfBounds); } @@ -69,16 +69,28 @@ impl VersionedTransaction { /// Verify the transaction and hash its message pub fn verify_and_hash_message(&self) -> Result { let message_bytes = self.message.serialize(); - if self - .signatures + if !self + ._verify_with_results(&message_bytes) .iter() - .zip(self.message.unmapped_keys_iter()) - .map(|(signature, pubkey)| signature.verify(pubkey.as_ref(), &message_bytes)) - .any(|verified| !verified) + .all(|verify_result| *verify_result) { Err(TransactionError::SignatureFailure) } else { Ok(VersionedMessage::hash_raw_message(&message_bytes)) } } + + /// Verify the transaction and return a list of verification results + pub fn verify_with_results(&self) -> Vec { + let message_bytes = self.message.serialize(); + self._verify_with_results(&message_bytes) + } + + fn _verify_with_results(&self, message_bytes: &[u8]) -> Vec { + self.signatures + .iter() + .zip(self.message.static_account_keys_iter()) + .map(|(signature, pubkey)| signature.verify(pubkey.as_ref(), message_bytes)) + .collect() + } } diff --git a/storage-proto/proto/transaction_by_addr.proto b/storage-proto/proto/transaction_by_addr.proto index 4febf026ccb224..6b467f26949b64 100644 --- a/storage-proto/proto/transaction_by_addr.proto +++ b/storage-proto/proto/transaction_by_addr.proto @@ -47,6 +47,10 @@ enum TransactionErrorType { WOULD_EXCEED_MAX_ACCOUNT_COST_LIMIT = 20; WOULD_EXCEED_MAX_ACCOUNT_DATA_COST_LIMIT = 21; TOO_MANY_ACCOUNT_LOCKS = 22; + ADDRESS_LOOKUP_TABLE_NOT_FOUND = 23; + INVALID_ADDRESS_LOOKUP_TABLE_OWNER = 24; + INVALID_ADDRESS_LOOKUP_TABLE_DATA = 25; + INVALID_ADDRESS_LOOKUP_TABLE_INDEX = 26; } message InstructionError { diff --git a/storage-proto/src/convert.rs b/storage-proto/src/convert.rs index 0e3a6c15419279..05d1c4be2ee1d1 100644 --- a/storage-proto/src/convert.rs +++ b/storage-proto/src/convert.rs @@ -570,6 +570,10 @@ impl TryFrom for TransactionError { 20 => TransactionError::WouldExceedMaxAccountCostLimit, 21 => TransactionError::WouldExceedMaxAccountDataCostLimit, 22 => TransactionError::TooManyAccountLocks, + 23 => TransactionError::AddressLookupTableNotFound, + 24 => TransactionError::InvalidAddressLookupTableOwner, + 25 => TransactionError::InvalidAddressLookupTableData, + 26 => TransactionError::InvalidAddressLookupTableIndex, _ => return Err("Invalid TransactionError"), }) } @@ -646,6 +650,18 @@ impl From for tx_by_addr::TransactionError { TransactionError::TooManyAccountLocks => { tx_by_addr::TransactionErrorType::TooManyAccountLocks } + TransactionError::AddressLookupTableNotFound => { + tx_by_addr::TransactionErrorType::AddressLookupTableNotFound + } + TransactionError::InvalidAddressLookupTableOwner => { + tx_by_addr::TransactionErrorType::InvalidAddressLookupTableOwner + } + TransactionError::InvalidAddressLookupTableData => { + tx_by_addr::TransactionErrorType::InvalidAddressLookupTableData + } + TransactionError::InvalidAddressLookupTableIndex => { + tx_by_addr::TransactionErrorType::InvalidAddressLookupTableIndex + } } as i32, instruction_error: match transaction_error { TransactionError::InstructionError(index, ref instruction_error) => {