Migrate from address maps to address lookup tables (#21634) (#21773)

* 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:
mergify[bot]
2021-12-10 18:10:37 +00:00
committed by GitHub
parent ef51778c78
commit 2a6bb2b954
11 changed files with 610 additions and 610 deletions

View File

@ -113,9 +113,10 @@ CREATE TYPE "TransactionMessage" AS (
instructions "CompiledInstruction"[] instructions "CompiledInstruction"[]
); );
CREATE TYPE "AddressMapIndexes" AS ( CREATE TYPE "TransactionMessageAddressTableLookup" AS (
writable SMALLINT[], account_key: BYTEA[],
readonly SMALLINT[] writable_indexes SMALLINT[],
readonly_indexes SMALLINT[]
); );
CREATE TYPE "TransactionMessageV0" AS ( CREATE TYPE "TransactionMessageV0" AS (
@ -123,17 +124,17 @@ CREATE TYPE "TransactionMessageV0" AS (
account_keys BYTEA[], account_keys BYTEA[],
recent_blockhash BYTEA, recent_blockhash BYTEA,
instructions "CompiledInstruction"[], instructions "CompiledInstruction"[],
address_map_indexes "AddressMapIndexes"[] address_table_lookups "TransactionMessageAddressTableLookup"[]
); );
CREATE TYPE "MappedAddresses" AS ( CREATE TYPE "LoadedAddresses" AS (
writable BYTEA[], writable BYTEA[],
readonly BYTEA[] readonly BYTEA[]
); );
CREATE TYPE "MappedMessage" AS ( CREATE TYPE "LoadedMessageV0" AS (
message "TransactionMessageV0", message "TransactionMessageV0",
mapped_addresses "MappedAddresses" loaded_addresses "LoadedAddresses"
); );
-- The table storing transactions -- The table storing transactions
@ -143,7 +144,7 @@ CREATE TABLE transaction (
is_vote BOOL NOT NULL, is_vote BOOL NOT NULL,
message_type SMALLINT, -- 0: legacy, 1: v0 message message_type SMALLINT, -- 0: legacy, 1: v0 message
legacy_message "TransactionMessage", legacy_message "TransactionMessage",
v0_mapped_message "MappedMessage", v0_loaded_message "LoadedMessageV0",
signatures BYTEA[], signatures BYTEA[],
message_hash BYTEA, message_hash BYTEA,
meta "TransactionStatusMeta", meta "TransactionStatusMeta",

View File

@ -11,12 +11,12 @@ DROP TABLE transaction;
DROP TYPE "TransactionError" CASCADE; DROP TYPE "TransactionError" CASCADE;
DROP TYPE "TransactionErrorCode" CASCADE; DROP TYPE "TransactionErrorCode" CASCADE;
DROP TYPE "MappedMessage" CASCADE; DROP TYPE "LoadedMessageV0" CASCADE;
DROP TYPE "MappedAddresses" CASCADE; DROP TYPE "LoadedAddresses" CASCADE;
DROP TYPE "TransactionMessageV0" CASCADE; DROP TYPE "TransactionMessageV0" CASCADE;
DROP TYPE "AddressMapIndexes" CASCADE;
DROP TYPE "TransactionMessage" CASCADE; DROP TYPE "TransactionMessage" CASCADE;
DROP TYPE "TransactionMessageHeader" CASCADE; DROP TYPE "TransactionMessageHeader" CASCADE;
DROP TYPE "TransactionMessageAddressTableLookup" CASCADE;
DROP TYPE "TransactionStatusMeta" CASCADE; DROP TYPE "TransactionStatusMeta" CASCADE;
DROP TYPE "RewardType" CASCADE; DROP TYPE "RewardType" CASCADE;
DROP TYPE "Reward" CASCADE; DROP TYPE "Reward" CASCADE;

View File

@ -18,8 +18,8 @@ use {
solana_sdk::{ solana_sdk::{
instruction::CompiledInstruction, instruction::CompiledInstruction,
message::{ message::{
v0::{self, AddressMapIndexes}, v0::{self, LoadedAddresses, MessageAddressTableLookup},
MappedAddresses, MappedMessage, Message, MessageHeader, SanitizedMessage, Message, MessageHeader, SanitizedMessage,
}, },
transaction::TransactionError, transaction::TransactionError,
}, },
@ -105,10 +105,11 @@ pub struct DbTransactionMessage {
} }
#[derive(Clone, Debug, ToSql)] #[derive(Clone, Debug, ToSql)]
#[postgres(name = "AddressMapIndexes")] #[postgres(name = "TransactionMessageAddressTableLookup")]
pub struct DbAddressMapIndexes { pub struct DbTransactionMessageAddressTableLookup {
pub writable: Vec<i16>, pub account_key: Vec<u8>,
pub readonly: Vec<i16>, pub writable_indexes: Vec<i16>,
pub readonly_indexes: Vec<i16>,
} }
#[derive(Clone, Debug, ToSql)] #[derive(Clone, Debug, ToSql)]
@ -118,21 +119,21 @@ pub struct DbTransactionMessageV0 {
pub account_keys: Vec<Vec<u8>>, pub account_keys: Vec<Vec<u8>>,
pub recent_blockhash: Vec<u8>, pub recent_blockhash: Vec<u8>,
pub instructions: Vec<DbCompiledInstruction>, pub instructions: Vec<DbCompiledInstruction>,
pub address_map_indexes: Vec<DbAddressMapIndexes>, pub address_table_lookups: Vec<DbTransactionMessageAddressTableLookup>,
} }
#[derive(Clone, Debug, ToSql)] #[derive(Clone, Debug, ToSql)]
#[postgres(name = "MappedAddresses")] #[postgres(name = "LoadedAddresses")]
pub struct DbMappedAddresses { pub struct DbLoadedAddresses {
pub writable: Vec<Vec<u8>>, pub writable: Vec<Vec<u8>>,
pub readonly: Vec<Vec<u8>>, pub readonly: Vec<Vec<u8>>,
} }
#[derive(Clone, Debug, ToSql)] #[derive(Clone, Debug, ToSql)]
#[postgres(name = "MappedMessage")] #[postgres(name = "LoadedMessageV0")]
pub struct DbMappedMessage { pub struct DbLoadedMessageV0 {
pub message: DbTransactionMessageV0, pub message: DbTransactionMessageV0,
pub mapped_addresses: DbMappedAddresses, pub loaded_addresses: DbLoadedAddresses,
} }
pub struct DbTransaction { pub struct DbTransaction {
@ -141,7 +142,7 @@ pub struct DbTransaction {
pub slot: i64, pub slot: i64,
pub message_type: i16, pub message_type: i16,
pub legacy_message: Option<DbTransactionMessage>, pub legacy_message: Option<DbTransactionMessage>,
pub v0_mapped_message: Option<DbMappedMessage>, pub v0_loaded_message: Option<DbLoadedMessageV0>,
pub message_hash: Vec<u8>, pub message_hash: Vec<u8>,
pub meta: DbTransactionStatusMeta, pub meta: DbTransactionStatusMeta,
pub signatures: Vec<Vec<u8>>, pub signatures: Vec<Vec<u8>>,
@ -151,32 +152,33 @@ pub struct LogTransactionRequest {
pub transaction_info: DbTransaction, pub transaction_info: DbTransaction,
} }
impl From<&AddressMapIndexes> for DbAddressMapIndexes { impl From<&MessageAddressTableLookup> for DbTransactionMessageAddressTableLookup {
fn from(address_map_indexes: &AddressMapIndexes) -> Self { fn from(address_table_lookup: &MessageAddressTableLookup) -> Self {
Self { Self {
writable: address_map_indexes account_key: address_table_lookup.account_key.as_ref().to_vec(),
.writable writable_indexes: address_table_lookup
.writable_indexes
.iter() .iter()
.map(|address_idx| *address_idx as i16) .map(|idx| *idx as i16)
.collect(), .collect(),
readonly: address_map_indexes readonly_indexes: address_table_lookup
.readonly .readonly_indexes
.iter() .iter()
.map(|address_idx| *address_idx as i16) .map(|idx| *idx as i16)
.collect(), .collect(),
} }
} }
} }
impl From<&MappedAddresses> for DbMappedAddresses { impl From<&LoadedAddresses> for DbLoadedAddresses {
fn from(mapped_addresses: &MappedAddresses) -> Self { fn from(loaded_addresses: &LoadedAddresses) -> Self {
Self { Self {
writable: mapped_addresses writable: loaded_addresses
.writable .writable
.iter() .iter()
.map(|pubkey| pubkey.as_ref().to_vec()) .map(|pubkey| pubkey.as_ref().to_vec())
.collect(), .collect(),
readonly: mapped_addresses readonly: loaded_addresses
.readonly .readonly
.iter() .iter()
.map(|pubkey| pubkey.as_ref().to_vec()) .map(|pubkey| pubkey.as_ref().to_vec())
@ -243,20 +245,20 @@ impl From<&v0::Message> for DbTransactionMessageV0 {
.iter() .iter()
.map(DbCompiledInstruction::from) .map(DbCompiledInstruction::from)
.collect(), .collect(),
address_map_indexes: message address_table_lookups: message
.address_map_indexes .address_table_lookups
.iter() .iter()
.map(DbAddressMapIndexes::from) .map(DbTransactionMessageAddressTableLookup::from)
.collect(), .collect(),
} }
} }
} }
impl From<&MappedMessage> for DbMappedMessage { impl From<&v0::LoadedMessage> for DbLoadedMessageV0 {
fn from(message: &MappedMessage) -> Self { fn from(message: &v0::LoadedMessage) -> Self {
Self { Self {
message: DbTransactionMessageV0::from(&message.message), 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, _ => None,
}, },
v0_mapped_message: match transaction_info.transaction.message() { v0_loaded_message: match transaction_info.transaction.message() {
SanitizedMessage::V0(mapped_message) => Some(DbMappedMessage::from(mapped_message)), SanitizedMessage::V0(loaded_message) => Some(DbLoadedMessageV0::from(loaded_message)),
_ => None, _ => None,
}, },
signatures: transaction_info signatures: transaction_info
@ -485,7 +487,7 @@ impl SimplePostgresClient {
config: &AccountsDbPluginPostgresConfig, config: &AccountsDbPluginPostgresConfig,
) -> Result<Statement, AccountsDbPluginError> { ) -> Result<Statement, AccountsDbPluginError> {
let stmt = "INSERT INTO transaction AS txn (signature, is_vote, slot, message_type, legacy_message, \ 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)"; VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)";
let stmt = client.prepare(stmt); let stmt = client.prepare(stmt);
@ -521,7 +523,7 @@ impl SimplePostgresClient {
&transaction_info.slot, &transaction_info.slot,
&transaction_info.message_type, &transaction_info.message_type,
&transaction_info.legacy_message, &transaction_info.legacy_message,
&transaction_info.v0_mapped_message, &transaction_info.v0_loaded_message,
&transaction_info.signatures, &transaction_info.signatures,
&transaction_info.message_hash, &transaction_info.message_hash,
&transaction_info.meta, &transaction_info.meta,
@ -670,42 +672,44 @@ pub(crate) mod tests {
check_inner_instructions_equality(&inner_instructions, &db_inner_instructions); check_inner_instructions_equality(&inner_instructions, &db_inner_instructions);
} }
fn check_address_map_indexes_equality( fn check_address_table_lookups_equality(
address_map_indexes: &AddressMapIndexes, address_table_lookups: &MessageAddressTableLookup,
db_address_map_indexes: &DbAddressMapIndexes, db_address_table_lookups: &DbTransactionMessageAddressTableLookup,
) { ) {
assert_eq!( assert_eq!(
address_map_indexes.writable.len(), address_table_lookups.writable_indexes.len(),
db_address_map_indexes.writable.len() db_address_table_lookups.writable_indexes.len()
); );
assert_eq!( assert_eq!(
address_map_indexes.readonly.len(), address_table_lookups.readonly_indexes.len(),
db_address_map_indexes.readonly.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!( assert_eq!(
address_map_indexes.writable[i], address_table_lookups.writable_indexes[i],
db_address_map_indexes.writable[i] as u8 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!( assert_eq!(
address_map_indexes.readonly[i], address_table_lookups.readonly_indexes[i],
db_address_map_indexes.readonly[i] as u8 db_address_table_lookups.readonly_indexes[i] as u8
) )
} }
} }
#[test] #[test]
fn test_transform_address_map_indexes() { fn test_transform_address_table_lookups() {
let address_map_indexes = AddressMapIndexes { let address_table_lookups = MessageAddressTableLookup {
writable: vec![1, 2, 3], account_key: Pubkey::new_unique(),
readonly: vec![4, 5, 6], writable_indexes: vec![1, 2, 3],
readonly_indexes: vec![4, 5, 6],
}; };
let db_address_map_indexes = DbAddressMapIndexes::from(&address_map_indexes); let db_address_table_lookups =
check_address_map_indexes_equality(&address_map_indexes, &db_address_map_indexes); 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) { fn check_reward_equality(reward: &Reward, db_reward: &DbReward) {
@ -1089,7 +1093,7 @@ pub(crate) mod tests {
check_transaction_message_equality(&message, &db_message); check_transaction_message_equality(&message, &db_message);
} }
fn check_transaction_messagev0_equality( fn check_transaction_message_v0_equality(
message: &v0::Message, message: &v0::Message,
db_message: &DbTransactionMessageV0, db_message: &DbTransactionMessageV0,
) { ) {
@ -1106,18 +1110,18 @@ pub(crate) mod tests {
); );
} }
assert_eq!( assert_eq!(
message.address_map_indexes.len(), message.address_table_lookups.len(),
db_message.address_map_indexes.len() db_message.address_table_lookups.len()
); );
for i in 0..message.address_map_indexes.len() { for i in 0..message.address_table_lookups.len() {
check_address_map_indexes_equality( check_address_table_lookups_equality(
&message.address_map_indexes[i], &message.address_table_lookups[i],
&db_message.address_map_indexes[i], &db_message.address_table_lookups[i],
); );
} }
} }
fn build_transaction_messagev0() -> v0::Message { fn build_transaction_message_v0() -> v0::Message {
v0::Message { v0::Message {
header: MessageHeader { header: MessageHeader {
num_readonly_signed_accounts: 2, num_readonly_signed_accounts: 2,
@ -1144,71 +1148,76 @@ pub(crate) mod tests {
data: vec![14, 15, 16], data: vec![14, 15, 16],
}, },
], ],
address_map_indexes: vec![ address_table_lookups: vec![
AddressMapIndexes { MessageAddressTableLookup {
writable: vec![0], account_key: Pubkey::new_unique(),
readonly: vec![1, 2], writable_indexes: vec![0],
readonly_indexes: vec![1, 2],
}, },
AddressMapIndexes { MessageAddressTableLookup {
writable: vec![1], account_key: Pubkey::new_unique(),
readonly: vec![0, 2], writable_indexes: vec![1],
readonly_indexes: vec![0, 2],
}, },
], ],
} }
} }
#[test] #[test]
fn test_transform_transaction_messagev0() { fn test_transform_transaction_message_v0() {
let message = build_transaction_messagev0(); let message = build_transaction_message_v0();
let db_message = DbTransactionMessageV0::from(&message); 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( fn check_loaded_addresses(
mapped_addresses: &MappedAddresses, loaded_addresses: &LoadedAddresses,
db_mapped_addresses: &DbMappedAddresses, db_loaded_addresses: &DbLoadedAddresses,
) { ) {
assert_eq!( assert_eq!(
mapped_addresses.writable.len(), loaded_addresses.writable.len(),
db_mapped_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!( assert_eq!(
mapped_addresses.writable[i].as_ref(), loaded_addresses.writable[i].as_ref(),
db_mapped_addresses.writable[i] db_loaded_addresses.writable[i]
); );
} }
assert_eq!( assert_eq!(
mapped_addresses.readonly.len(), loaded_addresses.readonly.len(),
db_mapped_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!( assert_eq!(
mapped_addresses.readonly[i].as_ref(), loaded_addresses.readonly[i].as_ref(),
db_mapped_addresses.readonly[i] db_loaded_addresses.readonly[i]
); );
} }
} }
fn check_mapped_message_equality(message: &MappedMessage, db_message: &DbMappedMessage) { fn check_loaded_message_v0_equality(
check_transaction_messagev0_equality(&message.message, &db_message.message); message: &v0::LoadedMessage,
check_mapped_addresses(&message.mapped_addresses, &db_message.mapped_addresses); db_message: &DbLoadedMessageV0,
) {
check_transaction_message_v0_equality(&message.message, &db_message.message);
check_loaded_addresses(&message.loaded_addresses, &db_message.loaded_addresses);
} }
#[test] #[test]
fn test_transform_mapped_message() { fn test_transform_loaded_message_v0() {
let message = MappedMessage { let message = v0::LoadedMessage {
message: build_transaction_messagev0(), message: build_transaction_message_v0(),
mapped_addresses: MappedAddresses { loaded_addresses: LoadedAddresses {
writable: vec![Pubkey::new_unique(), Pubkey::new_unique()], writable: vec![Pubkey::new_unique(), Pubkey::new_unique()],
readonly: vec![Pubkey::new_unique(), Pubkey::new_unique()], readonly: vec![Pubkey::new_unique(), Pubkey::new_unique()],
}, },
}; };
let db_message = DbMappedMessage::from(&message); let db_message = DbLoadedMessageV0::from(&message);
check_mapped_message_equality(&message, &db_message); check_loaded_message_v0_equality(&message, &db_message);
} }
fn check_transaction( fn check_transaction(
@ -1229,9 +1238,9 @@ pub(crate) mod tests {
} }
SanitizedMessage::V0(message) => { SanitizedMessage::V0(message) => {
assert_eq!(db_transaction.message_type, 1); assert_eq!(db_transaction.message_type, 1);
check_mapped_message_equality( check_loaded_message_v0_equality(
message, 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(&[2u8; 64]),
Signature::new(&[3u8; 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 = let transaction =
SanitizedTransaction::try_create(transaction, message_hash, Some(true), |_message| { SanitizedTransaction::try_create(transaction, message_hash, Some(true), |_message| {
Ok(MappedAddresses { Ok(LoadedAddresses {
writable: vec![Pubkey::new_unique(), Pubkey::new_unique()], writable: vec![Pubkey::new_unique(), Pubkey::new_unique()],
readonly: vec![Pubkey::new_unique(), Pubkey::new_unique()], readonly: vec![Pubkey::new_unique(), Pubkey::new_unique()],
}) })

View File

@ -5,12 +5,10 @@ pub mod legacy;
#[cfg(not(target_arch = "bpf"))] #[cfg(not(target_arch = "bpf"))]
#[path = ""] #[path = ""]
mod non_bpf_modules { mod non_bpf_modules {
mod mapped;
mod sanitized; mod sanitized;
pub mod v0;
mod versions; mod versions;
pub use {mapped::*, sanitized::*, versions::*}; pub use {sanitized::*, versions::*};
} }
pub use legacy::Message; pub use legacy::Message;

View File

@ -2,7 +2,7 @@ use {
crate::{ crate::{
hash::Hash, hash::Hash,
instruction::{CompiledInstruction, Instruction}, instruction::{CompiledInstruction, Instruction},
message::{MappedAddresses, MappedMessage, Message, MessageHeader}, message::{v0::{self, LoadedAddresses}, legacy::Message as LegacyMessage, MessageHeader},
pubkey::Pubkey, pubkey::Pubkey,
sanitize::{Sanitize, SanitizeError}, sanitize::{Sanitize, SanitizeError},
serialize_utils::{append_slice, append_u16, append_u8}, serialize_utils::{append_slice, append_u16, append_u8},
@ -17,9 +17,9 @@ use {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum SanitizedMessage { pub enum SanitizedMessage {
/// Sanitized legacy message /// Sanitized legacy message
Legacy(Message), Legacy(LegacyMessage),
/// Sanitized version #0 message with mapped addresses /// Sanitized version #0 message with mapped addresses
V0(MappedMessage), V0(v0::LoadedMessage),
} }
#[derive(PartialEq, Debug, Error, Eq, Clone)] #[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; type Error = SanitizeMessageError;
fn try_from(message: Message) -> Result<Self, Self::Error> { fn try_from(message: LegacyMessage) -> Result<Self, Self::Error> {
message.sanitize()?; message.sanitize()?;
let sanitized_msg = Self::Legacy(message); let sanitized_msg = Self::Legacy(message);
@ -80,12 +80,12 @@ impl SanitizedMessage {
pub fn header(&self) -> &MessageHeader { pub fn header(&self) -> &MessageHeader {
match self { match self {
Self::Legacy(message) => &message.header, 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 /// 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 { if let Self::Legacy(message) = &self {
Some(message) Some(message)
} else { } else {
@ -103,7 +103,7 @@ impl SanitizedMessage {
pub fn recent_blockhash(&self) -> &Hash { pub fn recent_blockhash(&self) -> &Hash {
match self { match self {
Self::Legacy(message) => &message.recent_blockhash, 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] { pub fn instructions(&self) -> &[CompiledInstruction] {
match self { match self {
Self::Legacy(message) => &message.instructions, 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)> { ) -> impl Iterator<Item = (&Pubkey, &CompiledInstruction)> {
match self { match self {
Self::Legacy(message) => message.instructions.iter(), Self::Legacy(message) => message.instructions.iter(),
Self::V0(mapped_msg) => mapped_msg.message.instructions.iter(), Self::V0(message) => message.instructions.iter(),
} }
.map(move |ix| { .map(move |ix| {
( (
@ -138,7 +138,7 @@ impl SanitizedMessage {
pub fn account_keys_iter(&self) -> Box<dyn Iterator<Item = &Pubkey> + '_> { pub fn account_keys_iter(&self) -> Box<dyn Iterator<Item = &Pubkey> + '_> {
match self { match self {
Self::Legacy(message) => Box::new(message.account_keys.iter()), 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 { pub fn account_keys_len(&self) -> usize {
match self { match self {
Self::Legacy(message) => message.account_keys.len(), 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 data
} }
/// Return the mapped addresses for this message if it has any. /// Return the resolved addresses for this message if it has any.
fn mapped_addresses(&self) -> Option<&MappedAddresses> { fn loaded_lookup_table_addresses(&self) -> Option<&LoadedAddresses> {
match &self { match &self {
SanitizedMessage::V0(message) => Some(&message.mapped_addresses), SanitizedMessage::V0(message) => Some(&message.loaded_addresses),
_ => None, _ => None,
} }
} }
@ -262,7 +262,7 @@ impl SanitizedMessage {
/// Return the number of readonly accounts loaded by this message. /// Return the number of readonly accounts loaded by this message.
pub fn num_readonly_accounts(&self) -> usize { pub fn num_readonly_accounts(&self) -> usize {
let mapped_readonly_addresses = self let mapped_readonly_addresses = self
.mapped_addresses() .loaded_lookup_table_addresses()
.map(|keys| keys.readonly.len()) .map(|keys| keys.readonly.len())
.unwrap_or_default(); .unwrap_or_default();
mapped_readonly_addresses mapped_readonly_addresses
@ -311,13 +311,13 @@ mod tests {
#[test] #[test]
fn test_try_from_message() { fn test_try_from_message() {
let dupe_key = Pubkey::new_unique(); let dupe_key = Pubkey::new_unique();
let legacy_message_with_dupes = Message { let legacy_message_with_dupes = LegacyMessage {
header: MessageHeader { header: MessageHeader {
num_required_signatures: 1, num_required_signatures: 1,
..MessageHeader::default() ..MessageHeader::default()
}, },
account_keys: vec![dupe_key, dupe_key], account_keys: vec![dupe_key, dupe_key],
..Message::default() ..LegacyMessage::default()
}; };
assert_eq!( assert_eq!(
@ -325,9 +325,9 @@ mod tests {
Some(SanitizeMessageError::DuplicateAccountKey), Some(SanitizeMessageError::DuplicateAccountKey),
); );
let legacy_message_with_no_signers = Message { let legacy_message_with_no_signers = LegacyMessage {
account_keys: vec![Pubkey::new_unique()], account_keys: vec![Pubkey::new_unique()],
..Message::default() ..LegacyMessage::default()
}; };
assert_eq!( assert_eq!(
@ -346,7 +346,7 @@ mod tests {
CompiledInstruction::new(2, &(), vec![0, 1]), 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, 1,
0, 0,
2, 2,
@ -370,20 +370,20 @@ mod tests {
let key4 = Pubkey::new_unique(); let key4 = Pubkey::new_unique();
let key5 = Pubkey::new_unique(); let key5 = Pubkey::new_unique();
let legacy_message = SanitizedMessage::try_from(Message { let legacy_message = SanitizedMessage::try_from(LegacyMessage {
header: MessageHeader { header: MessageHeader {
num_required_signatures: 2, num_required_signatures: 2,
num_readonly_signed_accounts: 1, num_readonly_signed_accounts: 1,
num_readonly_unsigned_accounts: 1, num_readonly_unsigned_accounts: 1,
}, },
account_keys: vec![key0, key1, key2, key3], account_keys: vec![key0, key1, key2, key3],
..Message::default() ..LegacyMessage::default()
}) })
.unwrap(); .unwrap();
assert_eq!(legacy_message.num_readonly_accounts(), 2); assert_eq!(legacy_message.num_readonly_accounts(), 2);
let mapped_message = SanitizedMessage::V0(MappedMessage { let v0_message = SanitizedMessage::V0(v0::LoadedMessage {
message: v0::Message { message: v0::Message {
header: MessageHeader { header: MessageHeader {
num_required_signatures: 2, num_required_signatures: 2,
@ -393,13 +393,13 @@ mod tests {
account_keys: vec![key0, key1, key2, key3], account_keys: vec![key0, key1, key2, key3],
..v0::Message::default() ..v0::Message::default()
}, },
mapped_addresses: MappedAddresses { loaded_addresses: LoadedAddresses {
writable: vec![key4], writable: vec![key4],
readonly: vec![key5], readonly: vec![key5],
}, },
}); });
assert_eq!(mapped_message.num_readonly_accounts(), 3); assert_eq!(v0_message.num_readonly_accounts(), 3);
} }
#[test] #[test]
@ -427,7 +427,7 @@ mod tests {
]; ];
let demote_program_write_locks = true; 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 sanitized_message = SanitizedMessage::try_from(message.clone()).unwrap();
let serialized = sanitized_message.serialize_instructions(demote_program_write_locks); 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 // assert that Message::deserialize_instruction is compatible with SanitizedMessage::serialize_instructions
for (i, instruction) in instructions.iter().enumerate() { for (i, instruction) in instructions.iter().enumerate() {
assert_eq!( assert_eq!(
Message::deserialize_instruction(i, &serialized).unwrap(), LegacyMessage::deserialize_instruction(i, &serialized).unwrap(),
*instruction *instruction
); );
} }
@ -481,18 +481,18 @@ mod tests {
data: vec![], data: vec![],
}; };
let legacy_message = SanitizedMessage::try_from(Message { let legacy_message = SanitizedMessage::try_from(LegacyMessage {
header: MessageHeader { header: MessageHeader {
num_required_signatures: 1, num_required_signatures: 1,
num_readonly_signed_accounts: 0, num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 0, num_readonly_unsigned_accounts: 0,
}, },
account_keys: vec![key0, key1, key2, program_id], account_keys: vec![key0, key1, key2, program_id],
..Message::default() ..LegacyMessage::default()
}) })
.unwrap(); .unwrap();
let mapped_message = SanitizedMessage::V0(MappedMessage { let v0_message = SanitizedMessage::V0(v0::LoadedMessage {
message: v0::Message { message: v0::Message {
header: MessageHeader { header: MessageHeader {
num_required_signatures: 1, num_required_signatures: 1,
@ -502,13 +502,13 @@ mod tests {
account_keys: vec![key0, key1], account_keys: vec![key0, key1],
..v0::Message::default() ..v0::Message::default()
}, },
mapped_addresses: MappedAddresses { loaded_addresses: LoadedAddresses {
writable: vec![key2], writable: vec![key2],
readonly: vec![program_id], readonly: vec![program_id],
}, },
}); });
for message in vec![legacy_message, mapped_message] { for message in vec![legacy_message, v0_message] {
assert_eq!( assert_eq!(
message.try_compile_instruction(&valid_instruction), message.try_compile_instruction(&valid_instruction),
Some(CompiledInstruction { Some(CompiledInstruction {

View File

@ -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());
}
}

View File

@ -2,7 +2,7 @@ use {
crate::{ crate::{
hash::Hash, hash::Hash,
instruction::CompiledInstruction, instruction::CompiledInstruction,
message::{v0, Message, MessageHeader}, message::{legacy::Message as LegacyMessage, MessageHeader},
pubkey::Pubkey, pubkey::Pubkey,
sanitize::{Sanitize, SanitizeError}, sanitize::{Sanitize, SanitizeError},
short_vec, short_vec,
@ -15,6 +15,8 @@ use {
std::fmt, std::fmt,
}; };
pub mod v0;
/// Bit mask that indicates whether a serialized message is versioned. /// Bit mask that indicates whether a serialized message is versioned.
pub const MESSAGE_VERSION_PREFIX: u8 = 0x80; 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 /// 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` /// is bit is not set, all bytes are used to encode the legacy `Message`
/// format. /// format.
#[frozen_abi(digest = "x2F3RG2RhJQWN6L2N3jebvcAvNYFrhE3sKTPJ4sENvL")] #[frozen_abi(digest = "G4EAiqmGgBprgf5ePYemLJcoFfx4R7rhC1Weo2FVJ7fn")]
#[derive(Debug, PartialEq, Eq, Clone, AbiEnumVisitor, AbiExample)] #[derive(Debug, PartialEq, Eq, Clone, AbiEnumVisitor, AbiExample)]
pub enum VersionedMessage { pub enum VersionedMessage {
Legacy(Message), Legacy(LegacyMessage),
V0(v0::Message), V0(v0::Message),
} }
@ -98,7 +100,7 @@ impl VersionedMessage {
impl Default for VersionedMessage { impl Default for VersionedMessage {
fn default() -> Self { 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) de::Error::invalid_length(1, &self)
})?; })?;
Ok(VersionedMessage::Legacy(Message { Ok(VersionedMessage::Legacy(LegacyMessage {
header: MessageHeader { header: MessageHeader {
num_required_signatures, num_required_signatures,
num_readonly_signed_accounts: message.num_readonly_signed_accounts, num_readonly_signed_accounts: message.num_readonly_signed_accounts,
@ -247,7 +249,7 @@ mod tests {
super::*, super::*,
crate::{ crate::{
instruction::{AccountMeta, Instruction}, 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(); message.recent_blockhash = Hash::new_unique();
let bytes1 = bincode::serialize(&message).unwrap(); let bytes1 = bincode::serialize(&message).unwrap();
@ -282,7 +284,7 @@ mod tests {
assert_eq!(bytes1, bytes2); 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(); let message2: VersionedMessage = bincode::deserialize(&bytes2).unwrap();
if let VersionedMessage::Legacy(message2) = message2 { if let VersionedMessage::Legacy(message2) = message2 {
@ -299,27 +301,27 @@ mod tests {
header: MessageHeader { header: MessageHeader {
num_required_signatures: 1, num_required_signatures: 1,
num_readonly_signed_accounts: 0, num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 2, num_readonly_unsigned_accounts: 0,
}, },
recent_blockhash: Hash::new_unique(), recent_blockhash: Hash::new_unique(),
account_keys: vec![ account_keys: vec![
Pubkey::new_unique(), Pubkey::new_unique(),
Pubkey::new_unique(),
Pubkey::new_unique(),
], ],
address_map_indexes: vec![ address_table_lookups: vec![
AddressMapIndexes { MessageAddressTableLookup {
writable: vec![1], account_key: Pubkey::new_unique(),
readonly: vec![0], writable_indexes: vec![1],
readonly_indexes: vec![0],
}, },
AddressMapIndexes { MessageAddressTableLookup {
writable: vec![0], account_key: Pubkey::new_unique(),
readonly: vec![1], writable_indexes: vec![0],
readonly_indexes: vec![1],
}, },
], ],
instructions: vec![CompiledInstruction { instructions: vec![CompiledInstruction {
program_id_index: 1, program_id_index: 1,
accounts: vec![0], accounts: vec![0, 2, 3, 4],
data: vec![], data: vec![],
}], }],
}; };

View File

@ -5,37 +5,44 @@ use {
pubkey::Pubkey, pubkey::Pubkey,
sysvar, 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)] #[derive(Debug, Clone)]
pub struct MappedMessage { pub struct LoadedMessage {
/// Message which loaded a collection of mapped addresses /// Message which loaded a collection of lookup table addresses
pub message: v0::Message, pub message: v0::Message,
/// Collection of mapped addresses loaded by this message /// Addresses loaded with on-chain address lookup tables
pub mapped_addresses: MappedAddresses, pub loaded_addresses: LoadedAddresses,
} }
/// Collection of mapped addresses loaded succinctly by a transaction using impl Deref for LoadedMessage {
/// on-chain address map accounts. 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)] #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct MappedAddresses { pub struct LoadedAddresses {
/// List of addresses for writable loaded accounts /// List of addresses for writable loaded accounts
pub writable: Vec<Pubkey>, pub writable: Vec<Pubkey>,
/// List of addresses for read-only loaded accounts /// List of addresses for read-only loaded accounts
pub readonly: Vec<Pubkey>, pub readonly: Vec<Pubkey>,
} }
impl MappedMessage { impl LoadedMessage {
/// Returns an iterator of account key segments. The ordering of segments /// Returns an iterator of account key segments. The ordering of segments
/// affects how account indexes from compiled instructions are resolved and /// affects how account indexes from compiled instructions are resolved and
/// so should not be changed. /// so should not be changed.
fn account_keys_segment_iter(&self) -> impl Iterator<Item = &Vec<Pubkey>> { fn account_keys_segment_iter(&self) -> impl Iterator<Item = &Vec<Pubkey>> {
vec![ vec![
&self.message.account_keys, &self.message.account_keys,
&self.mapped_addresses.writable, &self.loaded_addresses.writable,
&self.mapped_addresses.readonly, &self.loaded_addresses.readonly,
] ]
.into_iter() .into_iter()
} }
@ -82,7 +89,7 @@ impl MappedMessage {
let num_signed_accounts = usize::from(header.num_required_signatures); let num_signed_accounts = usize::from(header.num_required_signatures);
if key_index >= num_account_keys { if key_index >= num_account_keys {
let mapped_addresses_index = key_index.saturating_sub(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 { } else if key_index >= num_signed_accounts {
let num_unsigned_accounts = num_account_keys.saturating_sub(num_signed_accounts); let num_unsigned_accounts = num_account_keys.saturating_sub(num_signed_accounts);
let num_writable_unsigned_accounts = num_unsigned_accounts let num_writable_unsigned_accounts = num_unsigned_accounts
@ -138,7 +145,7 @@ mod tests {
itertools::Itertools, itertools::Itertools,
}; };
fn create_test_mapped_message() -> (MappedMessage, [Pubkey; 6]) { fn check_test_loaded_message() -> (LoadedMessage, [Pubkey; 6]) {
let key0 = Pubkey::new_unique(); let key0 = Pubkey::new_unique();
let key1 = Pubkey::new_unique(); let key1 = Pubkey::new_unique();
let key2 = Pubkey::new_unique(); let key2 = Pubkey::new_unique();
@ -146,7 +153,7 @@ mod tests {
let key4 = Pubkey::new_unique(); let key4 = Pubkey::new_unique();
let key5 = Pubkey::new_unique(); let key5 = Pubkey::new_unique();
let message = MappedMessage { let message = LoadedMessage {
message: v0::Message { message: v0::Message {
header: MessageHeader { header: MessageHeader {
num_required_signatures: 2, num_required_signatures: 2,
@ -156,7 +163,7 @@ mod tests {
account_keys: vec![key0, key1, key2, key3], account_keys: vec![key0, key1, key2, key3],
..v0::Message::default() ..v0::Message::default()
}, },
mapped_addresses: MappedAddresses { loaded_addresses: LoadedAddresses {
writable: vec![key4], writable: vec![key4],
readonly: vec![key5], readonly: vec![key5],
}, },
@ -167,7 +174,7 @@ mod tests {
#[test] #[test]
fn test_account_keys_segment_iter() { fn test_account_keys_segment_iter() {
let (message, keys) = create_test_mapped_message(); let (message, keys) = check_test_loaded_message();
let expected_segments = vec![ let expected_segments = vec![
vec![keys[0], keys[1], keys[2], keys[3]], vec![keys[0], keys[1], keys[2], keys[3]],
@ -183,14 +190,14 @@ mod tests {
#[test] #[test]
fn test_account_keys_len() { 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()); assert_eq!(message.account_keys_len(), keys.len());
} }
#[test] #[test]
fn test_account_keys_iter() { 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(); let mut iter = message.account_keys_iter();
for expected_key in keys { for expected_key in keys {
@ -200,19 +207,19 @@ mod tests {
#[test] #[test]
fn test_has_duplicates() { fn test_has_duplicates() {
let message = create_test_mapped_message().0; let message = check_test_loaded_message().0;
assert!(!message.has_duplicates()); assert!(!message.has_duplicates());
} }
#[test] #[test]
fn test_has_duplicates_with_dupe_keys() { 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 { message: v0::Message {
account_keys: keys.split_off(2), account_keys: keys.split_off(2),
..v0::Message::default() ..v0::Message::default()
}, },
mapped_addresses: MappedAddresses { loaded_addresses: LoadedAddresses {
writable: keys.split_off(2), writable: keys.split_off(2),
readonly: keys, readonly: keys,
}, },
@ -234,7 +241,7 @@ mod tests {
#[test] #[test]
fn test_get_account_key() { 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(0), Some(&keys[0]));
assert_eq!(message.get_account_key(1), Some(&keys[1])); assert_eq!(message.get_account_key(1), Some(&keys[1]));
@ -246,7 +253,7 @@ mod tests {
#[test] #[test]
fn test_is_writable_index() { 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(0));
assert!(!message.is_writable_index(1)); assert!(!message.is_writable_index(1));
@ -258,15 +265,15 @@ mod tests {
#[test] #[test]
fn test_is_writable() { 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(); message.message.account_keys[0] = sysvar::clock::id();
assert!(mapped_msg.is_writable_index(0)); assert!(message.is_writable_index(0));
assert!(!mapped_msg.is_writable(0, /*demote_program_write_locks=*/ true)); assert!(!message.is_writable(0, /*demote_program_write_locks=*/ true));
mapped_msg.message.account_keys[0] = system_program::id(); message.message.account_keys[0] = system_program::id();
assert!(mapped_msg.is_writable_index(0)); assert!(message.is_writable_index(0));
assert!(!mapped_msg.is_writable(0, /*demote_program_write_locks=*/ true)); assert!(!message.is_writable(0, /*demote_program_write_locks=*/ true));
} }
#[test] #[test]
@ -274,7 +281,7 @@ mod tests {
let key0 = Pubkey::new_unique(); let key0 = Pubkey::new_unique();
let key1 = Pubkey::new_unique(); let key1 = Pubkey::new_unique();
let key2 = Pubkey::new_unique(); let key2 = Pubkey::new_unique();
let mapped_msg = MappedMessage { let message = LoadedMessage {
message: v0::Message { message: v0::Message {
header: MessageHeader { header: MessageHeader {
num_required_signatures: 1, num_required_signatures: 1,
@ -289,13 +296,13 @@ mod tests {
}], }],
..v0::Message::default() ..v0::Message::default()
}, },
mapped_addresses: MappedAddresses { loaded_addresses: LoadedAddresses {
writable: vec![key1, key2], writable: vec![key1, key2],
readonly: vec![], readonly: vec![],
}, },
}; };
assert!(mapped_msg.is_writable_index(2)); assert!(message.is_writable_index(2));
assert!(!mapped_msg.is_writable(2, /*demote_program_write_locks=*/ true)); assert!(!message.is_writable(2, /*demote_program_write_locks=*/ true));
} }
} }

View 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());
}
}

View File

@ -3,7 +3,10 @@
use { use {
crate::{ crate::{
hash::Hash, hash::Hash,
message::{v0, MappedAddresses, MappedMessage, SanitizedMessage, VersionedMessage}, message::{
v0::{self, LoadedAddresses},
SanitizedMessage, VersionedMessage,
},
nonce::NONCED_TX_MARKER_IX_INDEX, nonce::NONCED_TX_MARKER_IX_INDEX,
precompiles::verify_if_precompile, precompiles::verify_if_precompile,
program_utils::limited_deserialize, program_utils::limited_deserialize,
@ -37,21 +40,21 @@ pub struct TransactionAccountLocks<'a> {
impl SanitizedTransaction { impl SanitizedTransaction {
/// Create a sanitized transaction from an unsanitized transaction. /// Create a sanitized transaction from an unsanitized transaction.
/// If the input transaction uses address maps, attempt to map the /// If the input transaction uses address tables, attempt to lookup
/// transaction keys to full addresses. /// the address for each table index.
pub fn try_create( pub fn try_create(
tx: VersionedTransaction, tx: VersionedTransaction,
message_hash: Hash, message_hash: Hash,
is_simple_vote_tx: Option<bool>, is_simple_vote_tx: Option<bool>,
address_mapper: impl Fn(&v0::Message) -> Result<MappedAddresses>, address_loader: impl Fn(&v0::Message) -> Result<LoadedAddresses>,
) -> Result<Self> { ) -> Result<Self> {
tx.sanitize()?; tx.sanitize()?;
let signatures = tx.signatures; let signatures = tx.signatures;
let message = match tx.message { let message = match tx.message {
VersionedMessage::Legacy(message) => SanitizedMessage::Legacy(message), VersionedMessage::Legacy(message) => SanitizedMessage::Legacy(message),
VersionedMessage::V0(message) => SanitizedMessage::V0(MappedMessage { VersionedMessage::V0(message) => SanitizedMessage::V0(v0::LoadedMessage {
mapped_addresses: address_mapper(&message)?, loaded_addresses: address_loader(&message)?,
message, message,
}), }),
}; };
@ -125,9 +128,9 @@ impl SanitizedTransaction {
pub fn to_versioned_transaction(&self) -> VersionedTransaction { pub fn to_versioned_transaction(&self) -> VersionedTransaction {
let signatures = self.signatures.clone(); let signatures = self.signatures.clone();
match &self.message { match &self.message {
SanitizedMessage::V0(mapped_msg) => VersionedTransaction { SanitizedMessage::V0(sanitized_msg) => VersionedTransaction {
signatures, signatures,
message: VersionedMessage::V0(mapped_msg.message.clone()), message: VersionedMessage::V0(sanitized_msg.message.clone()),
}, },
SanitizedMessage::Legacy(message) => VersionedTransaction { SanitizedMessage::Legacy(message) => VersionedTransaction {
signatures, signatures,
@ -193,7 +196,7 @@ impl SanitizedTransaction {
fn message_data(&self) -> Vec<u8> { fn message_data(&self) -> Vec<u8> {
match &self.message { match &self.message {
SanitizedMessage::Legacy(message) => message.serialize(), SanitizedMessage::Legacy(message) => message.serialize(),
SanitizedMessage::V0(mapped_msg) => mapped_msg.message.serialize(), SanitizedMessage::V0(message) => message.serialize(),
} }
} }

View File

@ -76,7 +76,10 @@ mod test {
solana_sdk::{ solana_sdk::{
hash::Hash, hash::Hash,
instruction::CompiledInstruction, instruction::CompiledInstruction,
message::{v0, MappedAddresses, MappedMessage, MessageHeader}, message::{
v0::{self, LoadedAddresses},
MessageHeader,
},
}, },
}; };
@ -125,7 +128,7 @@ mod test {
let sanitized_message = SanitizedMessage::Legacy(message); let sanitized_message = SanitizedMessage::Legacy(message);
assert_eq!(sanitized_message.extract_memos(), expected_memos); assert_eq!(sanitized_message.extract_memos(), expected_memos);
let mapped_message = MappedMessage { let sanitized_message = SanitizedMessage::V0(v0::LoadedMessage {
message: v0::Message { message: v0::Message {
header: MessageHeader { header: MessageHeader {
num_required_signatures: 1, num_required_signatures: 1,
@ -136,12 +139,11 @@ mod test {
instructions: memo_instructions, instructions: memo_instructions,
..v0::Message::default() ..v0::Message::default()
}, },
mapped_addresses: MappedAddresses { loaded_addresses: LoadedAddresses {
writable: vec![], writable: vec![],
readonly: vec![spl_memo_id_v1(), another_program_id, spl_memo_id_v3()], readonly: vec![spl_memo_id_v1(), another_program_id, spl_memo_id_v3()],
}, },
}; });
let sanitized_mapped_message = SanitizedMessage::V0(mapped_message); assert_eq!(sanitized_message.extract_memos(), expected_memos);
assert_eq!(sanitized_mapped_message.extract_memos(), expected_memos);
} }
} }