solana_runtime_transaction/runtime_transaction/
sdk_transactions.rs

1use {
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    /// Create a new `RuntimeTransaction<SanitizedTransaction>` from an
72    /// unsanitized `VersionedTransaction`.
73    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    /// Create a new `RuntimeTransaction<SanitizedTransaction>` from a
94    /// `RuntimeTransaction<SanitizedVersionedTransaction>` that already has
95    /// static metadata loaded.
96    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    // Simple transfer transaction for testing, it does not support vote instruction
190    // because simple vote transaction will not request limits
191    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), // override
261        ));
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), // override
271        ));
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}