* Migrate from address maps to address lookup tables
* update sanitize error
* cargo fmt
* update abi
(cherry picked from commit 6c108c8fc3)
Co-authored-by: Justin Starry <justin@solana.com>
			
			
This commit is contained in:
		| @@ -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", | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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<i16>, | ||||
|     pub readonly: Vec<i16>, | ||||
| #[postgres(name = "TransactionMessageAddressTableLookup")] | ||||
| pub struct DbTransactionMessageAddressTableLookup { | ||||
|     pub account_key: Vec<u8>, | ||||
|     pub writable_indexes: Vec<i16>, | ||||
|     pub readonly_indexes: Vec<i16>, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug, ToSql)] | ||||
| @@ -118,21 +119,21 @@ pub struct DbTransactionMessageV0 { | ||||
|     pub account_keys: Vec<Vec<u8>>, | ||||
|     pub recent_blockhash: Vec<u8>, | ||||
|     pub instructions: Vec<DbCompiledInstruction>, | ||||
|     pub address_map_indexes: Vec<DbAddressMapIndexes>, | ||||
|     pub address_table_lookups: Vec<DbTransactionMessageAddressTableLookup>, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug, ToSql)] | ||||
| #[postgres(name = "MappedAddresses")] | ||||
| pub struct DbMappedAddresses { | ||||
| #[postgres(name = "LoadedAddresses")] | ||||
| pub struct DbLoadedAddresses { | ||||
|     pub writable: Vec<Vec<u8>>, | ||||
|     pub readonly: Vec<Vec<u8>>, | ||||
| } | ||||
|  | ||||
| #[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<DbTransactionMessage>, | ||||
|     pub v0_mapped_message: Option<DbMappedMessage>, | ||||
|     pub v0_loaded_message: Option<DbLoadedMessageV0>, | ||||
|     pub message_hash: Vec<u8>, | ||||
|     pub meta: DbTransactionStatusMeta, | ||||
|     pub signatures: Vec<Vec<u8>>, | ||||
| @@ -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<Statement, AccountsDbPluginError> { | ||||
|         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()], | ||||
|                 }) | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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<SanitizeError> for SanitizeMessageError { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl TryFrom<Message> for SanitizedMessage { | ||||
| impl TryFrom<LegacyMessage> for SanitizedMessage { | ||||
|     type Error = SanitizeMessageError; | ||||
|     fn try_from(message: Message) -> Result<Self, Self::Error> { | ||||
|     fn try_from(message: LegacyMessage) -> Result<Self, Self::Error> { | ||||
|         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<Item = (&Pubkey, &CompiledInstruction)> { | ||||
|         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<dyn Iterator<Item = &Pubkey> + '_> { | ||||
|         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 { | ||||
|   | ||||
| @@ -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<u8>, | ||||
|     #[serde(with = "short_vec")] | ||||
|     pub readonly: Vec<u8>, | ||||
| } | ||||
|  | ||||
| /// 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<Pubkey>, | ||||
|  | ||||
|     /// 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<CompiledInstruction>, | ||||
|  | ||||
|     /// 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<AddressMapIndexes>, | ||||
| } | ||||
|  | ||||
| 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<u8> { | ||||
|         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()); | ||||
|     } | ||||
| } | ||||
| @@ -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![], | ||||
|             }], | ||||
|         }; | ||||
| @@ -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<Pubkey>, | ||||
|     /// List of addresses for read-only loaded accounts
 | ||||
|     pub readonly: Vec<Pubkey>, | ||||
| } | ||||
| 
 | ||||
| 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<Item = &Vec<Pubkey>> { | ||||
|         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<Pubkey>| MappedMessage { | ||||
|         let create_message_with_dupe_keys = |mut keys: Vec<Pubkey>| 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)); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										374
									
								
								sdk/program/src/message/versions/v0/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										374
									
								
								sdk/program/src/message/versions/v0/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<u8>, | ||||
|     /// List of indexes used to load readonly account addresses | ||||
|     #[serde(with = "short_vec")] | ||||
|     pub readonly_indexes: Vec<u8>, | ||||
| } | ||||
|  | ||||
| /// 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<Pubkey>, | ||||
|  | ||||
|     /// 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<CompiledInstruction>, | ||||
|  | ||||
|     /// List of address table lookups used to load additional accounts | ||||
|     /// for this transaction. | ||||
|     #[serde(with = "short_vec")] | ||||
|     pub address_table_lookups: Vec<MessageAddressTableLookup>, | ||||
| } | ||||
|  | ||||
| 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<u8> { | ||||
|         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()); | ||||
|     } | ||||
| } | ||||
| @@ -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<bool>, | ||||
|         address_mapper: impl Fn(&v0::Message) -> Result<MappedAddresses>, | ||||
|         address_loader: impl Fn(&v0::Message) -> Result<LoadedAddresses>, | ||||
|     ) -> Result<Self> { | ||||
|         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<u8> { | ||||
|         match &self.message { | ||||
|             SanitizedMessage::Legacy(message) => message.serialize(), | ||||
|             SanitizedMessage::V0(mapped_msg) => mapped_msg.message.serialize(), | ||||
|             SanitizedMessage::V0(message) => message.serialize(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -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); | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user