solana_runtime_transaction/
runtime_transaction.rs

1//! RuntimeTransaction is `runtime` facing representation of transaction, while
2//! solana_sdk::SanitizedTransaction is client facing representation.
3//!
4//! It has two states:
5//! 1. Statically Loaded: after receiving `packet` from sigverify and deserializing
6//!    it into `solana_sdk::VersionedTransaction`, then sanitizing into
7//!    `solana_sdk::SanitizedVersionedTransaction`, which can be wrapped into
8//!    `RuntimeTransaction` with static transaction metadata extracted.
9//! 2. Dynamically Loaded: after successfully loaded account addresses from onchain
10//!    ALT, RuntimeTransaction<SanitizedMessage> transits into Dynamically Loaded state,
11//!    with its dynamic metadata loaded.
12use {
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    // transaction meta is a collection of fields, it is updated
40    // during message state transition
41    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    // override to access from the cached meta instead of re-calculating
150    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    // Simple transfer transaction for testing, it does not support vote instruction
252    // because simple vote transaction will not request limits
253    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), // override
319        ));
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), // override
329        ));
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}