Migrate from address maps to address lookup tables (#21634)
* Migrate from address maps to address lookup tables * update sanitize error * cargo fmt * update abi
This commit is contained in:
@ -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",
|
||||||
|
@ -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;
|
||||||
|
@ -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()],
|
||||||
})
|
})
|
||||||
|
@ -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;
|
||||||
|
@ -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 {
|
||||||
|
@ -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::{
|
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![],
|
||||||
}],
|
}],
|
||||||
};
|
};
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
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 {
|
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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user