solana_runtime_transaction/
instructions_processor.rs

1use {
2    crate::compute_budget_instruction_details::*,
3    solana_compute_budget::compute_budget_limits::*,
4    solana_sdk::{feature_set::FeatureSet, pubkey::Pubkey, transaction::TransactionError},
5    solana_svm_transaction::instruction::SVMInstruction,
6};
7
8/// Processing compute_budget could be part of tx sanitizing, failed to process
9/// these instructions will drop the transaction eventually without execution,
10/// may as well fail it early.
11/// If succeeded, the transaction's specific limits/requests (could be default)
12/// are retrieved and returned,
13pub fn process_compute_budget_instructions<'a>(
14    instructions: impl Iterator<Item = (&'a Pubkey, SVMInstruction<'a>)> + Clone,
15    feature_set: &FeatureSet,
16) -> Result<ComputeBudgetLimits, TransactionError> {
17    ComputeBudgetInstructionDetails::try_from(instructions)?
18        .sanitize_and_convert_to_compute_budget_limits(feature_set)
19}
20
21#[cfg(test)]
22mod tests {
23    use {
24        super::*,
25        solana_sdk::{
26            compute_budget::ComputeBudgetInstruction,
27            hash::Hash,
28            instruction::{Instruction, InstructionError},
29            message::Message,
30            pubkey::Pubkey,
31            signature::Keypair,
32            signer::Signer,
33            system_instruction::{self},
34            transaction::{SanitizedTransaction, Transaction, TransactionError},
35        },
36        solana_svm_transaction::svm_message::SVMMessage,
37        std::num::NonZeroU32,
38    };
39
40    macro_rules! test {
41        ( $instructions: expr, $expected_result: expr) => {
42            for feature_set in [FeatureSet::default(), FeatureSet::all_enabled()] {
43                test!($instructions, $expected_result, &feature_set);
44            }
45        };
46        ( $instructions: expr, $expected_result: expr, $feature_set: expr) => {
47            let payer_keypair = Keypair::new();
48            let tx = SanitizedTransaction::from_transaction_for_tests(Transaction::new(
49                &[&payer_keypair],
50                Message::new($instructions, Some(&payer_keypair.pubkey())),
51                Hash::default(),
52            ));
53
54            let result = process_compute_budget_instructions(
55                SVMMessage::program_instructions_iter(&tx),
56                $feature_set,
57            );
58            assert_eq!($expected_result, result);
59        };
60    }
61
62    #[test]
63    fn test_process_instructions() {
64        // Units
65        test!(
66            &[],
67            Ok(ComputeBudgetLimits {
68                compute_unit_limit: 0,
69                ..ComputeBudgetLimits::default()
70            })
71        );
72        test!(
73            &[
74                ComputeBudgetInstruction::set_compute_unit_limit(1),
75                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
76            ],
77            Ok(ComputeBudgetLimits {
78                compute_unit_limit: 1,
79                ..ComputeBudgetLimits::default()
80            })
81        );
82        test!(
83            &[
84                ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT + 1),
85                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
86            ],
87            Ok(ComputeBudgetLimits {
88                compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT,
89                ..ComputeBudgetLimits::default()
90            })
91        );
92        test!(
93            &[
94                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
95                ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT),
96            ],
97            Ok(ComputeBudgetLimits {
98                compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT,
99                ..ComputeBudgetLimits::default()
100            })
101        );
102        test!(
103            &[
104                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
105                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
106                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
107                ComputeBudgetInstruction::set_compute_unit_limit(1),
108            ],
109            Ok(ComputeBudgetLimits {
110                compute_unit_limit: 1,
111                ..ComputeBudgetLimits::default()
112            })
113        );
114        test!(
115            &[
116                ComputeBudgetInstruction::set_compute_unit_limit(1),
117                ComputeBudgetInstruction::set_compute_unit_price(42)
118            ],
119            Ok(ComputeBudgetLimits {
120                compute_unit_limit: 1,
121                compute_unit_price: 42,
122                ..ComputeBudgetLimits::default()
123            })
124        );
125
126        // HeapFrame
127        test!(
128            &[],
129            Ok(ComputeBudgetLimits {
130                compute_unit_limit: 0,
131                ..ComputeBudgetLimits::default()
132            })
133        );
134        test!(
135            &[
136                ComputeBudgetInstruction::request_heap_frame(40 * 1024),
137                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
138            ],
139            Ok(ComputeBudgetLimits {
140                compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
141                updated_heap_bytes: 40 * 1024,
142                ..ComputeBudgetLimits::default()
143            }),
144            &FeatureSet::default()
145        );
146        test!(
147            &[
148                ComputeBudgetInstruction::request_heap_frame(40 * 1024),
149                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
150            ],
151            Ok(ComputeBudgetLimits {
152                compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
153                    + MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT,
154                updated_heap_bytes: 40 * 1024,
155                ..ComputeBudgetLimits::default()
156            }),
157            &FeatureSet::all_enabled()
158        );
159        test!(
160            &[
161                ComputeBudgetInstruction::request_heap_frame(40 * 1024 + 1),
162                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
163            ],
164            Err(TransactionError::InstructionError(
165                0,
166                InstructionError::InvalidInstructionData,
167            ))
168        );
169        test!(
170            &[
171                ComputeBudgetInstruction::request_heap_frame(31 * 1024),
172                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
173            ],
174            Err(TransactionError::InstructionError(
175                0,
176                InstructionError::InvalidInstructionData,
177            ))
178        );
179        test!(
180            &[
181                ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES + 1),
182                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
183            ],
184            Err(TransactionError::InstructionError(
185                0,
186                InstructionError::InvalidInstructionData,
187            ))
188        );
189        test!(
190            &[
191                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
192                ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
193            ],
194            Ok(ComputeBudgetLimits {
195                compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
196                updated_heap_bytes: MAX_HEAP_FRAME_BYTES,
197                ..ComputeBudgetLimits::default()
198            }),
199            &FeatureSet::default()
200        );
201        test!(
202            &[
203                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
204                ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
205            ],
206            Ok(ComputeBudgetLimits {
207                compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
208                    + MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT,
209                updated_heap_bytes: MAX_HEAP_FRAME_BYTES,
210                ..ComputeBudgetLimits::default()
211            }),
212            &FeatureSet::all_enabled()
213        );
214        test!(
215            &[
216                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
217                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
218                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
219                ComputeBudgetInstruction::request_heap_frame(1),
220            ],
221            Err(TransactionError::InstructionError(
222                3,
223                InstructionError::InvalidInstructionData,
224            ))
225        );
226        test!(
227            &[
228                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
229                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
230                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
231                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
232                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
233                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
234                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
235                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
236            ],
237            Ok(ComputeBudgetLimits {
238                compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT * 7,
239                ..ComputeBudgetLimits::default()
240            })
241        );
242
243        // Combined
244        test!(
245            &[
246                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
247                ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
248                ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT),
249                ComputeBudgetInstruction::set_compute_unit_price(u64::MAX),
250            ],
251            Ok(ComputeBudgetLimits {
252                compute_unit_price: u64::MAX,
253                compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT,
254                updated_heap_bytes: MAX_HEAP_FRAME_BYTES,
255                ..ComputeBudgetLimits::default()
256            })
257        );
258        test!(
259            &[
260                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
261                ComputeBudgetInstruction::set_compute_unit_limit(1),
262                ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
263                ComputeBudgetInstruction::set_compute_unit_price(u64::MAX),
264            ],
265            Ok(ComputeBudgetLimits {
266                compute_unit_price: u64::MAX,
267                compute_unit_limit: 1,
268                updated_heap_bytes: MAX_HEAP_FRAME_BYTES,
269                ..ComputeBudgetLimits::default()
270            })
271        );
272
273        // Duplicates
274        test!(
275            &[
276                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
277                ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT),
278                ComputeBudgetInstruction::set_compute_unit_limit(MAX_COMPUTE_UNIT_LIMIT - 1),
279            ],
280            Err(TransactionError::DuplicateInstruction(2))
281        );
282
283        test!(
284            &[
285                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
286                ComputeBudgetInstruction::request_heap_frame(MIN_HEAP_FRAME_BYTES),
287                ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
288            ],
289            Err(TransactionError::DuplicateInstruction(2))
290        );
291        test!(
292            &[
293                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
294                ComputeBudgetInstruction::set_compute_unit_price(0),
295                ComputeBudgetInstruction::set_compute_unit_price(u64::MAX),
296            ],
297            Err(TransactionError::DuplicateInstruction(2))
298        );
299    }
300
301    #[test]
302    fn test_process_loaded_accounts_data_size_limit_instruction() {
303        test!(
304            &[],
305            Ok(ComputeBudgetLimits {
306                compute_unit_limit: 0,
307                ..ComputeBudgetLimits::default()
308            })
309        );
310
311        // Assert when set_loaded_accounts_data_size_limit presents,
312        // budget is set with data_size
313        let data_size = 1;
314        let expected_result = Ok(ComputeBudgetLimits {
315            compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
316            loaded_accounts_bytes: NonZeroU32::new(data_size).unwrap(),
317            ..ComputeBudgetLimits::default()
318        });
319        test!(
320            &[
321                ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size),
322                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
323            ],
324            expected_result,
325            &FeatureSet::default()
326        );
327
328        let expected_result = Ok(ComputeBudgetLimits {
329            compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
330                + MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT,
331            loaded_accounts_bytes: NonZeroU32::new(data_size).unwrap(),
332            ..ComputeBudgetLimits::default()
333        });
334        test!(
335            &[
336                ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size),
337                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
338            ],
339            expected_result,
340            &FeatureSet::all_enabled()
341        );
342
343        // Assert when set_loaded_accounts_data_size_limit presents, with greater than max value
344        // budget is set to max data size
345        let data_size = MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES.get() + 1;
346        let expected_result = Ok(ComputeBudgetLimits {
347            compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
348            loaded_accounts_bytes: MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES,
349            ..ComputeBudgetLimits::default()
350        });
351        test!(
352            &[
353                ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size),
354                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
355            ],
356            expected_result,
357            &FeatureSet::default()
358        );
359
360        let expected_result = Ok(ComputeBudgetLimits {
361            compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
362                + MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT,
363            loaded_accounts_bytes: MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES,
364            ..ComputeBudgetLimits::default()
365        });
366        test!(
367            &[
368                ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size),
369                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
370            ],
371            expected_result,
372            &FeatureSet::all_enabled()
373        );
374
375        // Assert when set_loaded_accounts_data_size_limit is not presented
376        // budget is set to default data size
377        let expected_result = Ok(ComputeBudgetLimits {
378            compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
379            loaded_accounts_bytes: MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES,
380            ..ComputeBudgetLimits::default()
381        });
382
383        test!(
384            &[Instruction::new_with_bincode(
385                Pubkey::new_unique(),
386                &0_u8,
387                vec![]
388            ),],
389            expected_result
390        );
391
392        // Assert when set_loaded_accounts_data_size_limit presents more than once,
393        // return DuplicateInstruction
394        let data_size = MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES.get();
395        let expected_result = Err(TransactionError::DuplicateInstruction(2));
396
397        test!(
398            &[
399                Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
400                ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size),
401                ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size),
402            ],
403            expected_result
404        );
405    }
406
407    #[test]
408    fn test_process_mixed_instructions_without_compute_budget() {
409        let payer_keypair = Keypair::new();
410
411        let transaction =
412            SanitizedTransaction::from_transaction_for_tests(Transaction::new_signed_with_payer(
413                &[
414                    Instruction::new_with_bincode(Pubkey::new_unique(), &0_u8, vec![]),
415                    system_instruction::transfer(&payer_keypair.pubkey(), &Pubkey::new_unique(), 2),
416                ],
417                Some(&payer_keypair.pubkey()),
418                &[&payer_keypair],
419                Hash::default(),
420            ));
421
422        for (feature_set, expected_result) in [
423            (
424                FeatureSet::default(),
425                Ok(ComputeBudgetLimits {
426                    compute_unit_limit: 2 * DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT,
427                    ..ComputeBudgetLimits::default()
428                }),
429            ),
430            (
431                FeatureSet::all_enabled(),
432                Ok(ComputeBudgetLimits {
433                    compute_unit_limit: DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT
434                        + MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT,
435                    ..ComputeBudgetLimits::default()
436                }),
437            ),
438        ] {
439            let result = process_compute_budget_instructions(
440                SVMMessage::program_instructions_iter(&transaction),
441                &feature_set,
442            );
443
444            // assert process_instructions will be successful with default,
445            // and the default compute_unit_limit is 2 times default: one for bpf ix, one for
446            // builtin ix.
447            assert_eq!(result, expected_result);
448        }
449    }
450}