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