1use {
2 super::RuntimeTransaction,
3 crate::{
4 instruction_meta::InstructionMeta,
5 transaction_meta::{
6 CachedTransactionMeta, TransactionMeta, VersionedTransactionConfiguration,
7 },
8 transaction_with_meta::{StaticTransactionWithMeta, TransactionWithMeta},
9 },
10 solana_message::{AddressLoader, TransactionSignatureDetails},
11 solana_pubkey::Pubkey,
12 solana_svm_transaction::instruction::SVMInstruction,
13 solana_transaction::{
14 sanitized::{MessageHash, SanitizedTransaction},
15 simple_vote_transaction_checker::is_simple_vote_transaction,
16 versioned::{VersionedTransaction, sanitized::SanitizedVersionedTransaction},
17 },
18 solana_transaction_error::TransactionResult as Result,
19 std::{borrow::Cow, collections::HashSet},
20};
21
22impl RuntimeTransaction<SanitizedVersionedTransaction> {
23 pub fn try_from(
24 sanitized_versioned_tx: SanitizedVersionedTransaction,
25 message_hash: MessageHash,
26 is_simple_vote_tx: Option<bool>,
27 ) -> Result<Self> {
28 let message_hash = match message_hash {
29 MessageHash::Precomputed(hash) => hash,
30 MessageHash::Compute => sanitized_versioned_tx.get_message().message.hash(),
31 };
32 let is_simple_vote_tx = is_simple_vote_tx
33 .unwrap_or_else(|| is_simple_vote_transaction(&sanitized_versioned_tx));
34
35 let InstructionMeta {
36 precompile_signature_details,
37 instruction_data_len,
38 } = InstructionMeta::try_new(
39 sanitized_versioned_tx
40 .get_message()
41 .program_instructions_iter()
42 .map(|(program_id, ix)| (program_id, SVMInstruction::from(ix))),
43 )?;
44 let signature_details = TransactionSignatureDetails::new(
45 u64::from(
46 sanitized_versioned_tx
47 .get_message()
48 .message
49 .header()
50 .num_required_signatures,
51 ),
52 precompile_signature_details.num_secp256k1_instruction_signatures,
53 precompile_signature_details.num_ed25519_instruction_signatures,
54 precompile_signature_details.num_secp256r1_instruction_signatures,
55 );
56
57 let versioned_transaction_config =
58 VersionedTransactionConfiguration::try_from_sanitized_versioned_message(
59 sanitized_versioned_tx.get_message(),
60 )?;
61
62 Ok(Self {
63 transaction: sanitized_versioned_tx,
64 meta: CachedTransactionMeta {
65 message_hash,
66 is_simple_vote_transaction: is_simple_vote_tx,
67 signature_details,
68 versioned_transaction_config,
69 instruction_data_len,
70 },
71 })
72 }
73}
74
75impl RuntimeTransaction<SanitizedTransaction> {
76 pub fn try_create(
79 tx: VersionedTransaction,
80 message_hash: MessageHash,
81 is_simple_vote_tx: Option<bool>,
82 address_loader: impl AddressLoader,
83 reserved_account_keys: &HashSet<Pubkey>,
84 ) -> Result<Self> {
85 if tx.message.instructions().len()
86 > solana_transaction_context::MAX_INSTRUCTION_TRACE_LENGTH
87 {
88 return Err(solana_transaction_error::TransactionError::SanitizeFailure);
89 }
90
91 for instr in tx.message.instructions() {
92 if instr.accounts.len() > solana_transaction_context::MAX_ACCOUNTS_PER_INSTRUCTION {
93 return Err(solana_transaction_error::TransactionError::SanitizeFailure);
94 }
95 }
96
97 let statically_loaded_runtime_tx =
98 RuntimeTransaction::<SanitizedVersionedTransaction>::try_from(
99 SanitizedVersionedTransaction::try_from(tx)?,
100 message_hash,
101 is_simple_vote_tx,
102 )?;
103 Self::try_from(
104 statically_loaded_runtime_tx,
105 address_loader,
106 reserved_account_keys,
107 )
108 }
109
110 pub fn try_from(
114 statically_loaded_runtime_tx: RuntimeTransaction<SanitizedVersionedTransaction>,
115 address_loader: impl AddressLoader,
116 reserved_account_keys: &HashSet<Pubkey>,
117 ) -> Result<Self> {
118 let hash = *statically_loaded_runtime_tx.message_hash();
119 let is_simple_vote_tx = statically_loaded_runtime_tx.is_simple_vote_transaction();
120 let sanitized_transaction = SanitizedTransaction::try_new(
121 statically_loaded_runtime_tx.transaction,
122 hash,
123 is_simple_vote_tx,
124 address_loader,
125 reserved_account_keys,
126 )?;
127
128 let tx = Self {
129 transaction: sanitized_transaction,
130 meta: statically_loaded_runtime_tx.meta,
131 };
132
133 Ok(tx)
134 }
135}
136
137impl StaticTransactionWithMeta for RuntimeTransaction<SanitizedTransaction> {
138 #[inline]
139 fn to_versioned_transaction(&self) -> VersionedTransaction {
140 self.transaction.to_versioned_transaction()
141 }
142
143 fn serialized_size(&self) -> usize {
144 wincode::serialized_size(&self.to_versioned_transaction())
145 .expect("versioned transaction serialization should succeed") as usize
146 }
147}
148
149impl TransactionWithMeta for RuntimeTransaction<SanitizedTransaction> {
150 #[inline]
151 fn as_sanitized_transaction(&self) -> Cow<'_, SanitizedTransaction> {
152 Cow::Borrowed(self)
153 }
154}
155
156#[cfg(feature = "dev-context-only-utils")]
157impl RuntimeTransaction<SanitizedTransaction> {
158 pub fn from_transaction_for_tests(transaction: solana_transaction::Transaction) -> Self {
159 let versioned_transaction = VersionedTransaction::from(transaction);
160 Self::try_create(
161 versioned_transaction,
162 MessageHash::Compute,
163 None,
164 solana_message::SimpleAddressLoader::Disabled,
165 &HashSet::new(),
166 )
167 .expect("failed to create RuntimeTransaction from Transaction")
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use {
174 super::*,
175 agave_feature_set::FeatureSet,
176 agave_reserved_account_keys::ReservedAccountKeys,
177 solana_compute_budget_interface::ComputeBudgetInstruction,
178 solana_hash::Hash,
179 solana_instruction::{AccountMeta, Instruction},
180 solana_keypair::Keypair,
181 solana_message::{
182 Message, MessageHeader, SimpleAddressLoader, VersionedMessage,
183 compiled_instruction::CompiledInstruction,
184 },
185 solana_sdk_ids::vote,
186 solana_signature::Signature,
187 solana_signer::Signer,
188 solana_system_interface::instruction as system_instruction,
189 solana_transaction::{Transaction, versioned::VersionedTransaction},
190 };
191
192 fn vote_sanitized_versioned_transaction() -> SanitizedVersionedTransaction {
193 let block_hash = Hash::new_unique();
194 let vote_keypair = Keypair::new();
195 let node_keypair = Keypair::new();
196 let auth_keypair = Keypair::new();
197 let vote_ix = Instruction::new_with_bytes(
198 vote::id(),
199 &[],
200 vec![
201 AccountMeta::new(vote_keypair.pubkey(), false),
202 AccountMeta::new_readonly(auth_keypair.pubkey(), true),
203 ],
204 );
205 let mut vote_tx = Transaction::new_with_payer(&[vote_ix], Some(&node_keypair.pubkey()));
206 vote_tx.partial_sign(&[&node_keypair], block_hash);
207 vote_tx.partial_sign(&[&auth_keypair], block_hash);
208
209 SanitizedVersionedTransaction::try_from(VersionedTransaction::from(vote_tx)).unwrap()
210 }
211
212 fn non_vote_sanitized_versioned_transaction() -> SanitizedVersionedTransaction {
213 TestTransaction::new().to_sanitized_versioned_transaction()
214 }
215
216 struct TestTransaction {
219 from_keypair: Keypair,
220 hash: Hash,
221 instructions: Vec<Instruction>,
222 }
223
224 impl TestTransaction {
225 fn new() -> Self {
226 let from_keypair = Keypair::new();
227 let instructions = vec![system_instruction::transfer(
228 &from_keypair.pubkey(),
229 &solana_pubkey::new_rand(),
230 1,
231 )];
232 TestTransaction {
233 from_keypair,
234 hash: Hash::new_unique(),
235 instructions,
236 }
237 }
238
239 fn add_compute_unit_limit(&mut self, val: u32) -> &mut TestTransaction {
240 self.instructions
241 .push(ComputeBudgetInstruction::set_compute_unit_limit(val));
242 self
243 }
244
245 fn add_compute_unit_price(&mut self, val: u64) -> &mut TestTransaction {
246 self.instructions
247 .push(ComputeBudgetInstruction::set_compute_unit_price(val));
248 self
249 }
250
251 fn add_loaded_accounts_bytes(&mut self, val: u32) -> &mut TestTransaction {
252 self.instructions
253 .push(ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(val));
254 self
255 }
256
257 fn to_sanitized_versioned_transaction(&self) -> SanitizedVersionedTransaction {
258 let message = Message::new(&self.instructions, Some(&self.from_keypair.pubkey()));
259 let tx = Transaction::new(&[&self.from_keypair], message, self.hash);
260 SanitizedVersionedTransaction::try_from(VersionedTransaction::from(tx)).unwrap()
261 }
262 }
263
264 #[test]
265 fn test_runtime_transaction_is_vote_meta() {
266 fn get_is_simple_vote(
267 svt: SanitizedVersionedTransaction,
268 is_simple_vote: Option<bool>,
269 ) -> bool {
270 RuntimeTransaction::<SanitizedVersionedTransaction>::try_from(
271 svt,
272 MessageHash::Compute,
273 is_simple_vote,
274 )
275 .unwrap()
276 .meta
277 .is_simple_vote_transaction
278 }
279
280 assert!(!get_is_simple_vote(
281 non_vote_sanitized_versioned_transaction(),
282 None
283 ));
284
285 assert!(get_is_simple_vote(
286 non_vote_sanitized_versioned_transaction(),
287 Some(true), ));
289
290 assert!(get_is_simple_vote(
291 vote_sanitized_versioned_transaction(),
292 None
293 ));
294
295 assert!(!get_is_simple_vote(
296 vote_sanitized_versioned_transaction(),
297 Some(false), ));
299 }
300
301 #[test]
302 fn test_advancing_transaction_type() {
303 let hash = Hash::new_unique();
304
305 let statically_loaded_transaction =
306 RuntimeTransaction::<SanitizedVersionedTransaction>::try_from(
307 non_vote_sanitized_versioned_transaction(),
308 MessageHash::Precomputed(hash),
309 None,
310 )
311 .unwrap();
312
313 assert_eq!(hash, *statically_loaded_transaction.message_hash());
314 assert!(!statically_loaded_transaction.is_simple_vote_transaction());
315
316 let dynamically_loaded_transaction = RuntimeTransaction::<SanitizedTransaction>::try_from(
317 statically_loaded_transaction,
318 SimpleAddressLoader::Disabled,
319 &ReservedAccountKeys::empty_key_set(),
320 );
321 let dynamically_loaded_transaction =
322 dynamically_loaded_transaction.expect("created from statically loaded tx");
323
324 assert_eq!(hash, *dynamically_loaded_transaction.message_hash());
325 assert!(!dynamically_loaded_transaction.is_simple_vote_transaction());
326 }
327
328 #[test]
329 fn test_runtime_transaction_static_meta() {
330 let hash = Hash::new_unique();
331 let compute_unit_limit = 250_000;
332 let compute_unit_price = 1_000;
333 let loaded_accounts_bytes = 1_024;
334 let mut test_transaction = TestTransaction::new();
335
336 let runtime_transaction_static =
337 RuntimeTransaction::<SanitizedVersionedTransaction>::try_from(
338 test_transaction
339 .add_compute_unit_limit(compute_unit_limit)
340 .add_compute_unit_price(compute_unit_price)
341 .add_loaded_accounts_bytes(loaded_accounts_bytes)
342 .to_sanitized_versioned_transaction(),
343 MessageHash::Precomputed(hash),
344 None,
345 )
346 .unwrap();
347
348 assert_eq!(&hash, runtime_transaction_static.message_hash());
349 assert!(!runtime_transaction_static.is_simple_vote_transaction());
350
351 let signature_details = &runtime_transaction_static.meta.signature_details;
352 assert_eq!(1, signature_details.num_transaction_signatures());
353 assert_eq!(0, signature_details.num_secp256k1_instruction_signatures());
354 assert_eq!(0, signature_details.num_ed25519_instruction_signatures());
355
356 for feature_set in [FeatureSet::default(), FeatureSet::all_enabled()] {
357 let transaction_configuration = runtime_transaction_static
358 .transaction_configuration(&feature_set)
359 .unwrap();
360 assert_eq!(
361 compute_unit_limit,
362 transaction_configuration.compute_unit_limit
363 );
364 assert_eq!(
365 compute_unit_price,
366 transaction_configuration.compute_unit_price_in_microlamports()
367 );
368 assert_eq!(
369 loaded_accounts_bytes,
370 transaction_configuration.loaded_accounts_data_size_limit
371 );
372 }
373 }
374
375 #[test]
376 fn test_serialized_size() {
377 let transaction = RuntimeTransaction::<SanitizedTransaction>::try_from(
378 RuntimeTransaction::<SanitizedVersionedTransaction>::try_from(
379 non_vote_sanitized_versioned_transaction(),
380 MessageHash::Compute,
381 None,
382 )
383 .unwrap(),
384 SimpleAddressLoader::Disabled,
385 &ReservedAccountKeys::empty_key_set(),
386 )
387 .unwrap();
388
389 let expected = wincode::serialized_size(&transaction.to_versioned_transaction()).unwrap();
390 assert_eq!(transaction.serialized_size(), expected as usize);
391 assert_eq!(
392 expected,
393 wincode::serialize(&transaction.to_versioned_transaction())
394 .unwrap()
395 .len() as u64
396 );
397 }
398
399 #[test]
400 fn test_simd_406_instruction_accounts_limit() {
401 let account_keys = vec![Pubkey::new_unique(); 3];
402 let header = MessageHeader {
403 num_required_signatures: 1,
404 num_readonly_signed_accounts: 0,
405 num_readonly_unsigned_accounts: 1,
406 };
407 let mut accounts: Vec<u8> = vec![0; 254];
408 accounts.push(1);
410 let instr = CompiledInstruction::new_from_raw_parts(2, Vec::new(), accounts.clone());
411 let transaction = VersionedTransaction {
412 signatures: vec![Signature::default(); 1],
413 message: VersionedMessage::Legacy(solana_message::Message {
414 header,
415 account_keys: account_keys.clone(),
416 recent_blockhash: Hash::default(),
417 instructions: vec![instr],
418 }),
419 };
420 let result = RuntimeTransaction::<SanitizedTransaction>::try_create(
421 transaction,
422 MessageHash::Compute,
423 None,
424 solana_message::SimpleAddressLoader::Disabled,
425 &HashSet::new(),
426 );
427 assert!(result.is_ok());
428
429 accounts.push(2);
431 let instr = CompiledInstruction::new_from_raw_parts(2, Vec::new(), accounts.clone());
432 let transaction = VersionedTransaction {
433 signatures: vec![Signature::default(); 1],
434 message: VersionedMessage::Legacy(solana_message::Message {
435 header,
436 account_keys,
437 recent_blockhash: Hash::default(),
438 instructions: vec![instr],
439 }),
440 };
441 let result = RuntimeTransaction::<SanitizedTransaction>::try_create(
442 transaction,
443 MessageHash::Compute,
444 None,
445 solana_message::SimpleAddressLoader::Disabled,
446 &HashSet::new(),
447 );
448 assert_eq!(
449 result.err(),
450 Some(solana_transaction_error::TransactionError::SanitizeFailure)
451 );
452 }
453}