solana_runtime_transaction/
runtime_transaction.rs1use {
13 crate::{
14 compute_budget_instruction_details::*,
15 signature_details::get_precompile_signature_details,
16 transaction_meta::{DynamicMeta, StaticMeta, TransactionMeta},
17 },
18 core::ops::Deref,
19 solana_compute_budget::compute_budget_limits::ComputeBudgetLimits,
20 solana_sdk::{
21 feature_set::FeatureSet,
22 hash::Hash,
23 message::{AccountKeys, AddressLoader, TransactionSignatureDetails},
24 pubkey::Pubkey,
25 signature::Signature,
26 simple_vote_transaction_checker::is_simple_vote_transaction,
27 transaction::{Result, SanitizedTransaction, SanitizedVersionedTransaction},
28 },
29 solana_svm_transaction::{
30 instruction::SVMInstruction, message_address_table_lookup::SVMMessageAddressTableLookup,
31 svm_message::SVMMessage, svm_transaction::SVMTransaction,
32 },
33 std::collections::HashSet,
34};
35
36#[derive(Debug)]
37pub struct RuntimeTransaction<T> {
38 transaction: T,
39 meta: TransactionMeta,
42}
43
44impl<T> StaticMeta for RuntimeTransaction<T> {
45 fn message_hash(&self) -> &Hash {
46 &self.meta.message_hash
47 }
48 fn is_simple_vote_transaction(&self) -> bool {
49 self.meta.is_simple_vote_transaction
50 }
51 fn signature_details(&self) -> &TransactionSignatureDetails {
52 &self.meta.signature_details
53 }
54 fn compute_budget_limits(&self, feature_set: &FeatureSet) -> Result<ComputeBudgetLimits> {
55 self.meta
56 .compute_budget_instruction_details
57 .sanitize_and_convert_to_compute_budget_limits(feature_set)
58 }
59}
60
61impl<T: SVMMessage> DynamicMeta for RuntimeTransaction<T> {}
62
63impl<T> Deref for RuntimeTransaction<T> {
64 type Target = T;
65
66 fn deref(&self) -> &Self::Target {
67 &self.transaction
68 }
69}
70
71impl RuntimeTransaction<SanitizedVersionedTransaction> {
72 pub fn try_from(
73 sanitized_versioned_tx: SanitizedVersionedTransaction,
74 message_hash: Option<Hash>,
75 is_simple_vote_tx: Option<bool>,
76 ) -> Result<Self> {
77 let is_simple_vote_tx = is_simple_vote_tx
78 .unwrap_or_else(|| is_simple_vote_transaction(&sanitized_versioned_tx));
79 let message_hash =
80 message_hash.unwrap_or_else(|| sanitized_versioned_tx.get_message().message.hash());
81
82 let precompile_signature_details = get_precompile_signature_details(
83 sanitized_versioned_tx
84 .get_message()
85 .program_instructions_iter()
86 .map(|(program_id, ix)| (program_id, SVMInstruction::from(ix))),
87 );
88 let signature_details = TransactionSignatureDetails::new(
89 u64::from(
90 sanitized_versioned_tx
91 .get_message()
92 .message
93 .header()
94 .num_required_signatures,
95 ),
96 precompile_signature_details.num_secp256k1_instruction_signatures,
97 precompile_signature_details.num_ed25519_instruction_signatures,
98 );
99 let compute_budget_instruction_details = ComputeBudgetInstructionDetails::try_from(
100 sanitized_versioned_tx
101 .get_message()
102 .program_instructions_iter()
103 .map(|(program_id, ix)| (program_id, SVMInstruction::from(ix))),
104 )?;
105
106 Ok(Self {
107 transaction: sanitized_versioned_tx,
108 meta: TransactionMeta {
109 message_hash,
110 is_simple_vote_transaction: is_simple_vote_tx,
111 signature_details,
112 compute_budget_instruction_details,
113 },
114 })
115 }
116}
117
118impl RuntimeTransaction<SanitizedTransaction> {
119 pub fn try_from(
120 statically_loaded_runtime_tx: RuntimeTransaction<SanitizedVersionedTransaction>,
121 address_loader: impl AddressLoader,
122 reserved_account_keys: &HashSet<Pubkey>,
123 ) -> Result<Self> {
124 let hash = *statically_loaded_runtime_tx.message_hash();
125 let is_simple_vote_tx = statically_loaded_runtime_tx.is_simple_vote_transaction();
126 let sanitized_transaction = SanitizedTransaction::try_new(
127 statically_loaded_runtime_tx.transaction,
128 hash,
129 is_simple_vote_tx,
130 address_loader,
131 reserved_account_keys,
132 )?;
133
134 let mut tx = Self {
135 transaction: sanitized_transaction,
136 meta: statically_loaded_runtime_tx.meta,
137 };
138 tx.load_dynamic_metadata()?;
139
140 Ok(tx)
141 }
142
143 fn load_dynamic_metadata(&mut self) -> Result<()> {
144 Ok(())
145 }
146}
147
148impl<T: SVMMessage> SVMMessage for RuntimeTransaction<T> {
149 fn num_total_signatures(&self) -> u64 {
151 self.meta.signature_details.total_signatures()
152 }
153
154 fn num_write_locks(&self) -> u64 {
155 self.transaction.num_write_locks()
156 }
157
158 fn recent_blockhash(&self) -> &Hash {
159 self.transaction.recent_blockhash()
160 }
161
162 fn num_instructions(&self) -> usize {
163 self.transaction.num_instructions()
164 }
165
166 fn instructions_iter(&self) -> impl Iterator<Item = SVMInstruction> {
167 self.transaction.instructions_iter()
168 }
169
170 fn program_instructions_iter(&self) -> impl Iterator<Item = (&Pubkey, SVMInstruction)> + Clone {
171 self.transaction.program_instructions_iter()
172 }
173
174 fn account_keys(&self) -> AccountKeys {
175 self.transaction.account_keys()
176 }
177
178 fn fee_payer(&self) -> &Pubkey {
179 self.transaction.fee_payer()
180 }
181
182 fn is_writable(&self, index: usize) -> bool {
183 self.transaction.is_writable(index)
184 }
185
186 fn is_signer(&self, index: usize) -> bool {
187 self.transaction.is_signer(index)
188 }
189
190 fn is_invoked(&self, key_index: usize) -> bool {
191 self.transaction.is_invoked(key_index)
192 }
193
194 fn num_lookup_tables(&self) -> usize {
195 self.transaction.num_lookup_tables()
196 }
197
198 fn message_address_table_lookups(&self) -> impl Iterator<Item = SVMMessageAddressTableLookup> {
199 self.transaction.message_address_table_lookups()
200 }
201}
202
203impl<T: SVMTransaction> SVMTransaction for RuntimeTransaction<T> {
204 fn signature(&self) -> &Signature {
205 self.transaction.signature()
206 }
207
208 fn signatures(&self) -> &[Signature] {
209 self.transaction.signatures()
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use {
216 super::*,
217 solana_program::{
218 system_instruction,
219 vote::{self, state::Vote},
220 },
221 solana_sdk::{
222 compute_budget::ComputeBudgetInstruction,
223 instruction::Instruction,
224 message::Message,
225 reserved_account_keys::ReservedAccountKeys,
226 signer::{keypair::Keypair, Signer},
227 transaction::{SimpleAddressLoader, Transaction, VersionedTransaction},
228 },
229 };
230
231 fn vote_sanitized_versioned_transaction() -> SanitizedVersionedTransaction {
232 let bank_hash = Hash::new_unique();
233 let block_hash = Hash::new_unique();
234 let vote_keypair = Keypair::new();
235 let node_keypair = Keypair::new();
236 let auth_keypair = Keypair::new();
237 let votes = Vote::new(vec![1, 2, 3], bank_hash);
238 let vote_ix =
239 vote::instruction::vote(&vote_keypair.pubkey(), &auth_keypair.pubkey(), votes);
240 let mut vote_tx = Transaction::new_with_payer(&[vote_ix], Some(&node_keypair.pubkey()));
241 vote_tx.partial_sign(&[&node_keypair], block_hash);
242 vote_tx.partial_sign(&[&auth_keypair], block_hash);
243
244 SanitizedVersionedTransaction::try_from(VersionedTransaction::from(vote_tx)).unwrap()
245 }
246
247 fn non_vote_sanitized_versioned_transaction() -> SanitizedVersionedTransaction {
248 TestTransaction::new().to_sanitized_versioned_transaction()
249 }
250
251 struct TestTransaction {
254 from_keypair: Keypair,
255 hash: Hash,
256 instructions: Vec<Instruction>,
257 }
258
259 impl TestTransaction {
260 fn new() -> Self {
261 let from_keypair = Keypair::new();
262 let instructions = vec![system_instruction::transfer(
263 &from_keypair.pubkey(),
264 &solana_sdk::pubkey::new_rand(),
265 1,
266 )];
267 TestTransaction {
268 from_keypair,
269 hash: Hash::new_unique(),
270 instructions,
271 }
272 }
273
274 fn add_compute_unit_limit(&mut self, val: u32) -> &mut TestTransaction {
275 self.instructions
276 .push(ComputeBudgetInstruction::set_compute_unit_limit(val));
277 self
278 }
279
280 fn add_compute_unit_price(&mut self, val: u64) -> &mut TestTransaction {
281 self.instructions
282 .push(ComputeBudgetInstruction::set_compute_unit_price(val));
283 self
284 }
285
286 fn add_loaded_accounts_bytes(&mut self, val: u32) -> &mut TestTransaction {
287 self.instructions
288 .push(ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(val));
289 self
290 }
291
292 fn to_sanitized_versioned_transaction(&self) -> SanitizedVersionedTransaction {
293 let message = Message::new(&self.instructions, Some(&self.from_keypair.pubkey()));
294 let tx = Transaction::new(&[&self.from_keypair], message, self.hash);
295 SanitizedVersionedTransaction::try_from(VersionedTransaction::from(tx)).unwrap()
296 }
297 }
298
299 #[test]
300 fn test_runtime_transaction_is_vote_meta() {
301 fn get_is_simple_vote(
302 svt: SanitizedVersionedTransaction,
303 is_simple_vote: Option<bool>,
304 ) -> bool {
305 RuntimeTransaction::<SanitizedVersionedTransaction>::try_from(svt, None, is_simple_vote)
306 .unwrap()
307 .meta
308 .is_simple_vote_transaction
309 }
310
311 assert!(!get_is_simple_vote(
312 non_vote_sanitized_versioned_transaction(),
313 None
314 ));
315
316 assert!(get_is_simple_vote(
317 non_vote_sanitized_versioned_transaction(),
318 Some(true), ));
320
321 assert!(get_is_simple_vote(
322 vote_sanitized_versioned_transaction(),
323 None
324 ));
325
326 assert!(!get_is_simple_vote(
327 vote_sanitized_versioned_transaction(),
328 Some(false), ));
330 }
331
332 #[test]
333 fn test_advancing_transaction_type() {
334 let hash = Hash::new_unique();
335
336 let statically_loaded_transaction =
337 RuntimeTransaction::<SanitizedVersionedTransaction>::try_from(
338 non_vote_sanitized_versioned_transaction(),
339 Some(hash),
340 None,
341 )
342 .unwrap();
343
344 assert_eq!(hash, *statically_loaded_transaction.message_hash());
345 assert!(!statically_loaded_transaction.is_simple_vote_transaction());
346
347 let dynamically_loaded_transaction = RuntimeTransaction::<SanitizedTransaction>::try_from(
348 statically_loaded_transaction,
349 SimpleAddressLoader::Disabled,
350 &ReservedAccountKeys::empty_key_set(),
351 );
352 let dynamically_loaded_transaction =
353 dynamically_loaded_transaction.expect("created from statically loaded tx");
354
355 assert_eq!(hash, *dynamically_loaded_transaction.message_hash());
356 assert!(!dynamically_loaded_transaction.is_simple_vote_transaction());
357 }
358
359 #[test]
360 fn test_runtime_transaction_static_meta() {
361 let hash = Hash::new_unique();
362 let compute_unit_limit = 250_000;
363 let compute_unit_price = 1_000;
364 let loaded_accounts_bytes = 1_024;
365 let mut test_transaction = TestTransaction::new();
366
367 let runtime_transaction_static =
368 RuntimeTransaction::<SanitizedVersionedTransaction>::try_from(
369 test_transaction
370 .add_compute_unit_limit(compute_unit_limit)
371 .add_compute_unit_price(compute_unit_price)
372 .add_loaded_accounts_bytes(loaded_accounts_bytes)
373 .to_sanitized_versioned_transaction(),
374 Some(hash),
375 None,
376 )
377 .unwrap();
378
379 assert_eq!(&hash, runtime_transaction_static.message_hash());
380 assert!(!runtime_transaction_static.is_simple_vote_transaction());
381
382 let signature_details = &runtime_transaction_static.meta.signature_details;
383 assert_eq!(1, signature_details.num_transaction_signatures());
384 assert_eq!(0, signature_details.num_secp256k1_instruction_signatures());
385 assert_eq!(0, signature_details.num_ed25519_instruction_signatures());
386
387 let compute_budget_limits = runtime_transaction_static
388 .compute_budget_limits(&FeatureSet::default())
389 .unwrap();
390 assert_eq!(compute_unit_limit, compute_budget_limits.compute_unit_limit);
391 assert_eq!(compute_unit_price, compute_budget_limits.compute_unit_price);
392 assert_eq!(
393 loaded_accounts_bytes,
394 compute_budget_limits.loaded_accounts_bytes.get()
395 );
396 }
397}