Skip to content

Commit

Permalink
impl TransactionWithMeta for ResolvedTransactonView (#3651)
Browse files Browse the repository at this point in the history
  • Loading branch information
apfitzge authored Nov 25, 2024
1 parent 563d81f commit a9dbf47
Show file tree
Hide file tree
Showing 11 changed files with 471 additions and 29 deletions.
4 changes: 2 additions & 2 deletions core/src/banking_stage/committer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use {
solana_transaction_status::{
token_balances::TransactionTokenBalancesSet, TransactionTokenBalance,
},
std::{borrow::Borrow, collections::HashMap, sync::Arc},
std::{collections::HashMap, sync::Arc},
};

#[derive(Clone, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -140,7 +140,7 @@ impl Committer {
let txs = batch
.sanitized_transactions()
.iter()
.map(|tx| tx.as_sanitized_transaction().borrow().clone())
.map(|tx| tx.as_sanitized_transaction().into_owned())
.collect_vec();
let post_balances = bank.collect_balances(batch);
let post_token_balances =
Expand Down
4 changes: 3 additions & 1 deletion cost-model/src/transaction_cost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,9 @@ impl solana_runtime_transaction::transaction_meta::StaticMeta for WritableKeysTr
#[cfg(feature = "dev-context-only-utils")]
impl TransactionWithMeta for WritableKeysTransaction {
#[allow(refining_impl_trait)]
fn as_sanitized_transaction(&self) -> solana_sdk::transaction::SanitizedTransaction {
fn as_sanitized_transaction(
&self,
) -> std::borrow::Cow<solana_sdk::transaction::SanitizedTransaction> {
unimplemented!("WritableKeysTransaction::as_sanitized_transaction");
}

Expand Down
3 changes: 1 addition & 2 deletions ledger/src/blockstore_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ use {
solana_transaction_status::token_balances::TransactionTokenBalancesSet,
solana_vote::vote_account::VoteAccountsHashMap,
std::{
borrow::Borrow,
collections::{HashMap, HashSet},
ops::{Index, Range},
path::PathBuf,
Expand Down Expand Up @@ -208,7 +207,7 @@ pub fn execute_batch(
let transactions: Vec<SanitizedTransaction> = batch
.sanitized_transactions()
.iter()
.map(|tx| tx.as_sanitized_transaction().borrow().clone())
.map(|tx| tx.as_sanitized_transaction().into_owned())
.collect();
let post_token_balances = if record_token_balances {
collect_token_balances(bank, batch, &mut mint_decimals)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use {
transaction_meta::{StaticMeta, TransactionMeta},
transaction_with_meta::TransactionWithMeta,
},
core::borrow::Borrow,
solana_pubkey::Pubkey,
solana_sdk::{
message::{AddressLoader, TransactionSignatureDetails},
Expand All @@ -16,7 +15,7 @@ use {
},
},
solana_svm_transaction::instruction::SVMInstruction,
std::collections::HashSet,
std::{borrow::Cow, collections::HashSet},
};

impl RuntimeTransaction<SanitizedVersionedTransaction> {
Expand Down Expand Up @@ -125,8 +124,8 @@ impl RuntimeTransaction<SanitizedTransaction> {

impl TransactionWithMeta for RuntimeTransaction<SanitizedTransaction> {
#[inline]
fn as_sanitized_transaction(&self) -> impl Borrow<SanitizedTransaction> {
&self.transaction
fn as_sanitized_transaction(&self) -> Cow<SanitizedTransaction> {
Cow::Borrowed(self)
}

#[inline]
Expand Down
269 changes: 256 additions & 13 deletions runtime-transaction/src/runtime_transaction/transaction_view.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
use {
super::{ComputeBudgetInstructionDetails, RuntimeTransaction},
crate::{
signature_details::get_precompile_signature_details, transaction_meta::TransactionMeta,
signature_details::get_precompile_signature_details,
transaction_meta::{StaticMeta, TransactionMeta},
transaction_with_meta::TransactionWithMeta,
},
agave_transaction_view::{
resolved_transaction_view::ResolvedTransactionView, transaction_data::TransactionData,
transaction_version::TransactionVersion, transaction_view::SanitizedTransactionView,
},
solana_pubkey::Pubkey,
solana_sdk::{
message::{v0::LoadedAddresses, TransactionSignatureDetails, VersionedMessage},
instruction::CompiledInstruction,
message::{
v0::{LoadedAddresses, LoadedMessage, MessageAddressTableLookup},
LegacyMessage, MessageHeader, SanitizedMessage, TransactionSignatureDetails,
VersionedMessage,
},
simple_vote_transaction_checker::is_simple_vote_transaction_impl,
transaction::{MessageHash, Result, TransactionError},
transaction::{
MessageHash, Result, SanitizedTransaction, TransactionError, VersionedTransaction,
},
},
std::collections::HashSet,
solana_svm_transaction::svm_message::SVMMessage,
std::{borrow::Cow, collections::HashSet},
};

fn is_simple_vote_transaction<D: TransactionData>(
Expand Down Expand Up @@ -91,21 +101,103 @@ impl<D: TransactionData> RuntimeTransaction<ResolvedTransactionView<D>> {
}
}

impl<D: TransactionData> TransactionWithMeta for RuntimeTransaction<ResolvedTransactionView<D>> {
fn as_sanitized_transaction(&self) -> Cow<SanitizedTransaction> {
let VersionedTransaction {
signatures,
message,
} = self.to_versioned_transaction();

let is_writable_account_cache = (0..self.transaction.total_num_accounts())
.map(|index| self.is_writable(usize::from(index)))
.collect();

let message = match message {
VersionedMessage::Legacy(message) => SanitizedMessage::Legacy(LegacyMessage {
message: Cow::Owned(message),
is_writable_account_cache,
}),
VersionedMessage::V0(message) => SanitizedMessage::V0(LoadedMessage {
message: Cow::Owned(message),
loaded_addresses: Cow::Owned(self.loaded_addresses().unwrap().clone()),
is_writable_account_cache,
}),
};

// SAFETY:
// - Simple conversion between different formats
// - `ResolvedTransactionView` has undergone sanitization checks
Cow::Owned(
SanitizedTransaction::try_new_from_fields(
message,
*self.message_hash(),
self.is_simple_vote_transaction(),
signatures,
)
.expect("transaction view is sanitized"),
)
}

fn to_versioned_transaction(&self) -> VersionedTransaction {
let header = MessageHeader {
num_required_signatures: self.num_required_signatures(),
num_readonly_signed_accounts: self.num_readonly_signed_static_accounts(),
num_readonly_unsigned_accounts: self.num_readonly_unsigned_static_accounts(),
};
let static_account_keys = self.static_account_keys().to_vec();
let recent_blockhash = *self.recent_blockhash();
let instructions = self
.instructions_iter()
.map(|ix| CompiledInstruction {
program_id_index: ix.program_id_index,
accounts: ix.accounts.to_vec(),
data: ix.data.to_vec(),
})
.collect();

let message = match self.version() {
TransactionVersion::Legacy => {
VersionedMessage::Legacy(solana_sdk::message::legacy::Message {
header,
account_keys: static_account_keys,
recent_blockhash,
instructions,
})
}
TransactionVersion::V0 => VersionedMessage::V0(solana_sdk::message::v0::Message {
header,
account_keys: static_account_keys,
recent_blockhash,
instructions,
address_table_lookups: self
.address_table_lookup_iter()
.map(|atl| MessageAddressTableLookup {
account_key: *atl.account_key,
writable_indexes: atl.writable_indexes.to_vec(),
readonly_indexes: atl.readonly_indexes.to_vec(),
})
.collect(),
}),
};

VersionedTransaction {
signatures: self.signatures().to_vec(),
message,
}
}
}

#[cfg(test)]
mod tests {
use {
crate::{runtime_transaction::RuntimeTransaction, transaction_meta::StaticMeta},
agave_transaction_view::{
resolved_transaction_view::ResolvedTransactionView,
transaction_view::SanitizedTransactionView,
},
solana_pubkey::Pubkey,
super::*,
solana_sdk::{
address_lookup_table::AddressLookupTableAccount,
hash::Hash,
message::{v0, SimpleAddressLoader},
reserved_account_keys::ReservedAccountKeys,
signature::Keypair,
system_transaction,
transaction::{MessageHash, VersionedTransaction},
signature::{Keypair, Signature},
system_instruction, system_transaction,
},
};

Expand Down Expand Up @@ -147,4 +239,155 @@ mod tests {
assert_eq!(hash, *dynamic_runtime_transaction.message_hash());
assert!(!dynamic_runtime_transaction.is_simple_vote_transaction());
}

#[test]
fn test_to_versioned_transaction() {
fn assert_translation(
original_transaction: VersionedTransaction,
loaded_addresses: Option<LoadedAddresses>,
reserved_account_keys: &HashSet<Pubkey>,
) {
let bytes = bincode::serialize(&original_transaction).unwrap();
let transaction_view = SanitizedTransactionView::try_new_sanitized(&bytes[..]).unwrap();
let runtime_transaction = RuntimeTransaction::<SanitizedTransactionView<_>>::try_from(
transaction_view,
MessageHash::Compute,
None,
)
.unwrap();
let runtime_transaction = RuntimeTransaction::<ResolvedTransactionView<_>>::try_from(
runtime_transaction,
loaded_addresses,
reserved_account_keys,
)
.unwrap();

let versioned_transaction = runtime_transaction.to_versioned_transaction();
assert_eq!(original_transaction, versioned_transaction);
}

let reserved_key_set = ReservedAccountKeys::empty_key_set();

// Simple transfer.
let original_transaction = VersionedTransaction::from(system_transaction::transfer(
&Keypair::new(),
&Pubkey::new_unique(),
1,
Hash::new_unique(),
));
assert_translation(original_transaction, None, &reserved_key_set);

// Simple transfer with loaded addresses.
let payer = Pubkey::new_unique();
let to = Pubkey::new_unique();
let original_transaction = VersionedTransaction {
signatures: vec![Signature::default()], // 1 signature to be valid.
message: VersionedMessage::V0(
v0::Message::try_compile(
&payer,
&[system_instruction::transfer(&payer, &to, 1)],
&[AddressLookupTableAccount {
key: Pubkey::new_unique(),
addresses: vec![to],
}],
Hash::default(),
)
.unwrap(),
),
};
assert_translation(
original_transaction,
Some(LoadedAddresses {
writable: vec![to],
readonly: vec![],
}),
&reserved_key_set,
);
}

#[test]
fn test_as_sanitized_transaction() {
fn assert_translation(
original_transaction: SanitizedTransaction,
loaded_addresses: Option<LoadedAddresses>,
reserved_account_keys: &HashSet<Pubkey>,
) {
let bytes =
bincode::serialize(&original_transaction.to_versioned_transaction()).unwrap();
let transaction_view = SanitizedTransactionView::try_new_sanitized(&bytes[..]).unwrap();
let runtime_transaction = RuntimeTransaction::<SanitizedTransactionView<_>>::try_from(
transaction_view,
MessageHash::Compute,
None,
)
.unwrap();
let runtime_transaction = RuntimeTransaction::<ResolvedTransactionView<_>>::try_from(
runtime_transaction,
loaded_addresses,
reserved_account_keys,
)
.unwrap();

let sanitized_transaction = runtime_transaction.as_sanitized_transaction();
assert_eq!(
sanitized_transaction.message_hash(),
original_transaction.message_hash()
);
}

let reserved_key_set = ReservedAccountKeys::empty_key_set();

// Simple transfer.
let original_transaction = VersionedTransaction::from(system_transaction::transfer(
&Keypair::new(),
&Pubkey::new_unique(),
1,
Hash::new_unique(),
));
let sanitized_transaction = SanitizedTransaction::try_create(
original_transaction,
MessageHash::Compute,
None,
SimpleAddressLoader::Disabled,
&reserved_key_set,
)
.unwrap();
assert_translation(sanitized_transaction, None, &reserved_key_set);

// Simple transfer with loaded addresses.
let payer = Pubkey::new_unique();
let to = Pubkey::new_unique();
let original_transaction = VersionedTransaction {
signatures: vec![Signature::default()], // 1 signature to be valid.
message: VersionedMessage::V0(
v0::Message::try_compile(
&payer,
&[system_instruction::transfer(&payer, &to, 1)],
&[AddressLookupTableAccount {
key: Pubkey::new_unique(),
addresses: vec![to],
}],
Hash::default(),
)
.unwrap(),
),
};
let loaded_addresses = LoadedAddresses {
writable: vec![to],
readonly: vec![],
};
let sanitized_transaction = SanitizedTransaction::try_create(
original_transaction,
MessageHash::Compute,
None,
SimpleAddressLoader::Enabled(loaded_addresses.clone()),
&reserved_key_set,
)
.unwrap();
assert_translation(
sanitized_transaction,
Some(loaded_addresses),
&reserved_key_set,
);
}
}
4 changes: 2 additions & 2 deletions runtime-transaction/src/transaction_with_meta.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use {
crate::transaction_meta::StaticMeta,
core::borrow::Borrow,
solana_sdk::transaction::{SanitizedTransaction, VersionedTransaction},
solana_svm_transaction::svm_transaction::SVMTransaction,
std::borrow::Cow,
};

pub trait TransactionWithMeta: StaticMeta + SVMTransaction {
// Required to interact with geyser plugins.
fn as_sanitized_transaction(&self) -> impl Borrow<SanitizedTransaction>;
fn as_sanitized_transaction(&self) -> Cow<SanitizedTransaction>;
fn to_versioned_transaction(&self) -> VersionedTransaction;
}
Loading

0 comments on commit a9dbf47

Please sign in to comment.