solana_runtime_transaction/runtime_transaction/
sdk_transactions.rs

1use {
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    /// Create a new `RuntimeTransaction<SanitizedTransaction>` from an
76    /// unsanitized `VersionedTransaction`.
77    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    /// Create a new `RuntimeTransaction<SanitizedTransaction>` from a
98    /// `RuntimeTransaction<SanitizedVersionedTransaction>` that already has
99    /// static metadata loaded.
100    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    // Simple transfer transaction for testing, it does not support vote instruction
194    // because simple vote transaction will not request limits
195    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), // override
265        ));
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), // override
275        ));
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}