1use {
2 crate::{
3 compiled_instruction::CompiledInstruction,
4 legacy,
5 v0::{self, LoadedAddresses},
6 v1::CachedMessage,
7 AccountKeys, AddressLoader, MessageHeader, SanitizedVersionedMessage, VersionedMessage,
8 },
9 alloc::{borrow::Cow, vec::Vec},
10 solana_address::Address,
11 solana_hash::Hash,
12 solana_instruction::{BorrowedAccountMeta, BorrowedInstruction},
13 solana_sanitize::Sanitize,
14 solana_sdk_ids::{ed25519_program, secp256k1_program, secp256r1_program},
15 solana_transaction_error::SanitizeMessageError,
16 std::collections::HashSet,
17};
18
19#[derive(Debug, Clone, Eq, PartialEq)]
20pub struct LegacyMessage<'a> {
21 pub message: Cow<'a, legacy::Message>,
23 pub is_writable_account_cache: Vec<bool>,
26}
27
28impl LegacyMessage<'_> {
29 pub fn new(message: legacy::Message, reserved_account_keys: &HashSet<Address>) -> Self {
30 let is_writable_account_cache = message
31 .account_keys
32 .iter()
33 .enumerate()
34 .map(|(i, _key)| {
35 message.is_writable_index(i)
36 && !reserved_account_keys.contains(&message.account_keys[i])
37 && !message.demote_program_id(i)
38 })
39 .collect::<Vec<_>>();
40 Self {
41 message: Cow::Owned(message),
42 is_writable_account_cache,
43 }
44 }
45
46 pub fn has_duplicates(&self) -> bool {
47 self.message.has_duplicates()
48 }
49
50 pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
51 self.message.is_key_called_as_program(key_index)
52 }
53
54 pub fn is_upgradeable_loader_present(&self) -> bool {
56 self.message.is_upgradeable_loader_present()
57 }
58
59 pub fn account_keys(&self) -> AccountKeys<'_> {
61 AccountKeys::new(&self.message.account_keys, None)
62 }
63
64 pub fn is_writable(&self, index: usize) -> bool {
65 *self.is_writable_account_cache.get(index).unwrap_or(&false)
66 }
67}
68
69#[derive(Debug, Clone, Eq, PartialEq)]
71pub enum SanitizedMessage {
72 Legacy(LegacyMessage<'static>),
74 V0(v0::LoadedMessage<'static>),
76 V1(CachedMessage<'static>),
78}
79
80impl SanitizedMessage {
81 pub fn try_new(
85 sanitized_msg: SanitizedVersionedMessage,
86 address_loader: impl AddressLoader,
87 reserved_account_keys: &HashSet<Address>,
88 ) -> Result<Self, SanitizeMessageError> {
89 Ok(match sanitized_msg.message {
90 VersionedMessage::Legacy(message) => {
91 SanitizedMessage::Legacy(LegacyMessage::new(message, reserved_account_keys))
92 }
93 VersionedMessage::V0(message) => {
94 let loaded_addresses =
95 address_loader.load_addresses(&message.address_table_lookups)?;
96 SanitizedMessage::V0(v0::LoadedMessage::new(
97 message,
98 loaded_addresses,
99 reserved_account_keys,
100 ))
101 }
102 VersionedMessage::V1(message) => {
103 SanitizedMessage::V1(CachedMessage::new(message, reserved_account_keys))
104 }
105 })
106 }
107
108 pub fn try_from_legacy_message(
110 message: legacy::Message,
111 reserved_account_keys: &HashSet<Address>,
112 ) -> Result<Self, SanitizeMessageError> {
113 message.sanitize()?;
114 Ok(Self::Legacy(LegacyMessage::new(
115 message,
116 reserved_account_keys,
117 )))
118 }
119
120 pub fn has_duplicates(&self) -> bool {
122 match self {
123 SanitizedMessage::Legacy(message) => message.has_duplicates(),
124 SanitizedMessage::V0(message) => message.has_duplicates(),
125 SanitizedMessage::V1(message) => message.has_duplicates(),
126 }
127 }
128
129 pub fn header(&self) -> &MessageHeader {
132 match self {
133 Self::Legacy(legacy_message) => &legacy_message.message.header,
134 Self::V0(loaded_msg) => &loaded_msg.message.header,
135 Self::V1(cached_msg) => &cached_msg.message.header,
136 }
137 }
138
139 pub fn legacy_message(&self) -> Option<&legacy::Message> {
141 if let Self::Legacy(legacy_message) = &self {
142 Some(&legacy_message.message)
143 } else {
144 None
145 }
146 }
147
148 pub fn fee_payer(&self) -> &Address {
150 self.account_keys()
151 .get(0)
152 .expect("sanitized messages always have a fee payer at index 0")
153 }
154
155 pub fn recent_blockhash(&self) -> &Hash {
157 match self {
158 Self::Legacy(legacy_message) => &legacy_message.message.recent_blockhash,
159 Self::V0(loaded_msg) => &loaded_msg.message.recent_blockhash,
160 Self::V1(cached_msg) => &cached_msg.message.lifetime_specifier,
161 }
162 }
163
164 pub fn instructions(&self) -> &[CompiledInstruction] {
167 match self {
168 Self::Legacy(legacy_message) => &legacy_message.message.instructions,
169 Self::V0(loaded_msg) => &loaded_msg.message.instructions,
170 Self::V1(cached_msg) => &cached_msg.message.instructions,
171 }
172 }
173
174 pub fn program_instructions_iter(
177 &self,
178 ) -> impl Iterator<Item = (&Address, &CompiledInstruction)> + Clone {
179 self.instructions().iter().map(move |ix| {
180 (
181 self.account_keys()
182 .get(usize::from(ix.program_id_index))
183 .expect("program id index is sanitized"),
184 ix,
185 )
186 })
187 }
188
189 pub fn static_account_keys(&self) -> &[Address] {
191 match self {
192 Self::Legacy(legacy_message) => &legacy_message.message.account_keys,
193 Self::V0(loaded_msg) => &loaded_msg.message.account_keys,
194 Self::V1(cached_msg) => &cached_msg.message.account_keys,
195 }
196 }
197
198 pub fn account_keys(&self) -> AccountKeys<'_> {
200 match self {
201 Self::Legacy(message) => message.account_keys(),
202 Self::V0(message) => message.account_keys(),
203 Self::V1(message) => message.account_keys(),
204 }
205 }
206
207 pub fn message_address_table_lookups(&self) -> &[v0::MessageAddressTableLookup] {
209 match self {
210 Self::V0(message) => &message.message.address_table_lookups,
211 _ => &[],
213 }
214 }
215
216 pub fn is_invoked(&self, key_index: usize) -> bool {
219 match self {
220 Self::Legacy(message) => message.is_key_called_as_program(key_index),
221 Self::V0(message) => message.is_key_called_as_program(key_index),
222 Self::V1(message) => message.is_key_called_as_program(key_index),
223 }
224 }
225
226 pub fn is_writable(&self, index: usize) -> bool {
229 match self {
230 Self::Legacy(message) => message.is_writable(index),
231 Self::V0(message) => message.is_writable(index),
232 Self::V1(message) => message.is_writable(index),
233 }
234 }
235
236 pub fn is_signer(&self, index: usize) -> bool {
239 index < usize::from(self.header().num_required_signatures)
240 }
241
242 fn loaded_lookup_table_addresses(&self) -> Option<&LoadedAddresses> {
244 match &self {
245 SanitizedMessage::V0(message) => Some(&message.loaded_addresses),
246 _ => None,
247 }
248 }
249
250 pub fn num_readonly_accounts(&self) -> usize {
252 let loaded_readonly_addresses = self
253 .loaded_lookup_table_addresses()
254 .map(|keys| keys.readonly.len())
255 .unwrap_or_default();
256 loaded_readonly_addresses
257 .saturating_add(usize::from(self.header().num_readonly_signed_accounts))
258 .saturating_add(usize::from(self.header().num_readonly_unsigned_accounts))
259 }
260
261 pub fn decompile_instructions(&self) -> Vec<BorrowedInstruction<'_>> {
263 let account_keys = self.account_keys();
264 self.program_instructions_iter()
265 .map(|(program_id, instruction)| {
266 let accounts = instruction
267 .accounts
268 .iter()
269 .map(|account_index| {
270 let account_index = *account_index as usize;
271 BorrowedAccountMeta {
272 is_signer: self.is_signer(account_index),
273 is_writable: self.is_writable(account_index),
274 pubkey: account_keys.get(account_index).unwrap(),
275 }
276 })
277 .collect();
278
279 BorrowedInstruction {
280 accounts,
281 data: &instruction.data,
282 program_id,
283 }
284 })
285 .collect()
286 }
287
288 pub fn is_upgradeable_loader_present(&self) -> bool {
290 match self {
291 Self::Legacy(message) => message.is_upgradeable_loader_present(),
292 Self::V0(message) => message.is_upgradeable_loader_present(),
293 Self::V1(message) => message.is_upgradeable_loader_present(),
294 }
295 }
296
297 pub fn num_total_signatures(&self) -> u64 {
301 self.get_signature_details().total_signatures()
302 }
303
304 pub fn num_write_locks(&self) -> u64 {
307 self.account_keys()
308 .len()
309 .saturating_sub(self.num_readonly_accounts()) as u64
310 }
311
312 pub fn get_signature_details(&self) -> TransactionSignatureDetails {
314 let mut transaction_signature_details = TransactionSignatureDetails {
315 num_transaction_signatures: u64::from(self.header().num_required_signatures),
316 ..TransactionSignatureDetails::default()
317 };
318
319 for (program_id, instruction) in self.program_instructions_iter() {
321 if secp256k1_program::check_id(program_id) {
322 if let Some(num_verifies) = instruction.data.first() {
323 transaction_signature_details.num_secp256k1_instruction_signatures =
324 transaction_signature_details
325 .num_secp256k1_instruction_signatures
326 .saturating_add(u64::from(*num_verifies));
327 }
328 } else if ed25519_program::check_id(program_id) {
329 if let Some(num_verifies) = instruction.data.first() {
330 transaction_signature_details.num_ed25519_instruction_signatures =
331 transaction_signature_details
332 .num_ed25519_instruction_signatures
333 .saturating_add(u64::from(*num_verifies));
334 }
335 } else if secp256r1_program::check_id(program_id) {
336 if let Some(num_verifies) = instruction.data.first() {
337 transaction_signature_details.num_secp256r1_instruction_signatures =
338 transaction_signature_details
339 .num_secp256r1_instruction_signatures
340 .saturating_add(u64::from(*num_verifies));
341 }
342 }
343 }
344
345 transaction_signature_details
346 }
347}
348
349#[derive(Clone, Debug, Default)]
352pub struct TransactionSignatureDetails {
353 num_transaction_signatures: u64,
354 num_secp256k1_instruction_signatures: u64,
355 num_ed25519_instruction_signatures: u64,
356 num_secp256r1_instruction_signatures: u64,
357}
358
359impl TransactionSignatureDetails {
360 pub const fn new(
361 num_transaction_signatures: u64,
362 num_secp256k1_instruction_signatures: u64,
363 num_ed25519_instruction_signatures: u64,
364 num_secp256r1_instruction_signatures: u64,
365 ) -> Self {
366 Self {
367 num_transaction_signatures,
368 num_secp256k1_instruction_signatures,
369 num_ed25519_instruction_signatures,
370 num_secp256r1_instruction_signatures,
371 }
372 }
373
374 pub fn total_signatures(&self) -> u64 {
376 self.num_transaction_signatures
377 .saturating_add(self.num_secp256k1_instruction_signatures)
378 .saturating_add(self.num_ed25519_instruction_signatures)
379 .saturating_add(self.num_secp256r1_instruction_signatures)
380 }
381
382 pub fn num_transaction_signatures(&self) -> u64 {
384 self.num_transaction_signatures
385 }
386
387 pub fn num_secp256k1_instruction_signatures(&self) -> u64 {
389 self.num_secp256k1_instruction_signatures
390 }
391
392 pub fn num_ed25519_instruction_signatures(&self) -> u64 {
394 self.num_ed25519_instruction_signatures
395 }
396
397 pub fn num_secp256r1_instruction_signatures(&self) -> u64 {
399 self.num_secp256r1_instruction_signatures
400 }
401}
402
403#[cfg(test)]
404mod tests {
405 use {super::*, crate::v0, alloc::vec, std::collections::HashSet};
406
407 #[test]
408 fn test_try_from_legacy_message() {
409 let legacy_message_with_no_signers = legacy::Message {
410 account_keys: vec![Address::new_unique()],
411 ..legacy::Message::default()
412 };
413
414 assert_eq!(
415 SanitizedMessage::try_from_legacy_message(
416 legacy_message_with_no_signers,
417 &HashSet::default(),
418 )
419 .err(),
420 Some(SanitizeMessageError::IndexOutOfBounds),
421 );
422 }
423
424 #[test]
425 fn test_num_readonly_accounts() {
426 let key0 = Address::new_unique();
427 let key1 = Address::new_unique();
428 let key2 = Address::new_unique();
429 let key3 = Address::new_unique();
430 let key4 = Address::new_unique();
431 let key5 = Address::new_unique();
432
433 let legacy_message = SanitizedMessage::try_from_legacy_message(
434 legacy::Message {
435 header: MessageHeader {
436 num_required_signatures: 2,
437 num_readonly_signed_accounts: 1,
438 num_readonly_unsigned_accounts: 1,
439 },
440 account_keys: vec![key0, key1, key2, key3],
441 ..legacy::Message::default()
442 },
443 &HashSet::default(),
444 )
445 .unwrap();
446
447 assert_eq!(legacy_message.num_readonly_accounts(), 2);
448
449 let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
450 v0::Message {
451 header: MessageHeader {
452 num_required_signatures: 2,
453 num_readonly_signed_accounts: 1,
454 num_readonly_unsigned_accounts: 1,
455 },
456 account_keys: vec![key0, key1, key2, key3],
457 ..v0::Message::default()
458 },
459 LoadedAddresses {
460 writable: vec![key4],
461 readonly: vec![key5],
462 },
463 &HashSet::default(),
464 ));
465
466 assert_eq!(v0_message.num_readonly_accounts(), 3);
467 }
468
469 #[test]
470 #[allow(clippy::get_first)]
471 fn test_is_writable_account_cache() {
472 let key0 = Address::new_unique();
473 let key1 = Address::new_unique();
474 let key2 = Address::new_unique();
475 let key3 = Address::new_unique();
476 let key4 = Address::new_unique();
477 let key5 = Address::new_unique();
478
479 let legacy_message = SanitizedMessage::try_from_legacy_message(
480 legacy::Message {
481 header: MessageHeader {
482 num_required_signatures: 2,
483 num_readonly_signed_accounts: 1,
484 num_readonly_unsigned_accounts: 1,
485 },
486 account_keys: vec![key0, key1, key2, key3],
487 ..legacy::Message::default()
488 },
489 &HashSet::default(),
490 )
491 .unwrap();
492 match legacy_message {
493 SanitizedMessage::Legacy(message) => {
494 assert_eq!(
495 message.is_writable_account_cache.len(),
496 message.account_keys().len()
497 );
498 assert!(message.is_writable_account_cache.get(0).unwrap());
499 assert!(!message.is_writable_account_cache.get(1).unwrap());
500 assert!(message.is_writable_account_cache.get(2).unwrap());
501 assert!(!message.is_writable_account_cache.get(3).unwrap());
502 }
503 _ => {
504 panic!("Expect to be SanitizedMessage::LegacyMessage")
505 }
506 }
507
508 let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
509 v0::Message {
510 header: MessageHeader {
511 num_required_signatures: 2,
512 num_readonly_signed_accounts: 1,
513 num_readonly_unsigned_accounts: 1,
514 },
515 account_keys: vec![key0, key1, key2, key3],
516 ..v0::Message::default()
517 },
518 LoadedAddresses {
519 writable: vec![key4],
520 readonly: vec![key5],
521 },
522 &HashSet::default(),
523 ));
524 match v0_message {
525 SanitizedMessage::V0(message) => {
526 assert_eq!(
527 message.is_writable_account_cache.len(),
528 message.account_keys().len()
529 );
530 assert!(message.is_writable_account_cache.get(0).unwrap());
531 assert!(!message.is_writable_account_cache.get(1).unwrap());
532 assert!(message.is_writable_account_cache.get(2).unwrap());
533 assert!(!message.is_writable_account_cache.get(3).unwrap());
534 assert!(message.is_writable_account_cache.get(4).unwrap());
535 assert!(!message.is_writable_account_cache.get(5).unwrap());
536 }
537 _ => {
538 panic!("Expect to be SanitizedMessage::V0")
539 }
540 }
541 }
542
543 #[test]
544 fn test_get_signature_details() {
545 let key0 = Address::new_unique();
546 let key1 = Address::new_unique();
547 let loader_key = Address::new_unique();
548
549 let loader_instr = CompiledInstruction::new(2, &(), vec![0, 1]);
550 let mock_secp256k1_instr = CompiledInstruction::new(3, &[1u8; 10], vec![]);
551 let mock_ed25519_instr = CompiledInstruction::new(4, &[5u8; 10], vec![]);
552
553 let message = SanitizedMessage::try_from_legacy_message(
554 legacy::Message::new_with_compiled_instructions(
555 2,
556 1,
557 2,
558 vec![
559 key0,
560 key1,
561 loader_key,
562 secp256k1_program::id(),
563 ed25519_program::id(),
564 ],
565 Hash::default(),
566 vec![
567 loader_instr,
568 mock_secp256k1_instr.clone(),
569 mock_ed25519_instr,
570 mock_secp256k1_instr,
571 ],
572 ),
573 &HashSet::new(),
574 )
575 .unwrap();
576
577 let signature_details = message.get_signature_details();
578 assert_eq!(2, signature_details.num_transaction_signatures);
580 assert_eq!(2, signature_details.num_secp256k1_instruction_signatures);
582 assert_eq!(5, signature_details.num_ed25519_instruction_signatures);
584 }
585
586 #[test]
587 fn test_static_account_keys() {
588 let keys = vec![
589 Address::new_unique(),
590 Address::new_unique(),
591 Address::new_unique(),
592 ];
593
594 let header = MessageHeader {
595 num_required_signatures: 2,
596 num_readonly_signed_accounts: 1,
597 num_readonly_unsigned_accounts: 1,
598 };
599
600 let legacy_message = SanitizedMessage::try_from_legacy_message(
601 legacy::Message {
602 header,
603 account_keys: keys.clone(),
604 ..legacy::Message::default()
605 },
606 &HashSet::default(),
607 )
608 .unwrap();
609 assert_eq!(legacy_message.static_account_keys(), &keys);
610
611 let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
612 v0::Message {
613 header,
614 account_keys: keys.clone(),
615 ..v0::Message::default()
616 },
617 LoadedAddresses {
618 writable: vec![],
619 readonly: vec![],
620 },
621 &HashSet::default(),
622 ));
623 assert_eq!(v0_message.static_account_keys(), &keys);
624
625 let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
626 v0::Message {
627 header,
628 account_keys: keys.clone(),
629 ..v0::Message::default()
630 },
631 LoadedAddresses {
632 writable: vec![Address::new_unique()],
633 readonly: vec![Address::new_unique()],
634 },
635 &HashSet::default(),
636 ));
637 assert_eq!(v0_message.static_account_keys(), &keys);
638 }
639}