solana_runtime_transaction/runtime_transaction/
transaction_view.rs

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