diff --git a/accountsdb-plugin-postgres/scripts/create_schema.sql b/accountsdb-plugin-postgres/scripts/create_schema.sql index ec0de205dee6f7..58429fabb94cb4 100644 --- a/accountsdb-plugin-postgres/scripts/create_schema.sql +++ b/accountsdb-plugin-postgres/scripts/create_schema.sql @@ -113,9 +113,10 @@ CREATE TYPE "TransactionMessage" AS ( instructions "CompiledInstruction"[] ); -CREATE TYPE "AddressMapIndexes" AS ( - writable SMALLINT[], - readonly SMALLINT[] +CREATE TYPE "TransactionMessageAddressTableLookup" AS ( + account_key: BYTEA[], + writable_indexes SMALLINT[], + readonly_indexes SMALLINT[] ); CREATE TYPE "TransactionMessageV0" AS ( @@ -123,17 +124,17 @@ CREATE TYPE "TransactionMessageV0" AS ( account_keys BYTEA[], recent_blockhash BYTEA, instructions "CompiledInstruction"[], - address_map_indexes "AddressMapIndexes"[] + address_table_lookups "TransactionMessageAddressTableLookup"[] ); -CREATE TYPE "MappedAddresses" AS ( +CREATE TYPE "LoadedAddresses" AS ( writable BYTEA[], readonly BYTEA[] ); -CREATE TYPE "MappedMessage" AS ( +CREATE TYPE "LoadedMessageV0" AS ( message "TransactionMessageV0", - mapped_addresses "MappedAddresses" + loaded_addresses "LoadedAddresses" ); -- The table storing transactions @@ -143,7 +144,7 @@ CREATE TABLE transaction ( is_vote BOOL NOT NULL, message_type SMALLINT, -- 0: legacy, 1: v0 message legacy_message "TransactionMessage", - v0_mapped_message "MappedMessage", + v0_loaded_message "LoadedMessageV0", signatures BYTEA[], message_hash BYTEA, meta "TransactionStatusMeta", diff --git a/accountsdb-plugin-postgres/scripts/drop_schema.sql b/accountsdb-plugin-postgres/scripts/drop_schema.sql index 419ab44169cb95..e5b756870d1e5e 100644 --- a/accountsdb-plugin-postgres/scripts/drop_schema.sql +++ b/accountsdb-plugin-postgres/scripts/drop_schema.sql @@ -11,12 +11,12 @@ DROP TABLE transaction; DROP TYPE "TransactionError" CASCADE; DROP TYPE "TransactionErrorCode" CASCADE; -DROP TYPE "MappedMessage" CASCADE; -DROP TYPE "MappedAddresses" CASCADE; +DROP TYPE "LoadedMessageV0" CASCADE; +DROP TYPE "LoadedAddresses" CASCADE; DROP TYPE "TransactionMessageV0" CASCADE; -DROP TYPE "AddressMapIndexes" CASCADE; DROP TYPE "TransactionMessage" CASCADE; DROP TYPE "TransactionMessageHeader" CASCADE; +DROP TYPE "TransactionMessageAddressTableLookup" CASCADE; DROP TYPE "TransactionStatusMeta" CASCADE; DROP TYPE "RewardType" CASCADE; DROP TYPE "Reward" CASCADE; 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 bdc30b158c83ad..5aa7e27851f9c8 100644 --- a/accountsdb-plugin-postgres/src/postgres_client/postgres_client_transaction.rs +++ b/accountsdb-plugin-postgres/src/postgres_client/postgres_client_transaction.rs @@ -18,8 +18,8 @@ use { solana_sdk::{ instruction::CompiledInstruction, message::{ - v0::{self, AddressMapIndexes}, - MappedAddresses, MappedMessage, Message, MessageHeader, SanitizedMessage, + v0::{self, LoadedAddresses, MessageAddressTableLookup}, + Message, MessageHeader, SanitizedMessage, }, transaction::TransactionError, }, @@ -105,10 +105,11 @@ pub struct DbTransactionMessage { } #[derive(Clone, Debug, ToSql)] -#[postgres(name = "AddressMapIndexes")] -pub struct DbAddressMapIndexes { - pub writable: Vec, - pub readonly: Vec, +#[postgres(name = "TransactionMessageAddressTableLookup")] +pub struct DbTransactionMessageAddressTableLookup { + pub account_key: Vec, + pub writable_indexes: Vec, + pub readonly_indexes: Vec, } #[derive(Clone, Debug, ToSql)] @@ -118,21 +119,21 @@ pub struct DbTransactionMessageV0 { pub account_keys: Vec>, pub recent_blockhash: Vec, pub instructions: Vec, - pub address_map_indexes: Vec, + pub address_table_lookups: Vec, } #[derive(Clone, Debug, ToSql)] -#[postgres(name = "MappedAddresses")] -pub struct DbMappedAddresses { +#[postgres(name = "LoadedAddresses")] +pub struct DbLoadedAddresses { pub writable: Vec>, pub readonly: Vec>, } #[derive(Clone, Debug, ToSql)] -#[postgres(name = "MappedMessage")] -pub struct DbMappedMessage { +#[postgres(name = "LoadedMessageV0")] +pub struct DbLoadedMessageV0 { pub message: DbTransactionMessageV0, - pub mapped_addresses: DbMappedAddresses, + pub loaded_addresses: DbLoadedAddresses, } pub struct DbTransaction { @@ -141,7 +142,7 @@ pub struct DbTransaction { pub slot: i64, pub message_type: i16, pub legacy_message: Option, - pub v0_mapped_message: Option, + pub v0_loaded_message: Option, pub message_hash: Vec, pub meta: DbTransactionStatusMeta, pub signatures: Vec>, @@ -151,32 +152,33 @@ pub struct LogTransactionRequest { pub transaction_info: DbTransaction, } -impl From<&AddressMapIndexes> for DbAddressMapIndexes { - fn from(address_map_indexes: &AddressMapIndexes) -> Self { +impl From<&MessageAddressTableLookup> for DbTransactionMessageAddressTableLookup { + fn from(address_table_lookup: &MessageAddressTableLookup) -> Self { Self { - writable: address_map_indexes - .writable + account_key: address_table_lookup.account_key.as_ref().to_vec(), + writable_indexes: address_table_lookup + .writable_indexes .iter() - .map(|address_idx| *address_idx as i16) + .map(|idx| *idx as i16) .collect(), - readonly: address_map_indexes - .readonly + readonly_indexes: address_table_lookup + .readonly_indexes .iter() - .map(|address_idx| *address_idx as i16) + .map(|idx| *idx as i16) .collect(), } } } -impl From<&MappedAddresses> for DbMappedAddresses { - fn from(mapped_addresses: &MappedAddresses) -> Self { +impl From<&LoadedAddresses> for DbLoadedAddresses { + fn from(loaded_addresses: &LoadedAddresses) -> Self { Self { - writable: mapped_addresses + writable: loaded_addresses .writable .iter() .map(|pubkey| pubkey.as_ref().to_vec()) .collect(), - readonly: mapped_addresses + readonly: loaded_addresses .readonly .iter() .map(|pubkey| pubkey.as_ref().to_vec()) @@ -243,20 +245,20 @@ impl From<&v0::Message> for DbTransactionMessageV0 { .iter() .map(DbCompiledInstruction::from) .collect(), - address_map_indexes: message - .address_map_indexes + address_table_lookups: message + .address_table_lookups .iter() - .map(DbAddressMapIndexes::from) + .map(DbTransactionMessageAddressTableLookup::from) .collect(), } } } -impl From<&MappedMessage> for DbMappedMessage { - fn from(message: &MappedMessage) -> Self { +impl From<&v0::LoadedMessage> for DbLoadedMessageV0 { + fn from(message: &v0::LoadedMessage) -> Self { Self { message: DbTransactionMessageV0::from(&message.message), - mapped_addresses: DbMappedAddresses::from(&message.mapped_addresses), + loaded_addresses: DbLoadedAddresses::from(&message.loaded_addresses), } } } @@ -460,8 +462,8 @@ fn build_db_transaction(slot: u64, transaction_info: &ReplicaTransactionInfo) -> } _ => None, }, - v0_mapped_message: match transaction_info.transaction.message() { - SanitizedMessage::V0(mapped_message) => Some(DbMappedMessage::from(mapped_message)), + v0_loaded_message: match transaction_info.transaction.message() { + SanitizedMessage::V0(loaded_message) => Some(DbLoadedMessageV0::from(loaded_message)), _ => None, }, signatures: transaction_info @@ -485,7 +487,7 @@ impl SimplePostgresClient { config: &AccountsDbPluginPostgresConfig, ) -> Result { let stmt = "INSERT INTO transaction AS txn (signature, is_vote, slot, message_type, legacy_message, \ - v0_mapped_message, signatures, message_hash, meta, updated_on) \ + v0_loaded_message, signatures, message_hash, meta, updated_on) \ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)"; let stmt = client.prepare(stmt); @@ -521,7 +523,7 @@ impl SimplePostgresClient { &transaction_info.slot, &transaction_info.message_type, &transaction_info.legacy_message, - &transaction_info.v0_mapped_message, + &transaction_info.v0_loaded_message, &transaction_info.signatures, &transaction_info.message_hash, &transaction_info.meta, @@ -670,42 +672,44 @@ pub(crate) mod tests { check_inner_instructions_equality(&inner_instructions, &db_inner_instructions); } - fn check_address_map_indexes_equality( - address_map_indexes: &AddressMapIndexes, - db_address_map_indexes: &DbAddressMapIndexes, + fn check_address_table_lookups_equality( + address_table_lookups: &MessageAddressTableLookup, + db_address_table_lookups: &DbTransactionMessageAddressTableLookup, ) { assert_eq!( - address_map_indexes.writable.len(), - db_address_map_indexes.writable.len() + address_table_lookups.writable_indexes.len(), + db_address_table_lookups.writable_indexes.len() ); assert_eq!( - address_map_indexes.readonly.len(), - db_address_map_indexes.readonly.len() + address_table_lookups.readonly_indexes.len(), + db_address_table_lookups.readonly_indexes.len() ); - for i in 0..address_map_indexes.writable.len() { + for i in 0..address_table_lookups.writable_indexes.len() { assert_eq!( - address_map_indexes.writable[i], - db_address_map_indexes.writable[i] as u8 + address_table_lookups.writable_indexes[i], + db_address_table_lookups.writable_indexes[i] as u8 ) } - for i in 0..address_map_indexes.readonly.len() { + for i in 0..address_table_lookups.readonly_indexes.len() { assert_eq!( - address_map_indexes.readonly[i], - db_address_map_indexes.readonly[i] as u8 + address_table_lookups.readonly_indexes[i], + db_address_table_lookups.readonly_indexes[i] as u8 ) } } #[test] - fn test_transform_address_map_indexes() { - let address_map_indexes = AddressMapIndexes { - writable: vec![1, 2, 3], - readonly: vec![4, 5, 6], + fn test_transform_address_table_lookups() { + let address_table_lookups = MessageAddressTableLookup { + account_key: Pubkey::new_unique(), + writable_indexes: vec![1, 2, 3], + readonly_indexes: vec![4, 5, 6], }; - let db_address_map_indexes = DbAddressMapIndexes::from(&address_map_indexes); - check_address_map_indexes_equality(&address_map_indexes, &db_address_map_indexes); + let db_address_table_lookups = + DbTransactionMessageAddressTableLookup::from(&address_table_lookups); + check_address_table_lookups_equality(&address_table_lookups, &db_address_table_lookups); } fn check_reward_equality(reward: &Reward, db_reward: &DbReward) { @@ -1089,7 +1093,7 @@ pub(crate) mod tests { check_transaction_message_equality(&message, &db_message); } - fn check_transaction_messagev0_equality( + fn check_transaction_message_v0_equality( message: &v0::Message, db_message: &DbTransactionMessageV0, ) { @@ -1106,18 +1110,18 @@ pub(crate) mod tests { ); } assert_eq!( - message.address_map_indexes.len(), - db_message.address_map_indexes.len() + message.address_table_lookups.len(), + db_message.address_table_lookups.len() ); - for i in 0..message.address_map_indexes.len() { - check_address_map_indexes_equality( - &message.address_map_indexes[i], - &db_message.address_map_indexes[i], + for i in 0..message.address_table_lookups.len() { + check_address_table_lookups_equality( + &message.address_table_lookups[i], + &db_message.address_table_lookups[i], ); } } - fn build_transaction_messagev0() -> v0::Message { + fn build_transaction_message_v0() -> v0::Message { v0::Message { header: MessageHeader { num_readonly_signed_accounts: 2, @@ -1144,71 +1148,76 @@ pub(crate) mod tests { data: vec![14, 15, 16], }, ], - address_map_indexes: vec![ - AddressMapIndexes { - writable: vec![0], - readonly: vec![1, 2], + address_table_lookups: vec![ + MessageAddressTableLookup { + account_key: Pubkey::new_unique(), + writable_indexes: vec![0], + readonly_indexes: vec![1, 2], }, - AddressMapIndexes { - writable: vec![1], - readonly: vec![0, 2], + MessageAddressTableLookup { + account_key: Pubkey::new_unique(), + writable_indexes: vec![1], + readonly_indexes: vec![0, 2], }, ], } } #[test] - fn test_transform_transaction_messagev0() { - let message = build_transaction_messagev0(); + fn test_transform_transaction_message_v0() { + let message = build_transaction_message_v0(); let db_message = DbTransactionMessageV0::from(&message); - check_transaction_messagev0_equality(&message, &db_message); + check_transaction_message_v0_equality(&message, &db_message); } - fn check_mapped_addresses( - mapped_addresses: &MappedAddresses, - db_mapped_addresses: &DbMappedAddresses, + fn check_loaded_addresses( + loaded_addresses: &LoadedAddresses, + db_loaded_addresses: &DbLoadedAddresses, ) { assert_eq!( - mapped_addresses.writable.len(), - db_mapped_addresses.writable.len() + loaded_addresses.writable.len(), + db_loaded_addresses.writable.len() ); - for i in 0..mapped_addresses.writable.len() { + for i in 0..loaded_addresses.writable.len() { assert_eq!( - mapped_addresses.writable[i].as_ref(), - db_mapped_addresses.writable[i] + loaded_addresses.writable[i].as_ref(), + db_loaded_addresses.writable[i] ); } assert_eq!( - mapped_addresses.readonly.len(), - db_mapped_addresses.readonly.len() + loaded_addresses.readonly.len(), + db_loaded_addresses.readonly.len() ); - for i in 0..mapped_addresses.readonly.len() { + for i in 0..loaded_addresses.readonly.len() { assert_eq!( - mapped_addresses.readonly[i].as_ref(), - db_mapped_addresses.readonly[i] + loaded_addresses.readonly[i].as_ref(), + db_loaded_addresses.readonly[i] ); } } - fn check_mapped_message_equality(message: &MappedMessage, db_message: &DbMappedMessage) { - check_transaction_messagev0_equality(&message.message, &db_message.message); - check_mapped_addresses(&message.mapped_addresses, &db_message.mapped_addresses); + fn check_loaded_message_v0_equality( + message: &v0::LoadedMessage, + db_message: &DbLoadedMessageV0, + ) { + check_transaction_message_v0_equality(&message.message, &db_message.message); + check_loaded_addresses(&message.loaded_addresses, &db_message.loaded_addresses); } #[test] - fn test_transform_mapped_message() { - let message = MappedMessage { - message: build_transaction_messagev0(), - mapped_addresses: MappedAddresses { + fn test_transform_loaded_message_v0() { + let message = v0::LoadedMessage { + message: build_transaction_message_v0(), + loaded_addresses: LoadedAddresses { writable: vec![Pubkey::new_unique(), Pubkey::new_unique()], readonly: vec![Pubkey::new_unique(), Pubkey::new_unique()], }, }; - let db_message = DbMappedMessage::from(&message); - check_mapped_message_equality(&message, &db_message); + let db_message = DbLoadedMessageV0::from(&message); + check_loaded_message_v0_equality(&message, &db_message); } fn check_transaction( @@ -1229,9 +1238,9 @@ pub(crate) mod tests { } SanitizedMessage::V0(message) => { assert_eq!(db_transaction.message_type, 1); - check_mapped_message_equality( + check_loaded_message_v0_equality( message, - db_transaction.v0_mapped_message.as_ref().unwrap(), + db_transaction.v0_loaded_message.as_ref().unwrap(), ); } } @@ -1298,7 +1307,7 @@ pub(crate) mod tests { Signature::new(&[2u8; 64]), Signature::new(&[3u8; 64]), ], - message: VersionedMessage::V0(build_transaction_messagev0()), + message: VersionedMessage::V0(build_transaction_message_v0()), } } @@ -1313,7 +1322,7 @@ pub(crate) mod tests { let transaction = SanitizedTransaction::try_create(transaction, message_hash, Some(true), |_message| { - Ok(MappedAddresses { + Ok(LoadedAddresses { writable: vec![Pubkey::new_unique(), Pubkey::new_unique()], readonly: vec![Pubkey::new_unique(), Pubkey::new_unique()], }) diff --git a/sdk/program/src/message/mod.rs b/sdk/program/src/message/mod.rs index 8c5737d38378d9..910a03c39cadbe 100644 --- a/sdk/program/src/message/mod.rs +++ b/sdk/program/src/message/mod.rs @@ -5,12 +5,10 @@ pub mod legacy; #[cfg(not(target_arch = "bpf"))] #[path = ""] mod non_bpf_modules { - mod mapped; mod sanitized; - pub mod v0; mod versions; - pub use {mapped::*, sanitized::*, versions::*}; + pub use {sanitized::*, versions::*}; } pub use legacy::Message; diff --git a/sdk/program/src/message/sanitized.rs b/sdk/program/src/message/sanitized.rs index 7a600e002243ea..25ef25c0adbfdc 100644 --- a/sdk/program/src/message/sanitized.rs +++ b/sdk/program/src/message/sanitized.rs @@ -2,7 +2,7 @@ use { crate::{ hash::Hash, instruction::{CompiledInstruction, Instruction}, - message::{MappedAddresses, MappedMessage, Message, MessageHeader}, + message::{v0::{self, LoadedAddresses}, legacy::Message as LegacyMessage, MessageHeader}, pubkey::Pubkey, sanitize::{Sanitize, SanitizeError}, serialize_utils::{append_slice, append_u16, append_u8}, @@ -17,9 +17,9 @@ use { #[derive(Debug, Clone)] pub enum SanitizedMessage { /// Sanitized legacy message - Legacy(Message), + Legacy(LegacyMessage), /// Sanitized version #0 message with mapped addresses - V0(MappedMessage), + V0(v0::LoadedMessage), } #[derive(PartialEq, Debug, Error, Eq, Clone)] @@ -44,9 +44,9 @@ impl From for SanitizeMessageError { } } -impl TryFrom for SanitizedMessage { +impl TryFrom for SanitizedMessage { type Error = SanitizeMessageError; - fn try_from(message: Message) -> Result { + fn try_from(message: LegacyMessage) -> Result { message.sanitize()?; let sanitized_msg = Self::Legacy(message); @@ -80,12 +80,12 @@ impl SanitizedMessage { pub fn header(&self) -> &MessageHeader { match self { Self::Legacy(message) => &message.header, - Self::V0(mapped_msg) => &mapped_msg.message.header, + Self::V0(message) => &message.header, } } /// Returns a legacy message if this sanitized message wraps one - pub fn legacy_message(&self) -> Option<&Message> { + pub fn legacy_message(&self) -> Option<&LegacyMessage> { if let Self::Legacy(message) = &self { Some(message) } else { @@ -103,7 +103,7 @@ impl SanitizedMessage { pub fn recent_blockhash(&self) -> &Hash { match self { Self::Legacy(message) => &message.recent_blockhash, - Self::V0(mapped_msg) => &mapped_msg.message.recent_blockhash, + Self::V0(message) => &message.recent_blockhash, } } @@ -112,7 +112,7 @@ impl SanitizedMessage { pub fn instructions(&self) -> &[CompiledInstruction] { match self { Self::Legacy(message) => &message.instructions, - Self::V0(mapped_msg) => &mapped_msg.message.instructions, + Self::V0(message) => &message.instructions, } } @@ -123,7 +123,7 @@ impl SanitizedMessage { ) -> impl Iterator { match self { Self::Legacy(message) => message.instructions.iter(), - Self::V0(mapped_msg) => mapped_msg.message.instructions.iter(), + Self::V0(message) => message.instructions.iter(), } .map(move |ix| { ( @@ -138,7 +138,7 @@ impl SanitizedMessage { pub fn account_keys_iter(&self) -> Box + '_> { match self { Self::Legacy(message) => Box::new(message.account_keys.iter()), - Self::V0(mapped_msg) => Box::new(mapped_msg.account_keys_iter()), + Self::V0(message) => Box::new(message.account_keys_iter()), } } @@ -146,7 +146,7 @@ impl SanitizedMessage { pub fn account_keys_len(&self) -> usize { match self { Self::Legacy(message) => message.account_keys.len(), - Self::V0(mapped_msg) => mapped_msg.account_keys_len(), + Self::V0(message) => message.account_keys_len(), } } @@ -251,10 +251,10 @@ impl SanitizedMessage { data } - /// Return the mapped addresses for this message if it has any. - fn mapped_addresses(&self) -> Option<&MappedAddresses> { + /// Return the resolved addresses for this message if it has any. + fn loaded_lookup_table_addresses(&self) -> Option<&LoadedAddresses> { match &self { - SanitizedMessage::V0(message) => Some(&message.mapped_addresses), + SanitizedMessage::V0(message) => Some(&message.loaded_addresses), _ => None, } } @@ -262,7 +262,7 @@ impl SanitizedMessage { /// Return the number of readonly accounts loaded by this message. pub fn num_readonly_accounts(&self) -> usize { let mapped_readonly_addresses = self - .mapped_addresses() + .loaded_lookup_table_addresses() .map(|keys| keys.readonly.len()) .unwrap_or_default(); mapped_readonly_addresses @@ -311,13 +311,13 @@ mod tests { #[test] fn test_try_from_message() { let dupe_key = Pubkey::new_unique(); - let legacy_message_with_dupes = Message { + let legacy_message_with_dupes = LegacyMessage { header: MessageHeader { num_required_signatures: 1, ..MessageHeader::default() }, account_keys: vec![dupe_key, dupe_key], - ..Message::default() + ..LegacyMessage::default() }; assert_eq!( @@ -325,9 +325,9 @@ mod tests { Some(SanitizeMessageError::DuplicateAccountKey), ); - let legacy_message_with_no_signers = Message { + let legacy_message_with_no_signers = LegacyMessage { account_keys: vec![Pubkey::new_unique()], - ..Message::default() + ..LegacyMessage::default() }; assert_eq!( @@ -346,7 +346,7 @@ mod tests { CompiledInstruction::new(2, &(), vec![0, 1]), ]; - let message = SanitizedMessage::try_from(Message::new_with_compiled_instructions( + let message = SanitizedMessage::try_from(LegacyMessage::new_with_compiled_instructions( 1, 0, 2, @@ -370,20 +370,20 @@ mod tests { let key4 = Pubkey::new_unique(); let key5 = Pubkey::new_unique(); - let legacy_message = SanitizedMessage::try_from(Message { + let legacy_message = SanitizedMessage::try_from(LegacyMessage { header: MessageHeader { num_required_signatures: 2, num_readonly_signed_accounts: 1, num_readonly_unsigned_accounts: 1, }, account_keys: vec![key0, key1, key2, key3], - ..Message::default() + ..LegacyMessage::default() }) .unwrap(); assert_eq!(legacy_message.num_readonly_accounts(), 2); - let mapped_message = SanitizedMessage::V0(MappedMessage { + let v0_message = SanitizedMessage::V0(v0::LoadedMessage { message: v0::Message { header: MessageHeader { num_required_signatures: 2, @@ -393,13 +393,13 @@ mod tests { account_keys: vec![key0, key1, key2, key3], ..v0::Message::default() }, - mapped_addresses: MappedAddresses { + loaded_addresses: LoadedAddresses { writable: vec![key4], readonly: vec![key5], }, }); - assert_eq!(mapped_message.num_readonly_accounts(), 3); + assert_eq!(v0_message.num_readonly_accounts(), 3); } #[test] @@ -427,7 +427,7 @@ mod tests { ]; let demote_program_write_locks = true; - let message = Message::new(&instructions, Some(&id1)); + let message = LegacyMessage::new(&instructions, Some(&id1)); let sanitized_message = SanitizedMessage::try_from(message.clone()).unwrap(); let serialized = sanitized_message.serialize_instructions(demote_program_write_locks); @@ -438,7 +438,7 @@ mod tests { // assert that Message::deserialize_instruction is compatible with SanitizedMessage::serialize_instructions for (i, instruction) in instructions.iter().enumerate() { assert_eq!( - Message::deserialize_instruction(i, &serialized).unwrap(), + LegacyMessage::deserialize_instruction(i, &serialized).unwrap(), *instruction ); } @@ -481,18 +481,18 @@ mod tests { data: vec![], }; - let legacy_message = SanitizedMessage::try_from(Message { + let legacy_message = SanitizedMessage::try_from(LegacyMessage { header: MessageHeader { num_required_signatures: 1, num_readonly_signed_accounts: 0, num_readonly_unsigned_accounts: 0, }, account_keys: vec![key0, key1, key2, program_id], - ..Message::default() + ..LegacyMessage::default() }) .unwrap(); - let mapped_message = SanitizedMessage::V0(MappedMessage { + let v0_message = SanitizedMessage::V0(v0::LoadedMessage { message: v0::Message { header: MessageHeader { num_required_signatures: 1, @@ -502,13 +502,13 @@ mod tests { account_keys: vec![key0, key1], ..v0::Message::default() }, - mapped_addresses: MappedAddresses { + loaded_addresses: LoadedAddresses { writable: vec![key2], readonly: vec![program_id], }, }); - for message in vec![legacy_message, mapped_message] { + for message in vec![legacy_message, v0_message] { assert_eq!( message.try_compile_instruction(&valid_instruction), Some(CompiledInstruction { diff --git a/sdk/program/src/message/v0.rs b/sdk/program/src/message/v0.rs deleted file mode 100644 index 9319d32157181d..00000000000000 --- a/sdk/program/src/message/v0.rs +++ /dev/null @@ -1,396 +0,0 @@ -use crate::{ - hash::Hash, - instruction::CompiledInstruction, - message::{MessageHeader, MESSAGE_VERSION_PREFIX}, - pubkey::Pubkey, - sanitize::{Sanitize, SanitizeError}, - short_vec, -}; - -/// Indexes that are mapped to addresses using an on-chain address map for -/// succinctly loading readonly and writable accounts. -#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)] -#[serde(rename_all = "camelCase")] -pub struct AddressMapIndexes { - #[serde(with = "short_vec")] - pub writable: Vec, - #[serde(with = "short_vec")] - pub readonly: Vec, -} - -/// Transaction message format which supports succinct account loading with -/// indexes for on-chain address maps. -#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)] -#[serde(rename_all = "camelCase")] -pub struct Message { - /// The message header, identifying signed and read-only `account_keys` - pub header: MessageHeader, - - /// List of accounts loaded by this transaction. - #[serde(with = "short_vec")] - pub account_keys: Vec, - - /// The blockhash of a recent block. - pub recent_blockhash: Hash, - - /// Instructions that invoke a designated program, are executed in sequence, - /// and committed in one atomic transaction if all succeed. - /// - /// # Notes - /// - /// Account and program indexes will index into the list of addresses - /// constructed from the concatenation of `account_keys`, flattened list of - /// `writable` address map indexes, and the flattened `readonly` address - /// map indexes. - #[serde(with = "short_vec")] - pub instructions: Vec, - - /// List of address map indexes used to succinctly load additional accounts - /// for this transaction. - /// - /// # Notes - /// - /// The last `address_map_indexes.len()` accounts of the read-only unsigned - /// accounts are loaded as address maps. - #[serde(with = "short_vec")] - pub address_map_indexes: Vec, -} - -impl Sanitize for Message { - fn sanitize(&self) -> Result<(), SanitizeError> { - // signing area and read-only non-signing area should not - // overlap - if usize::from(self.header.num_required_signatures) - .saturating_add(usize::from(self.header.num_readonly_unsigned_accounts)) - > self.account_keys.len() - { - return Err(SanitizeError::IndexOutOfBounds); - } - - // there should be at least 1 RW fee-payer account. - if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures { - return Err(SanitizeError::IndexOutOfBounds); - } - - // there cannot be more address maps than read-only unsigned accounts. - let num_address_map_indexes = self.address_map_indexes.len(); - if num_address_map_indexes > usize::from(self.header.num_readonly_unsigned_accounts) { - return Err(SanitizeError::IndexOutOfBounds); - } - - // each map must load at least one entry - let mut num_loaded_accounts = self.account_keys.len(); - for indexes in &self.address_map_indexes { - let num_loaded_map_entries = indexes - .writable - .len() - .saturating_add(indexes.readonly.len()); - - if num_loaded_map_entries == 0 { - return Err(SanitizeError::InvalidValue); - } - - num_loaded_accounts = num_loaded_accounts.saturating_add(num_loaded_map_entries); - } - - // the number of loaded accounts must be <= 256 since account indices are - // encoded as `u8` - if num_loaded_accounts > 256 { - return Err(SanitizeError::IndexOutOfBounds); - } - - for ci in &self.instructions { - if usize::from(ci.program_id_index) >= num_loaded_accounts { - return Err(SanitizeError::IndexOutOfBounds); - } - // A program cannot be a payer. - if ci.program_id_index == 0 { - return Err(SanitizeError::IndexOutOfBounds); - } - for ai in &ci.accounts { - if usize::from(*ai) >= num_loaded_accounts { - return Err(SanitizeError::IndexOutOfBounds); - } - } - } - - Ok(()) - } -} - -impl Message { - /// Serialize this message with a version #0 prefix using bincode encoding. - pub fn serialize(&self) -> Vec { - bincode::serialize(&(MESSAGE_VERSION_PREFIX, self)).unwrap() - } -} - -#[cfg(test)] -mod tests { - use {super::*, crate::message::VersionedMessage}; - - fn simple_message() -> Message { - Message { - header: MessageHeader { - num_required_signatures: 1, - num_readonly_signed_accounts: 0, - num_readonly_unsigned_accounts: 1, - }, - account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()], - address_map_indexes: vec![AddressMapIndexes { - writable: vec![], - readonly: vec![0], - }], - ..Message::default() - } - } - - fn two_map_message() -> Message { - Message { - header: MessageHeader { - num_required_signatures: 1, - num_readonly_signed_accounts: 0, - num_readonly_unsigned_accounts: 2, - }, - account_keys: vec![ - Pubkey::new_unique(), - Pubkey::new_unique(), - Pubkey::new_unique(), - ], - address_map_indexes: vec![ - AddressMapIndexes { - writable: vec![1], - readonly: vec![0], - }, - AddressMapIndexes { - writable: vec![0], - readonly: vec![1], - }, - ], - ..Message::default() - } - } - - #[test] - fn test_sanitize_account_indices() { - assert!(Message { - account_keys: (0..=u8::MAX).map(|_| Pubkey::new_unique()).collect(), - address_map_indexes: vec![], - instructions: vec![CompiledInstruction { - program_id_index: 1, - accounts: vec![u8::MAX], - data: vec![], - }], - ..simple_message() - } - .sanitize() - .is_ok()); - - assert!(Message { - account_keys: (0..u8::MAX).map(|_| Pubkey::new_unique()).collect(), - address_map_indexes: vec![], - instructions: vec![CompiledInstruction { - program_id_index: 1, - accounts: vec![u8::MAX], - data: vec![], - }], - ..simple_message() - } - .sanitize() - .is_err()); - - assert!(Message { - account_keys: (0..u8::MAX).map(|_| Pubkey::new_unique()).collect(), - instructions: vec![CompiledInstruction { - program_id_index: 1, - accounts: vec![u8::MAX], - data: vec![], - }], - ..simple_message() - } - .sanitize() - .is_ok()); - - assert!(Message { - account_keys: (0..u8::MAX - 1).map(|_| Pubkey::new_unique()).collect(), - instructions: vec![CompiledInstruction { - program_id_index: 1, - accounts: vec![u8::MAX], - data: vec![], - }], - ..simple_message() - } - .sanitize() - .is_err()); - - assert!(Message { - address_map_indexes: vec![ - AddressMapIndexes { - writable: (0..200).step_by(2).collect(), - readonly: (1..200).step_by(2).collect(), - }, - AddressMapIndexes { - writable: (0..53).step_by(2).collect(), - readonly: (1..53).step_by(2).collect(), - }, - ], - instructions: vec![CompiledInstruction { - program_id_index: 1, - accounts: vec![u8::MAX], - data: vec![], - }], - ..two_map_message() - } - .sanitize() - .is_ok()); - - assert!(Message { - address_map_indexes: vec![ - AddressMapIndexes { - writable: (0..200).step_by(2).collect(), - readonly: (1..200).step_by(2).collect(), - }, - AddressMapIndexes { - writable: (0..52).step_by(2).collect(), - readonly: (1..52).step_by(2).collect(), - }, - ], - instructions: vec![CompiledInstruction { - program_id_index: 1, - accounts: vec![u8::MAX], - data: vec![], - }], - ..two_map_message() - } - .sanitize() - .is_err()); - } - - #[test] - fn test_sanitize_excessive_loaded_accounts() { - assert!(Message { - account_keys: (0..=u8::MAX).map(|_| Pubkey::new_unique()).collect(), - address_map_indexes: vec![], - ..simple_message() - } - .sanitize() - .is_ok()); - - assert!(Message { - account_keys: (0..257).map(|_| Pubkey::new_unique()).collect(), - address_map_indexes: vec![], - ..simple_message() - } - .sanitize() - .is_err()); - - assert!(Message { - account_keys: (0..u8::MAX).map(|_| Pubkey::new_unique()).collect(), - ..simple_message() - } - .sanitize() - .is_ok()); - - assert!(Message { - account_keys: (0..256).map(|_| Pubkey::new_unique()).collect(), - ..simple_message() - } - .sanitize() - .is_err()); - - assert!(Message { - address_map_indexes: vec![ - AddressMapIndexes { - writable: (0..200).step_by(2).collect(), - readonly: (1..200).step_by(2).collect(), - }, - AddressMapIndexes { - writable: (0..53).step_by(2).collect(), - readonly: (1..53).step_by(2).collect(), - } - ], - ..two_map_message() - } - .sanitize() - .is_ok()); - - assert!(Message { - address_map_indexes: vec![ - AddressMapIndexes { - writable: (0..200).step_by(2).collect(), - readonly: (1..200).step_by(2).collect(), - }, - AddressMapIndexes { - writable: (0..200).step_by(2).collect(), - readonly: (1..200).step_by(2).collect(), - } - ], - ..two_map_message() - } - .sanitize() - .is_err()); - } - - #[test] - fn test_sanitize_excessive_maps() { - assert!(Message { - header: MessageHeader { - num_readonly_unsigned_accounts: 1, - ..simple_message().header - }, - ..simple_message() - } - .sanitize() - .is_ok()); - - assert!(Message { - header: MessageHeader { - num_readonly_unsigned_accounts: 0, - ..simple_message().header - }, - ..simple_message() - } - .sanitize() - .is_err()); - } - - #[test] - fn test_sanitize_address_map() { - assert!(Message { - address_map_indexes: vec![AddressMapIndexes { - writable: vec![0], - readonly: vec![], - }], - ..simple_message() - } - .sanitize() - .is_ok()); - - assert!(Message { - address_map_indexes: vec![AddressMapIndexes { - writable: vec![], - readonly: vec![0], - }], - ..simple_message() - } - .sanitize() - .is_ok()); - - assert!(Message { - address_map_indexes: vec![AddressMapIndexes { - writable: vec![], - readonly: vec![], - }], - ..simple_message() - } - .sanitize() - .is_err()); - } - - #[test] - fn test_serialize() { - let message = simple_message(); - let versioned_msg = VersionedMessage::V0(message.clone()); - assert_eq!(message.serialize(), versioned_msg.serialize()); - } -} diff --git a/sdk/program/src/message/versions.rs b/sdk/program/src/message/versions/mod.rs similarity index 91% rename from sdk/program/src/message/versions.rs rename to sdk/program/src/message/versions/mod.rs index 1ec621d74c0d50..9242731af611bb 100644 --- a/sdk/program/src/message/versions.rs +++ b/sdk/program/src/message/versions/mod.rs @@ -2,7 +2,7 @@ use { crate::{ hash::Hash, instruction::CompiledInstruction, - message::{v0, Message, MessageHeader}, + message::{legacy::Message as LegacyMessage, MessageHeader}, pubkey::Pubkey, sanitize::{Sanitize, SanitizeError}, short_vec, @@ -15,6 +15,8 @@ use { std::fmt, }; +pub mod v0; + /// Bit mask that indicates whether a serialized message is versioned. pub const MESSAGE_VERSION_PREFIX: u8 = 0x80; @@ -26,10 +28,10 @@ pub const MESSAGE_VERSION_PREFIX: u8 = 0x80; /// which message version is serialized starting from version `0`. If the first /// is bit is not set, all bytes are used to encode the legacy `Message` /// format. -#[frozen_abi(digest = "x2F3RG2RhJQWN6L2N3jebvcAvNYFrhE3sKTPJ4sENvL")] +#[frozen_abi(digest = "G4EAiqmGgBprgf5ePYemLJcoFfx4R7rhC1Weo2FVJ7fn")] #[derive(Debug, PartialEq, Eq, Clone, AbiEnumVisitor, AbiExample)] pub enum VersionedMessage { - Legacy(Message), + Legacy(LegacyMessage), V0(v0::Message), } @@ -98,7 +100,7 @@ impl VersionedMessage { impl Default for VersionedMessage { fn default() -> Self { - Self::Legacy(Message::default()) + Self::Legacy(LegacyMessage::default()) } } @@ -206,7 +208,7 @@ impl<'de> Deserialize<'de> for VersionedMessage { de::Error::invalid_length(1, &self) })?; - Ok(VersionedMessage::Legacy(Message { + Ok(VersionedMessage::Legacy(LegacyMessage { header: MessageHeader { num_required_signatures, num_readonly_signed_accounts: message.num_readonly_signed_accounts, @@ -247,7 +249,7 @@ mod tests { super::*, crate::{ instruction::{AccountMeta, Instruction}, - message::v0::AddressMapIndexes, + message::v0::MessageAddressTableLookup, }, }; @@ -274,7 +276,7 @@ mod tests { ), ]; - let mut message = Message::new(&instructions, Some(&id1)); + let mut message = LegacyMessage::new(&instructions, Some(&id1)); message.recent_blockhash = Hash::new_unique(); let bytes1 = bincode::serialize(&message).unwrap(); @@ -282,7 +284,7 @@ mod tests { assert_eq!(bytes1, bytes2); - let message1: Message = bincode::deserialize(&bytes1).unwrap(); + let message1: LegacyMessage = bincode::deserialize(&bytes1).unwrap(); let message2: VersionedMessage = bincode::deserialize(&bytes2).unwrap(); if let VersionedMessage::Legacy(message2) = message2 { @@ -299,27 +301,27 @@ mod tests { header: MessageHeader { num_required_signatures: 1, num_readonly_signed_accounts: 0, - num_readonly_unsigned_accounts: 2, + num_readonly_unsigned_accounts: 0, }, recent_blockhash: Hash::new_unique(), account_keys: vec![ Pubkey::new_unique(), - Pubkey::new_unique(), - Pubkey::new_unique(), ], - address_map_indexes: vec![ - AddressMapIndexes { - writable: vec![1], - readonly: vec![0], + address_table_lookups: vec![ + MessageAddressTableLookup { + account_key: Pubkey::new_unique(), + writable_indexes: vec![1], + readonly_indexes: vec![0], }, - AddressMapIndexes { - writable: vec![0], - readonly: vec![1], + MessageAddressTableLookup { + account_key: Pubkey::new_unique(), + writable_indexes: vec![0], + readonly_indexes: vec![1], }, ], instructions: vec![CompiledInstruction { program_id_index: 1, - accounts: vec![0], + accounts: vec![0, 2, 3, 4], data: vec![], }], }; diff --git a/sdk/program/src/message/mapped.rs b/sdk/program/src/message/versions/v0/loaded.rs similarity index 81% rename from sdk/program/src/message/mapped.rs rename to sdk/program/src/message/versions/v0/loaded.rs index 4c60e4fd5d3c6a..d0b39fcbdcaa5d 100644 --- a/sdk/program/src/message/mapped.rs +++ b/sdk/program/src/message/versions/v0/loaded.rs @@ -5,37 +5,44 @@ use { pubkey::Pubkey, sysvar, }, - std::{collections::HashSet, convert::TryFrom}, + std::{collections::HashSet, ops::Deref, convert::TryFrom}, }; -/// Combination of a version #0 message and its mapped addresses +/// Combination of a version #0 message and its loaded addresses #[derive(Debug, Clone)] -pub struct MappedMessage { - /// Message which loaded a collection of mapped addresses +pub struct LoadedMessage { + /// Message which loaded a collection of lookup table addresses pub message: v0::Message, - /// Collection of mapped addresses loaded by this message - pub mapped_addresses: MappedAddresses, + /// Addresses loaded with on-chain address lookup tables + pub loaded_addresses: LoadedAddresses, } -/// Collection of mapped addresses loaded succinctly by a transaction using -/// on-chain address map accounts. +impl Deref for LoadedMessage { + type Target = v0::Message; + fn deref(&self) -> &Self::Target { + &self.message + } +} + +/// Collection of addresses loaded from on-chain lookup tables, split +/// by readonly and writable. #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] -pub struct MappedAddresses { +pub struct LoadedAddresses { /// List of addresses for writable loaded accounts pub writable: Vec, /// List of addresses for read-only loaded accounts pub readonly: Vec, } -impl MappedMessage { +impl LoadedMessage { /// Returns an iterator of account key segments. The ordering of segments /// affects how account indexes from compiled instructions are resolved and /// so should not be changed. fn account_keys_segment_iter(&self) -> impl Iterator> { vec![ &self.message.account_keys, - &self.mapped_addresses.writable, - &self.mapped_addresses.readonly, + &self.loaded_addresses.writable, + &self.loaded_addresses.readonly, ] .into_iter() } @@ -82,7 +89,7 @@ impl MappedMessage { 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.mapped_addresses.writable.len() + mapped_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 @@ -138,7 +145,7 @@ mod tests { itertools::Itertools, }; - fn create_test_mapped_message() -> (MappedMessage, [Pubkey; 6]) { + fn check_test_loaded_message() -> (LoadedMessage, [Pubkey; 6]) { let key0 = Pubkey::new_unique(); let key1 = Pubkey::new_unique(); let key2 = Pubkey::new_unique(); @@ -146,7 +153,7 @@ mod tests { let key4 = Pubkey::new_unique(); let key5 = Pubkey::new_unique(); - let message = MappedMessage { + let message = LoadedMessage { message: v0::Message { header: MessageHeader { num_required_signatures: 2, @@ -156,7 +163,7 @@ mod tests { account_keys: vec![key0, key1, key2, key3], ..v0::Message::default() }, - mapped_addresses: MappedAddresses { + loaded_addresses: LoadedAddresses { writable: vec![key4], readonly: vec![key5], }, @@ -167,7 +174,7 @@ mod tests { #[test] fn test_account_keys_segment_iter() { - let (message, keys) = create_test_mapped_message(); + let (message, keys) = check_test_loaded_message(); let expected_segments = vec![ vec![keys[0], keys[1], keys[2], keys[3]], @@ -183,14 +190,14 @@ mod tests { #[test] fn test_account_keys_len() { - let (message, keys) = create_test_mapped_message(); + let (message, keys) = check_test_loaded_message(); assert_eq!(message.account_keys_len(), keys.len()); } #[test] fn test_account_keys_iter() { - let (message, keys) = create_test_mapped_message(); + let (message, keys) = check_test_loaded_message(); let mut iter = message.account_keys_iter(); for expected_key in keys { @@ -200,19 +207,19 @@ mod tests { #[test] fn test_has_duplicates() { - let message = create_test_mapped_message().0; + let message = check_test_loaded_message().0; assert!(!message.has_duplicates()); } #[test] fn test_has_duplicates_with_dupe_keys() { - let create_message_with_dupe_keys = |mut keys: Vec| MappedMessage { + let create_message_with_dupe_keys = |mut keys: Vec| LoadedMessage { message: v0::Message { account_keys: keys.split_off(2), ..v0::Message::default() }, - mapped_addresses: MappedAddresses { + loaded_addresses: LoadedAddresses { writable: keys.split_off(2), readonly: keys, }, @@ -234,7 +241,7 @@ mod tests { #[test] fn test_get_account_key() { - let (message, keys) = create_test_mapped_message(); + let (message, keys) = check_test_loaded_message(); assert_eq!(message.get_account_key(0), Some(&keys[0])); assert_eq!(message.get_account_key(1), Some(&keys[1])); @@ -246,7 +253,7 @@ mod tests { #[test] fn test_is_writable_index() { - let message = create_test_mapped_message().0; + let message = check_test_loaded_message().0; assert!(message.is_writable_index(0)); assert!(!message.is_writable_index(1)); @@ -258,15 +265,15 @@ mod tests { #[test] fn test_is_writable() { - let mut mapped_msg = create_test_mapped_message().0; + let mut message = check_test_loaded_message().0; - mapped_msg.message.account_keys[0] = sysvar::clock::id(); - assert!(mapped_msg.is_writable_index(0)); - assert!(!mapped_msg.is_writable(0, /*demote_program_write_locks=*/ true)); + message.message.account_keys[0] = sysvar::clock::id(); + assert!(message.is_writable_index(0)); + assert!(!message.is_writable(0, /*demote_program_write_locks=*/ true)); - mapped_msg.message.account_keys[0] = system_program::id(); - assert!(mapped_msg.is_writable_index(0)); - assert!(!mapped_msg.is_writable(0, /*demote_program_write_locks=*/ true)); + message.message.account_keys[0] = system_program::id(); + assert!(message.is_writable_index(0)); + assert!(!message.is_writable(0, /*demote_program_write_locks=*/ true)); } #[test] @@ -274,7 +281,7 @@ mod tests { let key0 = Pubkey::new_unique(); let key1 = Pubkey::new_unique(); let key2 = Pubkey::new_unique(); - let mapped_msg = MappedMessage { + let message = LoadedMessage { message: v0::Message { header: MessageHeader { num_required_signatures: 1, @@ -289,13 +296,13 @@ mod tests { }], ..v0::Message::default() }, - mapped_addresses: MappedAddresses { + loaded_addresses: LoadedAddresses { writable: vec![key1, key2], readonly: vec![], }, }; - assert!(mapped_msg.is_writable_index(2)); - assert!(!mapped_msg.is_writable(2, /*demote_program_write_locks=*/ true)); + assert!(message.is_writable_index(2)); + assert!(!message.is_writable(2, /*demote_program_write_locks=*/ true)); } } diff --git a/sdk/program/src/message/versions/v0/mod.rs b/sdk/program/src/message/versions/v0/mod.rs new file mode 100644 index 00000000000000..ac0e59919971b2 --- /dev/null +++ b/sdk/program/src/message/versions/v0/mod.rs @@ -0,0 +1,374 @@ +use crate::{ + hash::Hash, + instruction::CompiledInstruction, + message::{MessageHeader, MESSAGE_VERSION_PREFIX}, + pubkey::Pubkey, + sanitize::{Sanitize, SanitizeError}, + short_vec, +}; + +mod loaded; + +pub use loaded::*; + +/// Address table lookups describe an on-chain address lookup table to use +/// for loading more readonly and writable accounts in a single tx. +#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)] +#[serde(rename_all = "camelCase")] +pub struct MessageAddressTableLookup { + /// Address lookup table account key + pub account_key: Pubkey, + /// List of indexes used to load writable account addresses + #[serde(with = "short_vec")] + pub writable_indexes: Vec, + /// List of indexes used to load readonly account addresses + #[serde(with = "short_vec")] + pub readonly_indexes: Vec, +} + +/// Transaction message format which supports succinct account loading with +/// on-chain address lookup tables. +#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)] +#[serde(rename_all = "camelCase")] +pub struct Message { + /// The message header, identifying signed and read-only `account_keys` + pub header: MessageHeader, + + /// List of accounts loaded by this transaction. + #[serde(with = "short_vec")] + pub account_keys: Vec, + + /// The blockhash of a recent block. + pub recent_blockhash: Hash, + + /// Instructions that invoke a designated program, are executed in sequence, + /// and committed in one atomic transaction if all succeed. + /// + /// # Notes + /// + /// Account and program indexes will index into the list of addresses + /// constructed from the concatenation of three key lists: + /// 1) message `account_keys` + /// 2) ordered list of keys loaded from `writable` lookup table indexes + /// 3) ordered list of keys loaded from `readable` lookup table indexes + #[serde(with = "short_vec")] + pub instructions: Vec, + + /// List of address table lookups used to load additional accounts + /// for this transaction. + #[serde(with = "short_vec")] + pub address_table_lookups: Vec, +} + +impl Sanitize for Message { + fn sanitize(&self) -> Result<(), SanitizeError> { + // signing area and read-only non-signing area should not + // overlap + if usize::from(self.header.num_required_signatures) + .saturating_add(usize::from(self.header.num_readonly_unsigned_accounts)) + > self.account_keys.len() + { + return Err(SanitizeError::IndexOutOfBounds); + } + + // there should be at least 1 RW fee-payer account. + if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures { + return Err(SanitizeError::InvalidValue); + } + + let mut num_loaded_accounts = self.account_keys.len(); + for lookup in &self.address_table_lookups { + let num_table_loaded_accounts = lookup + .writable_indexes + .len() + .saturating_add(lookup.readonly_indexes.len()); + + // each lookup table must be used to load at least one account + if num_table_loaded_accounts == 0 { + return Err(SanitizeError::InvalidValue); + } + + num_loaded_accounts = num_loaded_accounts.saturating_add(num_table_loaded_accounts); + } + + // the number of loaded accounts must be <= 256 since account indices are + // encoded as `u8` + if num_loaded_accounts > 256 { + return Err(SanitizeError::IndexOutOfBounds); + } + + for ci in &self.instructions { + if usize::from(ci.program_id_index) >= num_loaded_accounts { + return Err(SanitizeError::IndexOutOfBounds); + } + // A program cannot be a payer. + if ci.program_id_index == 0 { + return Err(SanitizeError::IndexOutOfBounds); + } + for ai in &ci.accounts { + if usize::from(*ai) >= num_loaded_accounts { + return Err(SanitizeError::IndexOutOfBounds); + } + } + } + + Ok(()) + } +} + +impl Message { + /// Serialize this message with a version #0 prefix using bincode encoding. + pub fn serialize(&self) -> Vec { + bincode::serialize(&(MESSAGE_VERSION_PREFIX, self)).unwrap() + } +} + +#[cfg(test)] +mod tests { + use { + super::*, + crate::message::VersionedMessage, + }; + + #[test] + fn test_sanitize() { + assert!(Message { + header: MessageHeader { + num_required_signatures: 1, + ..MessageHeader::default() + }, + account_keys: vec![Pubkey::new_unique()], + ..Message::default() + } + .sanitize() + .is_ok()); + } + + #[test] + fn test_sanitize_with_instruction() { + assert!(Message { + header: MessageHeader { + num_required_signatures: 1, + ..MessageHeader::default() + }, + account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()], + instructions: vec![CompiledInstruction { + program_id_index: 1, + accounts: vec![0], + data: vec![] + }], + ..Message::default() + } + .sanitize() + .is_ok()); + } + + #[test] + fn test_sanitize_with_table_lookup() { + assert!(Message { + header: MessageHeader { + num_required_signatures: 1, + ..MessageHeader::default() + }, + account_keys: vec![Pubkey::new_unique()], + address_table_lookups: vec![MessageAddressTableLookup { + account_key: Pubkey::new_unique(), + writable_indexes: vec![1, 2, 3], + readonly_indexes: vec![0], + }], + ..Message::default() + } + .sanitize() + .is_ok()); + } + + #[test] + fn test_sanitize_with_table_lookup_and_ix() { + assert!(Message { + header: MessageHeader { + num_required_signatures: 1, + ..MessageHeader::default() + }, + account_keys: vec![Pubkey::new_unique()], + address_table_lookups: vec![MessageAddressTableLookup { + account_key: Pubkey::new_unique(), + writable_indexes: vec![1, 2, 3], + readonly_indexes: vec![0], + }], + instructions: vec![CompiledInstruction { + program_id_index: 4, + accounts: vec![0, 1, 2, 3], + data: vec![] + }], + ..Message::default() + } + .sanitize() + .is_ok()); + } + + #[test] + fn test_sanitize_without_signer() { + assert!(Message { + header: MessageHeader::default(), + account_keys: vec![Pubkey::new_unique()], + ..Message::default() + } + .sanitize() + .is_err()); + } + + #[test] + fn test_sanitize_without_writable_signer() { + assert!(Message { + header: MessageHeader { + num_required_signatures: 1, + num_readonly_signed_accounts: 1, + ..MessageHeader::default() + }, + account_keys: vec![Pubkey::new_unique()], + ..Message::default() + } + .sanitize() + .is_err()); + } + + #[test] + fn test_sanitize_with_empty_table_lookup() { + assert!(Message { + header: MessageHeader { + num_required_signatures: 1, + ..MessageHeader::default() + }, + account_keys: vec![Pubkey::new_unique()], + address_table_lookups: vec![MessageAddressTableLookup { + account_key: Pubkey::new_unique(), + writable_indexes: vec![], + readonly_indexes: vec![], + }], + ..Message::default() + } + .sanitize() + .is_err()); + } + + + #[test] + fn test_sanitize_with_max_account_keys() { + assert!(Message { + header: MessageHeader { + num_required_signatures: 1, + ..MessageHeader::default() + }, + account_keys: (0..=u8::MAX).map(|_| Pubkey::new_unique()).collect(), + ..Message::default() + } + .sanitize() + .is_ok()); + } + + #[test] + fn test_sanitize_with_too_many_account_keys() { + assert!(Message { + header: MessageHeader { + num_required_signatures: 1, + ..MessageHeader::default() + }, + account_keys: (0..=256).map(|_| Pubkey::new_unique()).collect(), + ..Message::default() + } + .sanitize() + .is_err()); + } + + #[test] + fn test_sanitize_with_max_table_loaded_keys() { + assert!(Message { + header: MessageHeader { + num_required_signatures: 1, + ..MessageHeader::default() + }, + account_keys: vec![Pubkey::new_unique()], + address_table_lookups: vec![MessageAddressTableLookup { + account_key: Pubkey::new_unique(), + writable_indexes: (0..=254).step_by(2).collect(), + readonly_indexes: (1..=254).step_by(2).collect(), + }], + ..Message::default() + } + .sanitize() + .is_ok()); + } + + #[test] + fn test_sanitize_with_too_many_table_loaded_keys() { + assert!(Message { + header: MessageHeader { + num_required_signatures: 1, + ..MessageHeader::default() + }, + account_keys: vec![Pubkey::new_unique()], + address_table_lookups: vec![MessageAddressTableLookup { + account_key: Pubkey::new_unique(), + writable_indexes: (0..=255).step_by(2).collect(), + readonly_indexes: (1..=255).step_by(2).collect(), + }], + ..Message::default() + } + .sanitize() + .is_err()); + } + + #[test] + fn test_sanitize_with_invalid_ix_program_id() { + assert!(Message { + header: MessageHeader { + num_required_signatures: 1, + ..MessageHeader::default() + }, + account_keys: vec![Pubkey::new_unique()], + address_table_lookups: vec![MessageAddressTableLookup { + account_key: Pubkey::new_unique(), + writable_indexes: vec![0], + readonly_indexes: vec![], + }], + instructions: vec![CompiledInstruction { + program_id_index: 2, + accounts: vec![], + data: vec![] + }], + ..Message::default() + } + .sanitize() + .is_err()); + } + + #[test] + fn test_sanitize_with_invalid_ix_account() { + assert!(Message { + header: MessageHeader { + num_required_signatures: 1, + ..MessageHeader::default() + }, + account_keys: vec![Pubkey::new_unique()], + address_table_lookups: vec![MessageAddressTableLookup { + account_key: Pubkey::new_unique(), + writable_indexes: vec![], + readonly_indexes: vec![0], + }], + instructions: vec![CompiledInstruction { + program_id_index: 1, + accounts: vec![2], + data: vec![] + }], + ..Message::default() + } + .sanitize() + .is_err()); + } + #[test] + fn test_serialize() { + let message = Message::default(); + let versioned_msg = VersionedMessage::V0(message.clone()); + assert_eq!(message.serialize(), versioned_msg.serialize()); + } +} diff --git a/sdk/src/transaction/sanitized.rs b/sdk/src/transaction/sanitized.rs index 2768e665da6238..a6db1587b4a312 100644 --- a/sdk/src/transaction/sanitized.rs +++ b/sdk/src/transaction/sanitized.rs @@ -3,7 +3,10 @@ use { crate::{ hash::Hash, - message::{v0, MappedAddresses, MappedMessage, SanitizedMessage, VersionedMessage}, + message::{ + v0::{self, LoadedAddresses}, + SanitizedMessage, VersionedMessage, + }, nonce::NONCED_TX_MARKER_IX_INDEX, precompiles::verify_if_precompile, program_utils::limited_deserialize, @@ -37,21 +40,21 @@ pub struct TransactionAccountLocks<'a> { impl SanitizedTransaction { /// Create a sanitized transaction from an unsanitized transaction. - /// If the input transaction uses address maps, attempt to map the - /// transaction keys to full addresses. + /// If the input transaction uses address tables, attempt to lookup + /// the address for each table index. pub fn try_create( tx: VersionedTransaction, message_hash: Hash, is_simple_vote_tx: Option, - address_mapper: impl Fn(&v0::Message) -> Result, + address_loader: impl Fn(&v0::Message) -> Result, ) -> Result { tx.sanitize()?; let signatures = tx.signatures; let message = match tx.message { VersionedMessage::Legacy(message) => SanitizedMessage::Legacy(message), - VersionedMessage::V0(message) => SanitizedMessage::V0(MappedMessage { - mapped_addresses: address_mapper(&message)?, + VersionedMessage::V0(message) => SanitizedMessage::V0(v0::LoadedMessage { + loaded_addresses: address_loader(&message)?, message, }), }; @@ -125,9 +128,9 @@ impl SanitizedTransaction { pub fn to_versioned_transaction(&self) -> VersionedTransaction { let signatures = self.signatures.clone(); match &self.message { - SanitizedMessage::V0(mapped_msg) => VersionedTransaction { + SanitizedMessage::V0(sanitized_msg) => VersionedTransaction { signatures, - message: VersionedMessage::V0(mapped_msg.message.clone()), + message: VersionedMessage::V0(sanitized_msg.message.clone()), }, SanitizedMessage::Legacy(message) => VersionedTransaction { signatures, @@ -193,7 +196,7 @@ impl SanitizedTransaction { fn message_data(&self) -> Vec { match &self.message { SanitizedMessage::Legacy(message) => message.serialize(), - SanitizedMessage::V0(mapped_msg) => mapped_msg.message.serialize(), + SanitizedMessage::V0(message) => message.serialize(), } } diff --git a/transaction-status/src/extract_memos.rs b/transaction-status/src/extract_memos.rs index cb6f7a94451b07..0fb0564805637c 100644 --- a/transaction-status/src/extract_memos.rs +++ b/transaction-status/src/extract_memos.rs @@ -76,7 +76,10 @@ mod test { solana_sdk::{ hash::Hash, instruction::CompiledInstruction, - message::{v0, MappedAddresses, MappedMessage, MessageHeader}, + message::{ + v0::{self, LoadedAddresses}, + MessageHeader, + }, }, }; @@ -125,7 +128,7 @@ mod test { let sanitized_message = SanitizedMessage::Legacy(message); assert_eq!(sanitized_message.extract_memos(), expected_memos); - let mapped_message = MappedMessage { + let sanitized_message = SanitizedMessage::V0(v0::LoadedMessage { message: v0::Message { header: MessageHeader { num_required_signatures: 1, @@ -136,12 +139,11 @@ mod test { instructions: memo_instructions, ..v0::Message::default() }, - mapped_addresses: MappedAddresses { + loaded_addresses: LoadedAddresses { writable: vec![], readonly: vec![spl_memo_id_v1(), another_program_id, spl_memo_id_v3()], }, - }; - let sanitized_mapped_message = SanitizedMessage::V0(mapped_message); - assert_eq!(sanitized_mapped_message.extract_memos(), expected_memos); + }); + assert_eq!(sanitized_message.extract_memos(), expected_memos); } }