1use {
2 super::{ComputeBudgetInstructionDetails, RuntimeTransaction},
3 crate::{
4 signature_details::get_precompile_signature_details,
5 transaction_meta::{StaticMeta, TransactionMeta},
6 transaction_with_meta::TransactionWithMeta,
7 },
8 agave_transaction_view::{
9 resolved_transaction_view::ResolvedTransactionView, transaction_data::TransactionData,
10 transaction_version::TransactionVersion, transaction_view::SanitizedTransactionView,
11 },
12 solana_message::{
13 compiled_instruction::CompiledInstruction,
14 v0::{LoadedAddresses, LoadedMessage, MessageAddressTableLookup},
15 LegacyMessage, MessageHeader, SanitizedMessage, TransactionSignatureDetails,
16 VersionedMessage,
17 },
18 solana_pubkey::Pubkey,
19 solana_svm_transaction::svm_message::SVMMessage,
20 solana_transaction::{
21 sanitized::{MessageHash, SanitizedTransaction},
22 simple_vote_transaction_checker::is_simple_vote_transaction_impl,
23 versioned::VersionedTransaction,
24 },
25 solana_transaction_error::{TransactionError, TransactionResult as Result},
26 std::{borrow::Cow, collections::HashSet},
27};
28
29fn is_simple_vote_transaction<D: TransactionData>(
30 transaction: &SanitizedTransactionView<D>,
31) -> bool {
32 let signatures = transaction.signatures();
33 let is_legacy_message = matches!(transaction.version(), TransactionVersion::Legacy);
34 let instruction_programs = transaction
35 .program_instructions_iter()
36 .map(|(program_id, _ix)| program_id);
37
38 is_simple_vote_transaction_impl(signatures, is_legacy_message, instruction_programs)
39}
40
41impl<D: TransactionData> RuntimeTransaction<SanitizedTransactionView<D>> {
42 pub fn try_from(
43 transaction: SanitizedTransactionView<D>,
44 message_hash: MessageHash,
45 is_simple_vote_tx: Option<bool>,
46 ) -> Result<Self> {
47 let message_hash = match message_hash {
48 MessageHash::Precomputed(hash) => hash,
49 MessageHash::Compute => VersionedMessage::hash_raw_message(transaction.message_data()),
50 };
51 let is_simple_vote_tx =
52 is_simple_vote_tx.unwrap_or_else(|| is_simple_vote_transaction(&transaction));
53
54 let precompile_signature_details =
55 get_precompile_signature_details(transaction.program_instructions_iter());
56 let signature_details = TransactionSignatureDetails::new(
57 u64::from(transaction.num_required_signatures()),
58 precompile_signature_details.num_secp256k1_instruction_signatures,
59 precompile_signature_details.num_ed25519_instruction_signatures,
60 precompile_signature_details.num_secp256r1_instruction_signatures,
61 );
62 let compute_budget_instruction_details =
63 ComputeBudgetInstructionDetails::try_from(transaction.program_instructions_iter())?;
64
65 Ok(Self {
66 transaction,
67 meta: TransactionMeta {
68 message_hash,
69 is_simple_vote_transaction: is_simple_vote_tx,
70 signature_details,
71 compute_budget_instruction_details,
72 },
73 })
74 }
75}
76
77impl<D: TransactionData> RuntimeTransaction<ResolvedTransactionView<D>> {
78 pub fn try_from(
82 statically_loaded_runtime_tx: RuntimeTransaction<SanitizedTransactionView<D>>,
83 loaded_addresses: Option<LoadedAddresses>,
84 reserved_account_keys: &HashSet<Pubkey>,
85 ) -> Result<Self> {
86 let RuntimeTransaction { transaction, meta } = statically_loaded_runtime_tx;
87 let transaction =
92 ResolvedTransactionView::try_new(transaction, loaded_addresses, reserved_account_keys)
93 .map_err(|_| TransactionError::SanitizeFailure)?;
94 let mut tx = Self { transaction, meta };
95 tx.load_dynamic_metadata()?;
96
97 Ok(tx)
98 }
99
100 fn load_dynamic_metadata(&mut self) -> Result<()> {
101 Ok(())
102 }
103}
104
105impl<D: TransactionData> TransactionWithMeta for RuntimeTransaction<ResolvedTransactionView<D>> {
106 fn as_sanitized_transaction(&self) -> Cow<SanitizedTransaction> {
107 let VersionedTransaction {
108 signatures,
109 message,
110 } = self.to_versioned_transaction();
111
112 let is_writable_account_cache = (0..self.transaction.total_num_accounts())
113 .map(|index| self.is_writable(usize::from(index)))
114 .collect();
115
116 let message = match message {
117 VersionedMessage::Legacy(message) => SanitizedMessage::Legacy(LegacyMessage {
118 message: Cow::Owned(message),
119 is_writable_account_cache,
120 }),
121 VersionedMessage::V0(message) => SanitizedMessage::V0(LoadedMessage {
122 message: Cow::Owned(message),
123 loaded_addresses: Cow::Owned(self.loaded_addresses().unwrap().clone()),
124 is_writable_account_cache,
125 }),
126 };
127
128 Cow::Owned(
132 SanitizedTransaction::try_new_from_fields(
133 message,
134 *self.message_hash(),
135 self.is_simple_vote_transaction(),
136 signatures,
137 )
138 .expect("transaction view is sanitized"),
139 )
140 }
141
142 fn to_versioned_transaction(&self) -> VersionedTransaction {
143 let header = MessageHeader {
144 num_required_signatures: self.num_required_signatures(),
145 num_readonly_signed_accounts: self.num_readonly_signed_static_accounts(),
146 num_readonly_unsigned_accounts: self.num_readonly_unsigned_static_accounts(),
147 };
148 let static_account_keys = self.static_account_keys().to_vec();
149 let recent_blockhash = *self.recent_blockhash();
150 let instructions = self
151 .instructions_iter()
152 .map(|ix| CompiledInstruction {
153 program_id_index: ix.program_id_index,
154 accounts: ix.accounts.to_vec(),
155 data: ix.data.to_vec(),
156 })
157 .collect();
158
159 let message = match self.version() {
160 TransactionVersion::Legacy => {
161 VersionedMessage::Legacy(solana_message::legacy::Message {
162 header,
163 account_keys: static_account_keys,
164 recent_blockhash,
165 instructions,
166 })
167 }
168 TransactionVersion::V0 => VersionedMessage::V0(solana_message::v0::Message {
169 header,
170 account_keys: static_account_keys,
171 recent_blockhash,
172 instructions,
173 address_table_lookups: self
174 .address_table_lookup_iter()
175 .map(|atl| MessageAddressTableLookup {
176 account_key: *atl.account_key,
177 writable_indexes: atl.writable_indexes.to_vec(),
178 readonly_indexes: atl.readonly_indexes.to_vec(),
179 })
180 .collect(),
181 }),
182 };
183
184 VersionedTransaction {
185 signatures: self.signatures().to_vec(),
186 message,
187 }
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use {
194 super::*,
195 solana_hash::Hash,
196 solana_keypair::Keypair,
197 solana_message::{v0, AddressLookupTableAccount, SimpleAddressLoader},
198 solana_reserved_account_keys::ReservedAccountKeys,
199 solana_signature::Signature,
200 solana_system_interface::instruction as system_instruction,
201 solana_system_transaction as system_transaction,
202 };
203
204 #[test]
205 fn test_advancing_transaction_type() {
206 let serialized_transaction = {
208 let transaction = VersionedTransaction::from(system_transaction::transfer(
209 &Keypair::new(),
210 &Pubkey::new_unique(),
211 1,
212 Hash::new_unique(),
213 ));
214 bincode::serialize(&transaction).unwrap()
215 };
216
217 let hash = Hash::new_unique();
218 let transaction =
219 SanitizedTransactionView::try_new_sanitized(&serialized_transaction[..]).unwrap();
220 let static_runtime_transaction =
221 RuntimeTransaction::<SanitizedTransactionView<_>>::try_from(
222 transaction,
223 MessageHash::Precomputed(hash),
224 None,
225 )
226 .unwrap();
227
228 assert_eq!(hash, *static_runtime_transaction.message_hash());
229 assert!(!static_runtime_transaction.is_simple_vote_transaction());
230
231 let dynamic_runtime_transaction =
232 RuntimeTransaction::<ResolvedTransactionView<_>>::try_from(
233 static_runtime_transaction,
234 None,
235 &ReservedAccountKeys::empty_key_set(),
236 )
237 .unwrap();
238
239 assert_eq!(hash, *dynamic_runtime_transaction.message_hash());
240 assert!(!dynamic_runtime_transaction.is_simple_vote_transaction());
241 }
242
243 #[test]
244 fn test_to_versioned_transaction() {
245 fn assert_translation(
246 original_transaction: VersionedTransaction,
247 loaded_addresses: Option<LoadedAddresses>,
248 reserved_account_keys: &HashSet<Pubkey>,
249 ) {
250 let bytes = bincode::serialize(&original_transaction).unwrap();
251 let transaction_view = SanitizedTransactionView::try_new_sanitized(&bytes[..]).unwrap();
252 let runtime_transaction = RuntimeTransaction::<SanitizedTransactionView<_>>::try_from(
253 transaction_view,
254 MessageHash::Compute,
255 None,
256 )
257 .unwrap();
258 let runtime_transaction = RuntimeTransaction::<ResolvedTransactionView<_>>::try_from(
259 runtime_transaction,
260 loaded_addresses,
261 reserved_account_keys,
262 )
263 .unwrap();
264
265 let versioned_transaction = runtime_transaction.to_versioned_transaction();
266 assert_eq!(original_transaction, versioned_transaction);
267 }
268
269 let reserved_key_set = ReservedAccountKeys::empty_key_set();
270
271 let original_transaction = VersionedTransaction::from(system_transaction::transfer(
273 &Keypair::new(),
274 &Pubkey::new_unique(),
275 1,
276 Hash::new_unique(),
277 ));
278 assert_translation(original_transaction, None, &reserved_key_set);
279
280 let payer = Pubkey::new_unique();
282 let to = Pubkey::new_unique();
283 let original_transaction = VersionedTransaction {
284 signatures: vec![Signature::default()], message: VersionedMessage::V0(
286 v0::Message::try_compile(
287 &payer,
288 &[system_instruction::transfer(&payer, &to, 1)],
289 &[AddressLookupTableAccount {
290 key: Pubkey::new_unique(),
291 addresses: vec![to],
292 }],
293 Hash::default(),
294 )
295 .unwrap(),
296 ),
297 };
298 assert_translation(
299 original_transaction,
300 Some(LoadedAddresses {
301 writable: vec![to],
302 readonly: vec![],
303 }),
304 &reserved_key_set,
305 );
306 }
307
308 #[test]
309 fn test_as_sanitized_transaction() {
310 fn assert_translation(
311 original_transaction: SanitizedTransaction,
312 loaded_addresses: Option<LoadedAddresses>,
313 reserved_account_keys: &HashSet<Pubkey>,
314 ) {
315 let bytes =
316 bincode::serialize(&original_transaction.to_versioned_transaction()).unwrap();
317 let transaction_view = SanitizedTransactionView::try_new_sanitized(&bytes[..]).unwrap();
318 let runtime_transaction = RuntimeTransaction::<SanitizedTransactionView<_>>::try_from(
319 transaction_view,
320 MessageHash::Compute,
321 None,
322 )
323 .unwrap();
324 let runtime_transaction = RuntimeTransaction::<ResolvedTransactionView<_>>::try_from(
325 runtime_transaction,
326 loaded_addresses,
327 reserved_account_keys,
328 )
329 .unwrap();
330
331 let sanitized_transaction = runtime_transaction.as_sanitized_transaction();
332 assert_eq!(
333 sanitized_transaction.message_hash(),
334 original_transaction.message_hash()
335 );
336 }
337
338 let reserved_key_set = ReservedAccountKeys::empty_key_set();
339
340 let original_transaction = VersionedTransaction::from(system_transaction::transfer(
342 &Keypair::new(),
343 &Pubkey::new_unique(),
344 1,
345 Hash::new_unique(),
346 ));
347 let sanitized_transaction = SanitizedTransaction::try_create(
348 original_transaction,
349 MessageHash::Compute,
350 None,
351 SimpleAddressLoader::Disabled,
352 &reserved_key_set,
353 )
354 .unwrap();
355 assert_translation(sanitized_transaction, None, &reserved_key_set);
356
357 let payer = Pubkey::new_unique();
359 let to = Pubkey::new_unique();
360 let original_transaction = VersionedTransaction {
361 signatures: vec![Signature::default()], message: VersionedMessage::V0(
363 v0::Message::try_compile(
364 &payer,
365 &[system_instruction::transfer(&payer, &to, 1)],
366 &[AddressLookupTableAccount {
367 key: Pubkey::new_unique(),
368 addresses: vec![to],
369 }],
370 Hash::default(),
371 )
372 .unwrap(),
373 ),
374 };
375 let loaded_addresses = LoadedAddresses {
376 writable: vec![to],
377 readonly: vec![],
378 };
379 let sanitized_transaction = SanitizedTransaction::try_create(
380 original_transaction,
381 MessageHash::Compute,
382 None,
383 SimpleAddressLoader::Enabled(loaded_addresses.clone()),
384 &reserved_key_set,
385 )
386 .unwrap();
387 assert_translation(
388 sanitized_transaction,
389 Some(loaded_addresses),
390 &reserved_key_set,
391 );
392 }
393}