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