agave_transaction_view/
transaction_view.rs

1use {
2    crate::{
3        address_table_lookup_frame::AddressTableLookupIterator,
4        instructions_frame::InstructionsIterator, result::Result, sanitize::sanitize,
5        transaction_data::TransactionData, transaction_frame::TransactionFrame,
6        transaction_version::TransactionVersion,
7    },
8    core::fmt::{Debug, Formatter},
9    solana_hash::Hash,
10    solana_pubkey::Pubkey,
11    solana_signature::Signature,
12    solana_svm_transaction::instruction::SVMInstruction,
13};
14
15// alias for convenience
16pub type UnsanitizedTransactionView<D> = TransactionView<false, D>;
17pub type SanitizedTransactionView<D> = TransactionView<true, D>;
18
19/// A view into a serialized transaction.
20///
21/// This struct provides access to the transaction data without
22/// deserializing it. This is done by parsing and caching metadata
23/// about the layout of the serialized transaction.
24/// The owned `data` is abstracted through the `TransactionData` trait,
25/// so that different containers for the serialized transaction can be used.
26pub struct TransactionView<const SANITIZED: bool, D: TransactionData> {
27    data: D,
28    frame: TransactionFrame,
29}
30
31impl<D: TransactionData> TransactionView<false, D> {
32    /// Creates a new `TransactionView` without running sanitization checks.
33    pub fn try_new_unsanitized(data: D) -> Result<Self> {
34        let frame = TransactionFrame::try_new(data.data())?;
35        Ok(Self { data, frame })
36    }
37
38    /// Sanitizes the transaction view, returning a sanitized view on success.
39    pub fn sanitize(self) -> Result<SanitizedTransactionView<D>> {
40        sanitize(&self)?;
41        Ok(SanitizedTransactionView {
42            data: self.data,
43            frame: self.frame,
44        })
45    }
46}
47
48impl<D: TransactionData> TransactionView<true, D> {
49    /// Creates a new `TransactionView`, running sanitization checks.
50    pub fn try_new_sanitized(data: D) -> Result<Self> {
51        let unsanitized_view = TransactionView::try_new_unsanitized(data)?;
52        unsanitized_view.sanitize()
53    }
54}
55
56impl<const SANITIZED: bool, D: TransactionData> TransactionView<SANITIZED, D> {
57    /// Return the number of signatures in the transaction.
58    #[inline]
59    pub fn num_signatures(&self) -> u8 {
60        self.frame.num_signatures()
61    }
62
63    /// Return the version of the transaction.
64    #[inline]
65    pub fn version(&self) -> TransactionVersion {
66        self.frame.version()
67    }
68
69    /// Return the number of required signatures in the transaction.
70    #[inline]
71    pub fn num_required_signatures(&self) -> u8 {
72        self.frame.num_required_signatures()
73    }
74
75    /// Return the number of readonly signed static accounts in the transaction.
76    #[inline]
77    pub fn num_readonly_signed_static_accounts(&self) -> u8 {
78        self.frame.num_readonly_signed_static_accounts()
79    }
80
81    /// Return the number of readonly unsigned static accounts in the transaction.
82    #[inline]
83    pub fn num_readonly_unsigned_static_accounts(&self) -> u8 {
84        self.frame.num_readonly_unsigned_static_accounts()
85    }
86
87    /// Return the number of static account keys in the transaction.
88    #[inline]
89    pub fn num_static_account_keys(&self) -> u8 {
90        self.frame.num_static_account_keys()
91    }
92
93    /// Return the number of instructions in the transaction.
94    #[inline]
95    pub fn num_instructions(&self) -> u16 {
96        self.frame.num_instructions()
97    }
98
99    /// Return the number of address table lookups in the transaction.
100    #[inline]
101    pub fn num_address_table_lookups(&self) -> u8 {
102        self.frame.num_address_table_lookups()
103    }
104
105    /// Return the number of writable lookup accounts in the transaction.
106    #[inline]
107    pub fn total_writable_lookup_accounts(&self) -> u16 {
108        self.frame.total_writable_lookup_accounts()
109    }
110
111    /// Return the number of readonly lookup accounts in the transaction.
112    #[inline]
113    pub fn total_readonly_lookup_accounts(&self) -> u16 {
114        self.frame.total_readonly_lookup_accounts()
115    }
116
117    /// Return the slice of signatures in the transaction.
118    #[inline]
119    pub fn signatures(&self) -> &[Signature] {
120        let data = self.data();
121        // SAFETY: `frame` was created from `data`.
122        unsafe { self.frame.signatures(data) }
123    }
124
125    /// Return the slice of static account keys in the transaction.
126    #[inline]
127    pub fn static_account_keys(&self) -> &[Pubkey] {
128        let data = self.data();
129        // SAFETY: `frame` was created from `data`.
130        unsafe { self.frame.static_account_keys(data) }
131    }
132
133    /// Return the recent blockhash in the transaction.
134    #[inline]
135    pub fn recent_blockhash(&self) -> &Hash {
136        let data = self.data();
137        // SAFETY: `frame` was created from `data`.
138        unsafe { self.frame.recent_blockhash(data) }
139    }
140
141    /// Return an iterator over the instructions in the transaction.
142    #[inline]
143    pub fn instructions_iter(&self) -> InstructionsIterator {
144        let data = self.data();
145        // SAFETY: `frame` was created from `data`.
146        unsafe { self.frame.instructions_iter(data) }
147    }
148
149    /// Return an iterator over the address table lookups in the transaction.
150    #[inline]
151    pub fn address_table_lookup_iter(&self) -> AddressTableLookupIterator {
152        let data = self.data();
153        // SAFETY: `frame` was created from `data`.
154        unsafe { self.frame.address_table_lookup_iter(data) }
155    }
156
157    /// Return the full serialized transaction data.
158    #[inline]
159    pub fn data(&self) -> &[u8] {
160        self.data.data()
161    }
162
163    /// Return the serialized **message** data.
164    /// This does not include the signatures.
165    #[inline]
166    pub fn message_data(&self) -> &[u8] {
167        &self.data()[usize::from(self.frame.message_offset())..]
168    }
169}
170
171// Implementation that relies on sanitization checks having been run.
172impl<D: TransactionData> TransactionView<true, D> {
173    /// Return an iterator over the instructions paired with their program ids.
174    pub fn program_instructions_iter(
175        &self,
176    ) -> impl Iterator<Item = (&Pubkey, SVMInstruction)> + Clone {
177        self.instructions_iter().map(|ix| {
178            let program_id_index = usize::from(ix.program_id_index);
179            let program_id = &self.static_account_keys()[program_id_index];
180            (program_id, ix)
181        })
182    }
183
184    /// Return the number of unsigned static account keys.
185    #[inline]
186    pub(crate) fn num_static_unsigned_static_accounts(&self) -> u8 {
187        self.num_static_account_keys()
188            .wrapping_sub(self.num_required_signatures())
189    }
190
191    /// Return the number of writable unsigned static accounts.
192    #[inline]
193    pub(crate) fn num_writable_unsigned_static_accounts(&self) -> u8 {
194        self.num_static_unsigned_static_accounts()
195            .wrapping_sub(self.num_readonly_unsigned_static_accounts())
196    }
197
198    /// Return the number of writable unsigned static accounts.
199    #[inline]
200    pub(crate) fn num_writable_signed_static_accounts(&self) -> u8 {
201        self.num_required_signatures()
202            .wrapping_sub(self.num_readonly_signed_static_accounts())
203    }
204
205    /// Return the total number of accounts in the transactions.
206    #[inline]
207    pub fn total_num_accounts(&self) -> u16 {
208        u16::from(self.num_static_account_keys())
209            .wrapping_add(self.total_writable_lookup_accounts())
210            .wrapping_add(self.total_readonly_lookup_accounts())
211    }
212
213    /// Return the number of requested writable keys.
214    #[inline]
215    pub fn num_requested_write_locks(&self) -> u64 {
216        u64::from(
217            u16::from(
218                (self.num_static_account_keys())
219                    .wrapping_sub(self.num_readonly_signed_static_accounts())
220                    .wrapping_sub(self.num_readonly_unsigned_static_accounts()),
221            )
222            .wrapping_add(self.total_writable_lookup_accounts()),
223        )
224    }
225}
226
227// Manual implementation of `Debug` - avoids bound on `D`.
228// Prints nicely formatted struct-ish fields even for the iterator fields.
229impl<const SANITIZED: bool, D: TransactionData> Debug for TransactionView<SANITIZED, D> {
230    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
231        f.debug_struct("TransactionView")
232            .field("frame", &self.frame)
233            .field("signatures", &self.signatures())
234            .field("static_account_keys", &self.static_account_keys())
235            .field("recent_blockhash", &self.recent_blockhash())
236            .field("instructions", &self.instructions_iter())
237            .field("address_table_lookups", &self.address_table_lookup_iter())
238            .finish()
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use {
245        super::*,
246        solana_message::{Message, VersionedMessage},
247        solana_pubkey::Pubkey,
248        solana_signature::Signature,
249        solana_system_interface::instruction as system_instruction,
250        solana_transaction::versioned::VersionedTransaction,
251    };
252
253    fn verify_transaction_view_frame(tx: &VersionedTransaction) {
254        let bytes = bincode::serialize(tx).unwrap();
255        let view = TransactionView::try_new_unsanitized(bytes.as_ref()).unwrap();
256
257        assert_eq!(view.num_signatures(), tx.signatures.len() as u8);
258
259        assert_eq!(
260            view.num_required_signatures(),
261            tx.message.header().num_required_signatures
262        );
263        assert_eq!(
264            view.num_readonly_signed_static_accounts(),
265            tx.message.header().num_readonly_signed_accounts
266        );
267        assert_eq!(
268            view.num_readonly_unsigned_static_accounts(),
269            tx.message.header().num_readonly_unsigned_accounts
270        );
271
272        assert_eq!(
273            view.num_static_account_keys(),
274            tx.message.static_account_keys().len() as u8
275        );
276        assert_eq!(
277            view.num_instructions(),
278            tx.message.instructions().len() as u16
279        );
280        assert_eq!(
281            view.num_address_table_lookups(),
282            tx.message
283                .address_table_lookups()
284                .map(|x| x.len() as u8)
285                .unwrap_or(0)
286        );
287    }
288
289    fn multiple_transfers() -> VersionedTransaction {
290        let payer = Pubkey::new_unique();
291        VersionedTransaction {
292            signatures: vec![Signature::default()], // 1 signature to be valid.
293            message: VersionedMessage::Legacy(Message::new(
294                &[
295                    system_instruction::transfer(&payer, &Pubkey::new_unique(), 1),
296                    system_instruction::transfer(&payer, &Pubkey::new_unique(), 1),
297                ],
298                Some(&payer),
299            )),
300        }
301    }
302
303    #[test]
304    fn test_multiple_transfers() {
305        verify_transaction_view_frame(&multiple_transfers());
306    }
307}