agave_transaction_view/
transaction_view.rs1use {
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
15pub type UnsanitizedTransactionView<D> = TransactionView<false, D>;
17pub type SanitizedTransactionView<D> = TransactionView<true, D>;
18
19pub struct TransactionView<const SANITIZED: bool, D: TransactionData> {
27 data: D,
28 frame: TransactionFrame,
29}
30
31impl<D: TransactionData> TransactionView<false, D> {
32 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 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 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 #[inline]
59 pub fn num_signatures(&self) -> u8 {
60 self.frame.num_signatures()
61 }
62
63 #[inline]
65 pub fn version(&self) -> TransactionVersion {
66 self.frame.version()
67 }
68
69 #[inline]
71 pub fn num_required_signatures(&self) -> u8 {
72 self.frame.num_required_signatures()
73 }
74
75 #[inline]
77 pub fn num_readonly_signed_static_accounts(&self) -> u8 {
78 self.frame.num_readonly_signed_static_accounts()
79 }
80
81 #[inline]
83 pub fn num_readonly_unsigned_static_accounts(&self) -> u8 {
84 self.frame.num_readonly_unsigned_static_accounts()
85 }
86
87 #[inline]
89 pub fn num_static_account_keys(&self) -> u8 {
90 self.frame.num_static_account_keys()
91 }
92
93 #[inline]
95 pub fn num_instructions(&self) -> u16 {
96 self.frame.num_instructions()
97 }
98
99 #[inline]
101 pub fn num_address_table_lookups(&self) -> u8 {
102 self.frame.num_address_table_lookups()
103 }
104
105 #[inline]
107 pub fn total_writable_lookup_accounts(&self) -> u16 {
108 self.frame.total_writable_lookup_accounts()
109 }
110
111 #[inline]
113 pub fn total_readonly_lookup_accounts(&self) -> u16 {
114 self.frame.total_readonly_lookup_accounts()
115 }
116
117 #[inline]
119 pub fn signatures(&self) -> &[Signature] {
120 let data = self.data();
121 unsafe { self.frame.signatures(data) }
123 }
124
125 #[inline]
127 pub fn static_account_keys(&self) -> &[Pubkey] {
128 let data = self.data();
129 unsafe { self.frame.static_account_keys(data) }
131 }
132
133 #[inline]
135 pub fn recent_blockhash(&self) -> &Hash {
136 let data = self.data();
137 unsafe { self.frame.recent_blockhash(data) }
139 }
140
141 #[inline]
143 pub fn instructions_iter(&self) -> InstructionsIterator {
144 let data = self.data();
145 unsafe { self.frame.instructions_iter(data) }
147 }
148
149 #[inline]
151 pub fn address_table_lookup_iter(&self) -> AddressTableLookupIterator {
152 let data = self.data();
153 unsafe { self.frame.address_table_lookup_iter(data) }
155 }
156
157 #[inline]
159 pub fn data(&self) -> &[u8] {
160 self.data.data()
161 }
162
163 #[inline]
166 pub fn message_data(&self) -> &[u8] {
167 &self.data()[usize::from(self.frame.message_offset())..]
168 }
169}
170
171impl<D: TransactionData> TransactionView<true, D> {
173 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 #[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 #[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 #[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 #[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 #[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
227impl<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()], 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}