solana_runtime_transaction/runtime_transaction/
transaction_view.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    agave_transaction_view::{
9        resolved_transaction_view::ResolvedTransactionView, transaction_data::TransactionData,
10        transaction_version::TransactionVersion, transaction_view::SanitizedTransactionView,
11    },
12    solana_message::{
13        compiled_instruction::CompiledInstruction,
14        v0::{LoadedAddresses, LoadedMessage, MessageAddressTableLookup},
15        LegacyMessage, MessageHeader, SanitizedMessage, TransactionSignatureDetails,
16        VersionedMessage,
17    },
18    solana_pubkey::Pubkey,
19    solana_svm_transaction::svm_message::SVMMessage,
20    solana_transaction::{
21        sanitized::{MessageHash, SanitizedTransaction},
22        simple_vote_transaction_checker::is_simple_vote_transaction_impl,
23        versioned::VersionedTransaction,
24    },
25    solana_transaction_error::{TransactionError, TransactionResult as Result},
26    std::{borrow::Cow, collections::HashSet},
27};
28
29fn is_simple_vote_transaction<D: TransactionData>(
30    transaction: &SanitizedTransactionView<D>,
31) -> bool {
32    let signatures = transaction.signatures();
33    let is_legacy_message = matches!(transaction.version(), TransactionVersion::Legacy);
34    let instruction_programs = transaction
35        .program_instructions_iter()
36        .map(|(program_id, _ix)| program_id);
37
38    is_simple_vote_transaction_impl(signatures, is_legacy_message, instruction_programs)
39}
40
41impl<D: TransactionData> RuntimeTransaction<SanitizedTransactionView<D>> {
42    pub fn try_from(
43        transaction: SanitizedTransactionView<D>,
44        message_hash: MessageHash,
45        is_simple_vote_tx: Option<bool>,
46    ) -> Result<Self> {
47        let message_hash = match message_hash {
48            MessageHash::Precomputed(hash) => hash,
49            MessageHash::Compute => VersionedMessage::hash_raw_message(transaction.message_data()),
50        };
51        let is_simple_vote_tx =
52            is_simple_vote_tx.unwrap_or_else(|| is_simple_vote_transaction(&transaction));
53
54        let precompile_signature_details =
55            get_precompile_signature_details(transaction.program_instructions_iter());
56        let signature_details = TransactionSignatureDetails::new(
57            u64::from(transaction.num_required_signatures()),
58            precompile_signature_details.num_secp256k1_instruction_signatures,
59            precompile_signature_details.num_ed25519_instruction_signatures,
60            precompile_signature_details.num_secp256r1_instruction_signatures,
61        );
62        let compute_budget_instruction_details =
63            ComputeBudgetInstructionDetails::try_from(transaction.program_instructions_iter())?;
64
65        Ok(Self {
66            transaction,
67            meta: TransactionMeta {
68                message_hash,
69                is_simple_vote_transaction: is_simple_vote_tx,
70                signature_details,
71                compute_budget_instruction_details,
72            },
73        })
74    }
75}
76
77impl<D: TransactionData> RuntimeTransaction<ResolvedTransactionView<D>> {
78    /// Create a new `RuntimeTransaction<ResolvedTransactionView>` from a
79    /// `RuntimeTransaction<SanitizedTransactionView>` that already has
80    /// static metadata loaded.
81    pub fn try_from(
82        statically_loaded_runtime_tx: RuntimeTransaction<SanitizedTransactionView<D>>,
83        loaded_addresses: Option<LoadedAddresses>,
84        reserved_account_keys: &HashSet<Pubkey>,
85    ) -> Result<Self> {
86        let RuntimeTransaction { transaction, meta } = statically_loaded_runtime_tx;
87        // transaction-view does not distinguish between different types of errors here.
88        // return generic sanitize failure error here.
89        // these transactions should be immediately dropped, and we generally
90        // will not care about the specific error at this point.
91        let transaction =
92            ResolvedTransactionView::try_new(transaction, loaded_addresses, reserved_account_keys)
93                .map_err(|_| TransactionError::SanitizeFailure)?;
94        let mut tx = Self { transaction, meta };
95        tx.load_dynamic_metadata()?;
96
97        Ok(tx)
98    }
99
100    fn load_dynamic_metadata(&mut self) -> Result<()> {
101        Ok(())
102    }
103}
104
105impl<D: TransactionData> TransactionWithMeta for RuntimeTransaction<ResolvedTransactionView<D>> {
106    fn as_sanitized_transaction(&self) -> Cow<SanitizedTransaction> {
107        let VersionedTransaction {
108            signatures,
109            message,
110        } = self.to_versioned_transaction();
111
112        let is_writable_account_cache = (0..self.transaction.total_num_accounts())
113            .map(|index| self.is_writable(usize::from(index)))
114            .collect();
115
116        let message = match message {
117            VersionedMessage::Legacy(message) => SanitizedMessage::Legacy(LegacyMessage {
118                message: Cow::Owned(message),
119                is_writable_account_cache,
120            }),
121            VersionedMessage::V0(message) => SanitizedMessage::V0(LoadedMessage {
122                message: Cow::Owned(message),
123                loaded_addresses: Cow::Owned(self.loaded_addresses().unwrap().clone()),
124                is_writable_account_cache,
125            }),
126        };
127
128        // SAFETY:
129        // - Simple conversion between different formats
130        // - `ResolvedTransactionView` has undergone sanitization checks
131        Cow::Owned(
132            SanitizedTransaction::try_new_from_fields(
133                message,
134                *self.message_hash(),
135                self.is_simple_vote_transaction(),
136                signatures,
137            )
138            .expect("transaction view is sanitized"),
139        )
140    }
141
142    fn to_versioned_transaction(&self) -> VersionedTransaction {
143        let header = MessageHeader {
144            num_required_signatures: self.num_required_signatures(),
145            num_readonly_signed_accounts: self.num_readonly_signed_static_accounts(),
146            num_readonly_unsigned_accounts: self.num_readonly_unsigned_static_accounts(),
147        };
148        let static_account_keys = self.static_account_keys().to_vec();
149        let recent_blockhash = *self.recent_blockhash();
150        let instructions = self
151            .instructions_iter()
152            .map(|ix| CompiledInstruction {
153                program_id_index: ix.program_id_index,
154                accounts: ix.accounts.to_vec(),
155                data: ix.data.to_vec(),
156            })
157            .collect();
158
159        let message = match self.version() {
160            TransactionVersion::Legacy => {
161                VersionedMessage::Legacy(solana_message::legacy::Message {
162                    header,
163                    account_keys: static_account_keys,
164                    recent_blockhash,
165                    instructions,
166                })
167            }
168            TransactionVersion::V0 => VersionedMessage::V0(solana_message::v0::Message {
169                header,
170                account_keys: static_account_keys,
171                recent_blockhash,
172                instructions,
173                address_table_lookups: self
174                    .address_table_lookup_iter()
175                    .map(|atl| MessageAddressTableLookup {
176                        account_key: *atl.account_key,
177                        writable_indexes: atl.writable_indexes.to_vec(),
178                        readonly_indexes: atl.readonly_indexes.to_vec(),
179                    })
180                    .collect(),
181            }),
182        };
183
184        VersionedTransaction {
185            signatures: self.signatures().to_vec(),
186            message,
187        }
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use {
194        super::*,
195        solana_hash::Hash,
196        solana_keypair::Keypair,
197        solana_message::{v0, AddressLookupTableAccount, SimpleAddressLoader},
198        solana_reserved_account_keys::ReservedAccountKeys,
199        solana_signature::Signature,
200        solana_system_interface::instruction as system_instruction,
201        solana_system_transaction as system_transaction,
202    };
203
204    #[test]
205    fn test_advancing_transaction_type() {
206        // Create serialized simple transfer.
207        let serialized_transaction = {
208            let transaction = VersionedTransaction::from(system_transaction::transfer(
209                &Keypair::new(),
210                &Pubkey::new_unique(),
211                1,
212                Hash::new_unique(),
213            ));
214            bincode::serialize(&transaction).unwrap()
215        };
216
217        let hash = Hash::new_unique();
218        let transaction =
219            SanitizedTransactionView::try_new_sanitized(&serialized_transaction[..]).unwrap();
220        let static_runtime_transaction =
221            RuntimeTransaction::<SanitizedTransactionView<_>>::try_from(
222                transaction,
223                MessageHash::Precomputed(hash),
224                None,
225            )
226            .unwrap();
227
228        assert_eq!(hash, *static_runtime_transaction.message_hash());
229        assert!(!static_runtime_transaction.is_simple_vote_transaction());
230
231        let dynamic_runtime_transaction =
232            RuntimeTransaction::<ResolvedTransactionView<_>>::try_from(
233                static_runtime_transaction,
234                None,
235                &ReservedAccountKeys::empty_key_set(),
236            )
237            .unwrap();
238
239        assert_eq!(hash, *dynamic_runtime_transaction.message_hash());
240        assert!(!dynamic_runtime_transaction.is_simple_vote_transaction());
241    }
242
243    #[test]
244    fn test_to_versioned_transaction() {
245        fn assert_translation(
246            original_transaction: VersionedTransaction,
247            loaded_addresses: Option<LoadedAddresses>,
248            reserved_account_keys: &HashSet<Pubkey>,
249        ) {
250            let bytes = bincode::serialize(&original_transaction).unwrap();
251            let transaction_view = SanitizedTransactionView::try_new_sanitized(&bytes[..]).unwrap();
252            let runtime_transaction = RuntimeTransaction::<SanitizedTransactionView<_>>::try_from(
253                transaction_view,
254                MessageHash::Compute,
255                None,
256            )
257            .unwrap();
258            let runtime_transaction = RuntimeTransaction::<ResolvedTransactionView<_>>::try_from(
259                runtime_transaction,
260                loaded_addresses,
261                reserved_account_keys,
262            )
263            .unwrap();
264
265            let versioned_transaction = runtime_transaction.to_versioned_transaction();
266            assert_eq!(original_transaction, versioned_transaction);
267        }
268
269        let reserved_key_set = ReservedAccountKeys::empty_key_set();
270
271        // Simple transfer.
272        let original_transaction = VersionedTransaction::from(system_transaction::transfer(
273            &Keypair::new(),
274            &Pubkey::new_unique(),
275            1,
276            Hash::new_unique(),
277        ));
278        assert_translation(original_transaction, None, &reserved_key_set);
279
280        // Simple transfer with loaded addresses.
281        let payer = Pubkey::new_unique();
282        let to = Pubkey::new_unique();
283        let original_transaction = VersionedTransaction {
284            signatures: vec![Signature::default()], // 1 signature to be valid.
285            message: VersionedMessage::V0(
286                v0::Message::try_compile(
287                    &payer,
288                    &[system_instruction::transfer(&payer, &to, 1)],
289                    &[AddressLookupTableAccount {
290                        key: Pubkey::new_unique(),
291                        addresses: vec![to],
292                    }],
293                    Hash::default(),
294                )
295                .unwrap(),
296            ),
297        };
298        assert_translation(
299            original_transaction,
300            Some(LoadedAddresses {
301                writable: vec![to],
302                readonly: vec![],
303            }),
304            &reserved_key_set,
305        );
306    }
307
308    #[test]
309    fn test_as_sanitized_transaction() {
310        fn assert_translation(
311            original_transaction: SanitizedTransaction,
312            loaded_addresses: Option<LoadedAddresses>,
313            reserved_account_keys: &HashSet<Pubkey>,
314        ) {
315            let bytes =
316                bincode::serialize(&original_transaction.to_versioned_transaction()).unwrap();
317            let transaction_view = SanitizedTransactionView::try_new_sanitized(&bytes[..]).unwrap();
318            let runtime_transaction = RuntimeTransaction::<SanitizedTransactionView<_>>::try_from(
319                transaction_view,
320                MessageHash::Compute,
321                None,
322            )
323            .unwrap();
324            let runtime_transaction = RuntimeTransaction::<ResolvedTransactionView<_>>::try_from(
325                runtime_transaction,
326                loaded_addresses,
327                reserved_account_keys,
328            )
329            .unwrap();
330
331            let sanitized_transaction = runtime_transaction.as_sanitized_transaction();
332            assert_eq!(
333                sanitized_transaction.message_hash(),
334                original_transaction.message_hash()
335            );
336        }
337
338        let reserved_key_set = ReservedAccountKeys::empty_key_set();
339
340        // Simple transfer.
341        let original_transaction = VersionedTransaction::from(system_transaction::transfer(
342            &Keypair::new(),
343            &Pubkey::new_unique(),
344            1,
345            Hash::new_unique(),
346        ));
347        let sanitized_transaction = SanitizedTransaction::try_create(
348            original_transaction,
349            MessageHash::Compute,
350            None,
351            SimpleAddressLoader::Disabled,
352            &reserved_key_set,
353        )
354        .unwrap();
355        assert_translation(sanitized_transaction, None, &reserved_key_set);
356
357        // Simple transfer with loaded addresses.
358        let payer = Pubkey::new_unique();
359        let to = Pubkey::new_unique();
360        let original_transaction = VersionedTransaction {
361            signatures: vec![Signature::default()], // 1 signature to be valid.
362            message: VersionedMessage::V0(
363                v0::Message::try_compile(
364                    &payer,
365                    &[system_instruction::transfer(&payer, &to, 1)],
366                    &[AddressLookupTableAccount {
367                        key: Pubkey::new_unique(),
368                        addresses: vec![to],
369                    }],
370                    Hash::default(),
371                )
372                .unwrap(),
373            ),
374        };
375        let loaded_addresses = LoadedAddresses {
376            writable: vec![to],
377            readonly: vec![],
378        };
379        let sanitized_transaction = SanitizedTransaction::try_create(
380            original_transaction,
381            MessageHash::Compute,
382            None,
383            SimpleAddressLoader::Enabled(loaded_addresses.clone()),
384            &reserved_key_set,
385        )
386        .unwrap();
387        assert_translation(
388            sanitized_transaction,
389            Some(loaded_addresses),
390            &reserved_key_set,
391        );
392    }
393}