1use {
2 crate::{
3 result::{Result, TransactionViewError},
4 transaction_data::TransactionData,
5 transaction_version::TransactionVersion,
6 transaction_view::TransactionView,
7 },
8 core::{
9 fmt::{Debug, Formatter},
10 ops::Deref,
11 },
12 solana_hash::Hash,
13 solana_message::{v0::LoadedAddresses, AccountKeys},
14 solana_pubkey::Pubkey,
15 solana_sdk_ids::bpf_loader_upgradeable,
16 solana_signature::Signature,
17 solana_svm_transaction::{
18 instruction::SVMInstruction, message_address_table_lookup::SVMMessageAddressTableLookup,
19 svm_message::SVMMessage, svm_transaction::SVMTransaction,
20 },
21 std::collections::HashSet,
22};
23
24pub struct ResolvedTransactionView<D: TransactionData> {
27 view: TransactionView<true, D>,
29 resolved_addresses: Option<LoadedAddresses>,
31 writable_cache: [bool; 256],
35}
36
37impl<D: TransactionData> Deref for ResolvedTransactionView<D> {
38 type Target = TransactionView<true, D>;
39
40 fn deref(&self) -> &Self::Target {
41 &self.view
42 }
43}
44
45impl<D: TransactionData> ResolvedTransactionView<D> {
46 pub fn try_new(
49 view: TransactionView<true, D>,
50 resolved_addresses: Option<LoadedAddresses>,
51 reserved_account_keys: &HashSet<Pubkey>,
52 ) -> Result<Self> {
53 let resolved_addresses_ref = resolved_addresses.as_ref();
54
55 if matches!(view.version(), TransactionVersion::V0) && resolved_addresses_ref.is_none() {
61 return Err(TransactionViewError::AddressLookupMismatch);
62 }
63 if let Some(loaded_addresses) = resolved_addresses_ref {
64 if loaded_addresses.writable.len() != usize::from(view.total_writable_lookup_accounts())
65 || loaded_addresses.readonly.len()
66 != usize::from(view.total_readonly_lookup_accounts())
67 {
68 return Err(TransactionViewError::AddressLookupMismatch);
69 }
70 } else if view.total_writable_lookup_accounts() != 0
71 || view.total_readonly_lookup_accounts() != 0
72 {
73 return Err(TransactionViewError::AddressLookupMismatch);
74 }
75
76 let writable_cache =
77 Self::cache_is_writable(&view, resolved_addresses_ref, reserved_account_keys);
78 Ok(Self {
79 view,
80 resolved_addresses,
81 writable_cache,
82 })
83 }
84
85 fn cache_is_writable(
90 view: &TransactionView<true, D>,
91 resolved_addresses: Option<&LoadedAddresses>,
92 reserved_account_keys: &HashSet<Pubkey>,
93 ) -> [bool; 256] {
94 let account_keys = AccountKeys::new(view.static_account_keys(), resolved_addresses);
97
98 let mut is_writable_cache = [false; 256];
99 let num_static_account_keys = usize::from(view.num_static_account_keys());
100 let num_writable_lookup_accounts = usize::from(view.total_writable_lookup_accounts());
101 let num_signed_accounts = usize::from(view.num_required_signatures());
102 let num_writable_unsigned_static_accounts =
103 usize::from(view.num_writable_unsigned_static_accounts());
104 let num_writable_signed_static_accounts =
105 usize::from(view.num_writable_signed_static_accounts());
106
107 for (index, key) in account_keys.iter().enumerate() {
108 let is_requested_write = {
109 if index >= num_static_account_keys {
111 let loaded_address_index = index.wrapping_sub(num_static_account_keys);
112 loaded_address_index < num_writable_lookup_accounts
113 } else if index >= num_signed_accounts {
114 let unsigned_account_index = index.wrapping_sub(num_signed_accounts);
115 unsigned_account_index < num_writable_unsigned_static_accounts
116 } else {
117 index < num_writable_signed_static_accounts
118 }
119 };
120
121 is_writable_cache[index] = is_requested_write && !reserved_account_keys.contains(key);
123 }
124
125 let mut is_upgradable_loader_present = None;
130 for ix in view.instructions_iter() {
131 let program_id_index = usize::from(ix.program_id_index);
132 if is_writable_cache[program_id_index]
133 && !*is_upgradable_loader_present.get_or_insert_with(|| {
134 for key in account_keys.iter() {
135 if key == &bpf_loader_upgradeable::ID {
136 return true;
137 }
138 }
139 false
140 })
141 {
142 is_writable_cache[program_id_index] = false;
143 }
144 }
145
146 is_writable_cache
147 }
148
149 pub fn loaded_addresses(&self) -> Option<&LoadedAddresses> {
150 self.resolved_addresses.as_ref()
151 }
152}
153
154impl<D: TransactionData> SVMMessage for ResolvedTransactionView<D> {
155 fn num_transaction_signatures(&self) -> u64 {
156 u64::from(self.view.num_required_signatures())
157 }
158
159 fn num_write_locks(&self) -> u64 {
160 self.view.num_requested_write_locks()
161 }
162
163 fn recent_blockhash(&self) -> &Hash {
164 self.view.recent_blockhash()
165 }
166
167 fn num_instructions(&self) -> usize {
168 usize::from(self.view.num_instructions())
169 }
170
171 fn instructions_iter(&self) -> impl Iterator<Item = SVMInstruction> {
172 self.view.instructions_iter()
173 }
174
175 fn program_instructions_iter(
176 &self,
177 ) -> impl Iterator<
178 Item = (
179 &solana_pubkey::Pubkey,
180 solana_svm_transaction::instruction::SVMInstruction,
181 ),
182 > + Clone {
183 self.view.program_instructions_iter()
184 }
185
186 fn account_keys(&self) -> AccountKeys {
187 AccountKeys::new(
188 self.view.static_account_keys(),
189 self.resolved_addresses.as_ref(),
190 )
191 }
192
193 fn fee_payer(&self) -> &Pubkey {
194 &self.view.static_account_keys()[0]
195 }
196
197 fn is_writable(&self, index: usize) -> bool {
198 self.writable_cache.get(index).copied().unwrap_or(false)
199 }
200
201 fn is_signer(&self, index: usize) -> bool {
202 index < usize::from(self.view.num_required_signatures())
203 }
204
205 fn is_invoked(&self, key_index: usize) -> bool {
206 let Ok(index) = u8::try_from(key_index) else {
207 return false;
208 };
209 self.view
210 .instructions_iter()
211 .any(|ix| ix.program_id_index == index)
212 }
213
214 fn num_lookup_tables(&self) -> usize {
215 usize::from(self.view.num_address_table_lookups())
216 }
217
218 fn message_address_table_lookups(&self) -> impl Iterator<Item = SVMMessageAddressTableLookup> {
219 self.view.address_table_lookup_iter()
220 }
221}
222
223impl<D: TransactionData> SVMTransaction for ResolvedTransactionView<D> {
224 fn signature(&self) -> &Signature {
225 &self.view.signatures()[0]
226 }
227
228 fn signatures(&self) -> &[Signature] {
229 self.view.signatures()
230 }
231}
232
233impl<D: TransactionData> Debug for ResolvedTransactionView<D> {
234 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
235 f.debug_struct("ResolvedTransactionView")
236 .field("view", &self.view)
237 .finish()
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use {
244 super::*,
245 crate::transaction_view::SanitizedTransactionView,
246 solana_message::{
247 compiled_instruction::CompiledInstruction,
248 v0::{self, MessageAddressTableLookup},
249 MessageHeader, VersionedMessage,
250 },
251 solana_sdk_ids::{system_program, sysvar},
252 solana_signature::Signature,
253 solana_transaction::versioned::VersionedTransaction,
254 };
255
256 #[test]
257 fn test_expected_loaded_addresses() {
258 let static_keys = vec![Pubkey::new_unique(), Pubkey::new_unique()];
260 let transaction = VersionedTransaction {
261 signatures: vec![Signature::default()],
262 message: VersionedMessage::V0(v0::Message {
263 header: MessageHeader {
264 num_required_signatures: 1,
265 num_readonly_signed_accounts: 0,
266 num_readonly_unsigned_accounts: 0,
267 },
268 instructions: vec![],
269 account_keys: static_keys,
270 address_table_lookups: vec![MessageAddressTableLookup {
271 account_key: Pubkey::new_unique(),
272 writable_indexes: vec![0],
273 readonly_indexes: vec![1],
274 }],
275 recent_blockhash: Hash::default(),
276 }),
277 };
278 let bytes = bincode::serialize(&transaction).unwrap();
279 let view = SanitizedTransactionView::try_new_sanitized(bytes.as_ref()).unwrap();
280 let result = ResolvedTransactionView::try_new(view, None, &HashSet::default());
281 assert!(matches!(
282 result,
283 Err(TransactionViewError::AddressLookupMismatch)
284 ));
285 }
286
287 #[test]
288 fn test_unexpected_loaded_addresses() {
289 let static_keys = vec![Pubkey::new_unique(), Pubkey::new_unique()];
291 let loaded_addresses = LoadedAddresses {
292 writable: vec![Pubkey::new_unique()],
293 readonly: vec![],
294 };
295 let transaction = VersionedTransaction {
296 signatures: vec![Signature::default()],
297 message: VersionedMessage::V0(v0::Message {
298 header: MessageHeader {
299 num_required_signatures: 1,
300 num_readonly_signed_accounts: 0,
301 num_readonly_unsigned_accounts: 0,
302 },
303 instructions: vec![],
304 account_keys: static_keys,
305 address_table_lookups: vec![],
306 recent_blockhash: Hash::default(),
307 }),
308 };
309 let bytes = bincode::serialize(&transaction).unwrap();
310 let view = SanitizedTransactionView::try_new_sanitized(bytes.as_ref()).unwrap();
311 let result =
312 ResolvedTransactionView::try_new(view, Some(loaded_addresses), &HashSet::default());
313 assert!(matches!(
314 result,
315 Err(TransactionViewError::AddressLookupMismatch)
316 ));
317 }
318
319 #[test]
320 fn test_mismatched_loaded_address_lengths() {
321 let static_keys = vec![Pubkey::new_unique(), Pubkey::new_unique()];
324 let loaded_addresses = LoadedAddresses {
325 writable: vec![Pubkey::new_unique()],
326 readonly: vec![],
327 };
328 let transaction = VersionedTransaction {
329 signatures: vec![Signature::default()],
330 message: VersionedMessage::V0(v0::Message {
331 header: MessageHeader {
332 num_required_signatures: 1,
333 num_readonly_signed_accounts: 0,
334 num_readonly_unsigned_accounts: 0,
335 },
336 instructions: vec![],
337 account_keys: static_keys,
338 address_table_lookups: vec![MessageAddressTableLookup {
339 account_key: Pubkey::new_unique(),
340 writable_indexes: vec![0],
341 readonly_indexes: vec![1],
342 }],
343 recent_blockhash: Hash::default(),
344 }),
345 };
346 let bytes = bincode::serialize(&transaction).unwrap();
347 let view = SanitizedTransactionView::try_new_sanitized(bytes.as_ref()).unwrap();
348 let result =
349 ResolvedTransactionView::try_new(view, Some(loaded_addresses), &HashSet::default());
350 assert!(matches!(
351 result,
352 Err(TransactionViewError::AddressLookupMismatch)
353 ));
354 }
355
356 #[test]
357 fn test_is_writable() {
358 let reserved_account_keys = HashSet::from_iter([sysvar::clock::id(), system_program::id()]);
359 let create_transaction_with_keys =
361 |static_keys: Vec<Pubkey>, loaded_addresses: &LoadedAddresses| VersionedTransaction {
362 signatures: vec![Signature::default()],
363 message: VersionedMessage::V0(v0::Message {
364 header: MessageHeader {
365 num_required_signatures: 1,
366 num_readonly_signed_accounts: 0,
367 num_readonly_unsigned_accounts: 1,
368 },
369 account_keys: static_keys[..2].to_vec(),
370 recent_blockhash: Hash::default(),
371 instructions: vec![],
372 address_table_lookups: vec![MessageAddressTableLookup {
373 account_key: Pubkey::new_unique(),
374 writable_indexes: (0..loaded_addresses.writable.len())
375 .map(|x| (static_keys.len() + x) as u8)
376 .collect(),
377 readonly_indexes: (0..loaded_addresses.readonly.len())
378 .map(|x| {
379 (static_keys.len() + loaded_addresses.writable.len() + x) as u8
380 })
381 .collect(),
382 }],
383 }),
384 };
385
386 let key0 = Pubkey::new_unique();
387 let key1 = Pubkey::new_unique();
388 let key2 = Pubkey::new_unique();
389 {
390 let static_keys = vec![sysvar::clock::id(), key0];
391 let loaded_addresses = LoadedAddresses {
392 writable: vec![key1],
393 readonly: vec![key2],
394 };
395 let transaction = create_transaction_with_keys(static_keys, &loaded_addresses);
396 let bytes = bincode::serialize(&transaction).unwrap();
397 let view = SanitizedTransactionView::try_new_sanitized(bytes.as_ref()).unwrap();
398 let resolved_view = ResolvedTransactionView::try_new(
399 view,
400 Some(loaded_addresses),
401 &reserved_account_keys,
402 )
403 .unwrap();
404
405 let expected = vec![false, false, true, false];
407 for (index, expected) in expected.into_iter().enumerate() {
408 assert_eq!(resolved_view.is_writable(index), expected);
409 }
410 }
411
412 {
413 let static_keys = vec![system_program::id(), key0];
414 let loaded_addresses = LoadedAddresses {
415 writable: vec![key1],
416 readonly: vec![key2],
417 };
418 let transaction = create_transaction_with_keys(static_keys, &loaded_addresses);
419 let bytes = bincode::serialize(&transaction).unwrap();
420 let view = SanitizedTransactionView::try_new_sanitized(bytes.as_ref()).unwrap();
421 let resolved_view = ResolvedTransactionView::try_new(
422 view,
423 Some(loaded_addresses),
424 &reserved_account_keys,
425 )
426 .unwrap();
427
428 let expected = vec![false, false, true, false];
430 for (index, expected) in expected.into_iter().enumerate() {
431 assert_eq!(resolved_view.is_writable(index), expected);
432 }
433 }
434
435 {
436 let static_keys = vec![key0, key1];
437 let loaded_addresses = LoadedAddresses {
438 writable: vec![system_program::id()],
439 readonly: vec![key2],
440 };
441 let transaction = create_transaction_with_keys(static_keys, &loaded_addresses);
442 let bytes = bincode::serialize(&transaction).unwrap();
443 let view = SanitizedTransactionView::try_new_sanitized(bytes.as_ref()).unwrap();
444 let resolved_view = ResolvedTransactionView::try_new(
445 view,
446 Some(loaded_addresses),
447 &reserved_account_keys,
448 )
449 .unwrap();
450
451 let expected = vec![true, false, false, false];
453 for (index, expected) in expected.into_iter().enumerate() {
454 assert_eq!(resolved_view.is_writable(index), expected);
455 }
456 }
457 }
458
459 #[test]
460 fn test_demote_writable_program() {
461 let reserved_account_keys = HashSet::default();
462 let key0 = Pubkey::new_unique();
463 let key1 = Pubkey::new_unique();
464 let key2 = Pubkey::new_unique();
465 let key3 = Pubkey::new_unique();
466 let key4 = Pubkey::new_unique();
467 let loaded_addresses = LoadedAddresses {
468 writable: vec![key3, key4],
469 readonly: vec![],
470 };
471 let create_transaction_with_static_keys =
472 |static_keys: Vec<Pubkey>, loaded_addresses: &LoadedAddresses| VersionedTransaction {
473 signatures: vec![Signature::default()],
474 message: VersionedMessage::V0(v0::Message {
475 header: MessageHeader {
476 num_required_signatures: 1,
477 num_readonly_signed_accounts: 0,
478 num_readonly_unsigned_accounts: 0,
479 },
480 instructions: vec![CompiledInstruction {
481 program_id_index: 1,
482 accounts: vec![0],
483 data: vec![],
484 }],
485 account_keys: static_keys,
486 address_table_lookups: vec![MessageAddressTableLookup {
487 account_key: Pubkey::new_unique(),
488 writable_indexes: (0..loaded_addresses.writable.len())
489 .map(|x| x as u8)
490 .collect(),
491 readonly_indexes: (0..loaded_addresses.readonly.len())
492 .map(|x| (loaded_addresses.writable.len() + x) as u8)
493 .collect(),
494 }],
495 recent_blockhash: Hash::default(),
496 }),
497 };
498
499 {
501 let static_keys = vec![key0, key1, key2];
502 let transaction = create_transaction_with_static_keys(static_keys, &loaded_addresses);
503 let bytes = bincode::serialize(&transaction).unwrap();
504 let view = SanitizedTransactionView::try_new_sanitized(bytes.as_ref()).unwrap();
505 let resolved_view = ResolvedTransactionView::try_new(
506 view,
507 Some(loaded_addresses.clone()),
508 &reserved_account_keys,
509 )
510 .unwrap();
511
512 let expected = vec![true, false, true, true, true];
513 for (index, expected) in expected.into_iter().enumerate() {
514 assert_eq!(resolved_view.is_writable(index), expected);
515 }
516 }
517
518 {
520 let static_keys = vec![key0, key1, bpf_loader_upgradeable::ID];
521 let transaction = create_transaction_with_static_keys(static_keys, &loaded_addresses);
522 let bytes = bincode::serialize(&transaction).unwrap();
523 let view = SanitizedTransactionView::try_new_sanitized(bytes.as_ref()).unwrap();
524 let resolved_view = ResolvedTransactionView::try_new(
525 view,
526 Some(loaded_addresses.clone()),
527 &reserved_account_keys,
528 )
529 .unwrap();
530
531 let expected = vec![true, true, true, true, true];
532 for (index, expected) in expected.into_iter().enumerate() {
533 assert_eq!(resolved_view.is_writable(index), expected);
534 }
535 }
536
537 {
539 let static_keys = vec![key0, key1, key2];
540 let loaded_addresses = LoadedAddresses {
541 writable: vec![key3],
542 readonly: vec![bpf_loader_upgradeable::ID],
543 };
544 let transaction = create_transaction_with_static_keys(static_keys, &loaded_addresses);
545 let bytes = bincode::serialize(&transaction).unwrap();
546 let view = SanitizedTransactionView::try_new_sanitized(bytes.as_ref()).unwrap();
547
548 let resolved_view = ResolvedTransactionView::try_new(
549 view,
550 Some(loaded_addresses.clone()),
551 &reserved_account_keys,
552 )
553 .unwrap();
554
555 let expected = vec![true, true, true, true, false];
556 for (index, expected) in expected.into_iter().enumerate() {
557 assert_eq!(resolved_view.is_writable(index), expected);
558 }
559 }
560 }
561}