solana_runtime_transaction/runtime_transaction/
sdk_transactions.rs1use {
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 solana_message::{AddressLoader, TransactionSignatureDetails},
9 solana_pubkey::Pubkey,
10 solana_svm_transaction::instruction::SVMInstruction,
11 solana_transaction::{
12 sanitized::{MessageHash, SanitizedTransaction},
13 simple_vote_transaction_checker::is_simple_vote_transaction,
14 versioned::{sanitized::SanitizedVersionedTransaction, VersionedTransaction},
15 },
16 solana_transaction_error::TransactionResult as Result,
17 std::{borrow::Cow, collections::HashSet},
18};
19
20impl RuntimeTransaction<SanitizedVersionedTransaction> {
21 pub fn try_from(
22 sanitized_versioned_tx: SanitizedVersionedTransaction,
23 message_hash: MessageHash,
24 is_simple_vote_tx: Option<bool>,
25 ) -> Result<Self> {
26 let message_hash = match message_hash {
27 MessageHash::Precomputed(hash) => hash,
28 MessageHash::Compute => sanitized_versioned_tx.get_message().message.hash(),
29 };
30 let is_simple_vote_tx = is_simple_vote_tx
31 .unwrap_or_else(|| is_simple_vote_transaction(&sanitized_versioned_tx));
32
33 let precompile_signature_details = get_precompile_signature_details(
34 sanitized_versioned_tx
35 .get_message()
36 .program_instructions_iter()
37 .map(|(program_id, ix)| (program_id, SVMInstruction::from(ix))),
38 );
39 let signature_details = TransactionSignatureDetails::new(
40 u64::from(
41 sanitized_versioned_tx
42 .get_message()
43 .message
44 .header()
45 .num_required_signatures,
46 ),
47 precompile_signature_details.num_secp256k1_instruction_signatures,
48 precompile_signature_details.num_ed25519_instruction_signatures,
49 precompile_signature_details.num_secp256r1_instruction_signatures,
50 );
51 let compute_budget_instruction_details = ComputeBudgetInstructionDetails::try_from(
52 sanitized_versioned_tx
53 .get_message()
54 .program_instructions_iter()
55 .map(|(program_id, ix)| (program_id, SVMInstruction::from(ix))),
56 )?;
57
58 Ok(Self {
59 transaction: sanitized_versioned_tx,
60 meta: TransactionMeta {
61 message_hash,
62 is_simple_vote_transaction: is_simple_vote_tx,
63 signature_details,
64 compute_budget_instruction_details,
65 },
66 })
67 }
68}
69
70impl RuntimeTransaction<SanitizedTransaction> {
71 pub fn try_create(
74 tx: VersionedTransaction,
75 message_hash: MessageHash,
76 is_simple_vote_tx: Option<bool>,
77 address_loader: impl AddressLoader,
78 reserved_account_keys: &HashSet<Pubkey>,
79 ) -> Result<Self> {
80 let statically_loaded_runtime_tx =
81 RuntimeTransaction::<SanitizedVersionedTransaction>::try_from(
82 SanitizedVersionedTransaction::try_from(tx)?,
83 message_hash,
84 is_simple_vote_tx,
85 )?;
86 Self::try_from(
87 statically_loaded_runtime_tx,
88 address_loader,
89 reserved_account_keys,
90 )
91 }
92
93 pub fn try_from(
97 statically_loaded_runtime_tx: RuntimeTransaction<SanitizedVersionedTransaction>,
98 address_loader: impl AddressLoader,
99 reserved_account_keys: &HashSet<Pubkey>,
100 ) -> Result<Self> {
101 let hash = *statically_loaded_runtime_tx.message_hash();
102 let is_simple_vote_tx = statically_loaded_runtime_tx.is_simple_vote_transaction();
103 let sanitized_transaction = SanitizedTransaction::try_new(
104 statically_loaded_runtime_tx.transaction,
105 hash,
106 is_simple_vote_tx,
107 address_loader,
108 reserved_account_keys,
109 )?;
110
111 let mut tx = Self {
112 transaction: sanitized_transaction,
113 meta: statically_loaded_runtime_tx.meta,
114 };
115 tx.load_dynamic_metadata()?;
116
117 Ok(tx)
118 }
119
120 fn load_dynamic_metadata(&mut self) -> Result<()> {
121 Ok(())
122 }
123}
124
125impl TransactionWithMeta for RuntimeTransaction<SanitizedTransaction> {
126 #[inline]
127 fn as_sanitized_transaction(&self) -> Cow<SanitizedTransaction> {
128 Cow::Borrowed(self)
129 }
130
131 #[inline]
132 fn to_versioned_transaction(&self) -> VersionedTransaction {
133 self.transaction.to_versioned_transaction()
134 }
135}
136
137#[cfg(feature = "dev-context-only-utils")]
138impl RuntimeTransaction<SanitizedTransaction> {
139 pub fn from_transaction_for_tests(transaction: solana_transaction::Transaction) -> Self {
140 let versioned_transaction = VersionedTransaction::from(transaction);
141 Self::try_create(
142 versioned_transaction,
143 MessageHash::Compute,
144 None,
145 solana_message::SimpleAddressLoader::Disabled,
146 &HashSet::new(),
147 )
148 .expect("failed to create RuntimeTransaction from Transaction")
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use {
155 super::*,
156 solana_compute_budget_interface::ComputeBudgetInstruction,
157 solana_feature_set::FeatureSet,
158 solana_hash::Hash,
159 solana_instruction::Instruction,
160 solana_keypair::Keypair,
161 solana_message::{Message, SimpleAddressLoader},
162 solana_program::vote::{self, state::Vote},
163 solana_reserved_account_keys::ReservedAccountKeys,
164 solana_signer::Signer,
165 solana_system_interface::instruction as system_instruction,
166 solana_transaction::{versioned::VersionedTransaction, Transaction},
167 };
168
169 fn vote_sanitized_versioned_transaction() -> SanitizedVersionedTransaction {
170 let bank_hash = Hash::new_unique();
171 let block_hash = Hash::new_unique();
172 let vote_keypair = Keypair::new();
173 let node_keypair = Keypair::new();
174 let auth_keypair = Keypair::new();
175 let votes = Vote::new(vec![1, 2, 3], bank_hash);
176 let vote_ix =
177 vote::instruction::vote(&vote_keypair.pubkey(), &auth_keypair.pubkey(), votes);
178 let mut vote_tx = Transaction::new_with_payer(&[vote_ix], Some(&node_keypair.pubkey()));
179 vote_tx.partial_sign(&[&node_keypair], block_hash);
180 vote_tx.partial_sign(&[&auth_keypair], block_hash);
181
182 SanitizedVersionedTransaction::try_from(VersionedTransaction::from(vote_tx)).unwrap()
183 }
184
185 fn non_vote_sanitized_versioned_transaction() -> SanitizedVersionedTransaction {
186 TestTransaction::new().to_sanitized_versioned_transaction()
187 }
188
189 struct TestTransaction {
192 from_keypair: Keypair,
193 hash: Hash,
194 instructions: Vec<Instruction>,
195 }
196
197 impl TestTransaction {
198 fn new() -> Self {
199 let from_keypair = Keypair::new();
200 let instructions = vec![system_instruction::transfer(
201 &from_keypair.pubkey(),
202 &solana_pubkey::new_rand(),
203 1,
204 )];
205 TestTransaction {
206 from_keypair,
207 hash: Hash::new_unique(),
208 instructions,
209 }
210 }
211
212 fn add_compute_unit_limit(&mut self, val: u32) -> &mut TestTransaction {
213 self.instructions
214 .push(ComputeBudgetInstruction::set_compute_unit_limit(val));
215 self
216 }
217
218 fn add_compute_unit_price(&mut self, val: u64) -> &mut TestTransaction {
219 self.instructions
220 .push(ComputeBudgetInstruction::set_compute_unit_price(val));
221 self
222 }
223
224 fn add_loaded_accounts_bytes(&mut self, val: u32) -> &mut TestTransaction {
225 self.instructions
226 .push(ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(val));
227 self
228 }
229
230 fn to_sanitized_versioned_transaction(&self) -> SanitizedVersionedTransaction {
231 let message = Message::new(&self.instructions, Some(&self.from_keypair.pubkey()));
232 let tx = Transaction::new(&[&self.from_keypair], message, self.hash);
233 SanitizedVersionedTransaction::try_from(VersionedTransaction::from(tx)).unwrap()
234 }
235 }
236
237 #[test]
238 fn test_runtime_transaction_is_vote_meta() {
239 fn get_is_simple_vote(
240 svt: SanitizedVersionedTransaction,
241 is_simple_vote: Option<bool>,
242 ) -> bool {
243 RuntimeTransaction::<SanitizedVersionedTransaction>::try_from(
244 svt,
245 MessageHash::Compute,
246 is_simple_vote,
247 )
248 .unwrap()
249 .meta
250 .is_simple_vote_transaction
251 }
252
253 assert!(!get_is_simple_vote(
254 non_vote_sanitized_versioned_transaction(),
255 None
256 ));
257
258 assert!(get_is_simple_vote(
259 non_vote_sanitized_versioned_transaction(),
260 Some(true), ));
262
263 assert!(get_is_simple_vote(
264 vote_sanitized_versioned_transaction(),
265 None
266 ));
267
268 assert!(!get_is_simple_vote(
269 vote_sanitized_versioned_transaction(),
270 Some(false), ));
272 }
273
274 #[test]
275 fn test_advancing_transaction_type() {
276 let hash = Hash::new_unique();
277
278 let statically_loaded_transaction =
279 RuntimeTransaction::<SanitizedVersionedTransaction>::try_from(
280 non_vote_sanitized_versioned_transaction(),
281 MessageHash::Precomputed(hash),
282 None,
283 )
284 .unwrap();
285
286 assert_eq!(hash, *statically_loaded_transaction.message_hash());
287 assert!(!statically_loaded_transaction.is_simple_vote_transaction());
288
289 let dynamically_loaded_transaction = RuntimeTransaction::<SanitizedTransaction>::try_from(
290 statically_loaded_transaction,
291 SimpleAddressLoader::Disabled,
292 &ReservedAccountKeys::empty_key_set(),
293 );
294 let dynamically_loaded_transaction =
295 dynamically_loaded_transaction.expect("created from statically loaded tx");
296
297 assert_eq!(hash, *dynamically_loaded_transaction.message_hash());
298 assert!(!dynamically_loaded_transaction.is_simple_vote_transaction());
299 }
300
301 #[test]
302 fn test_runtime_transaction_static_meta() {
303 let hash = Hash::new_unique();
304 let compute_unit_limit = 250_000;
305 let compute_unit_price = 1_000;
306 let loaded_accounts_bytes = 1_024;
307 let mut test_transaction = TestTransaction::new();
308
309 let runtime_transaction_static =
310 RuntimeTransaction::<SanitizedVersionedTransaction>::try_from(
311 test_transaction
312 .add_compute_unit_limit(compute_unit_limit)
313 .add_compute_unit_price(compute_unit_price)
314 .add_loaded_accounts_bytes(loaded_accounts_bytes)
315 .to_sanitized_versioned_transaction(),
316 MessageHash::Precomputed(hash),
317 None,
318 )
319 .unwrap();
320
321 assert_eq!(&hash, runtime_transaction_static.message_hash());
322 assert!(!runtime_transaction_static.is_simple_vote_transaction());
323
324 let signature_details = &runtime_transaction_static.meta.signature_details;
325 assert_eq!(1, signature_details.num_transaction_signatures());
326 assert_eq!(0, signature_details.num_secp256k1_instruction_signatures());
327 assert_eq!(0, signature_details.num_ed25519_instruction_signatures());
328
329 for feature_set in [FeatureSet::default(), FeatureSet::all_enabled()] {
330 let compute_budget_limits = runtime_transaction_static
331 .compute_budget_instruction_details()
332 .sanitize_and_convert_to_compute_budget_limits(&feature_set)
333 .unwrap();
334 assert_eq!(compute_unit_limit, compute_budget_limits.compute_unit_limit);
335 assert_eq!(compute_unit_price, compute_budget_limits.compute_unit_price);
336 assert_eq!(
337 loaded_accounts_bytes,
338 compute_budget_limits.loaded_accounts_bytes.get()
339 );
340 }
341 }
342}