1use {
2 crate::versioned::{sanitized::SanitizedVersionedTransaction, VersionedTransaction},
3 alloc::vec::Vec,
4 solana_address::Address,
5 solana_hash::Hash,
6 solana_message::{
7 legacy,
8 v0::{self, LoadedAddresses},
9 v1::{self, CachedMessage},
10 AddressLoader, LegacyMessage, SanitizedMessage, SanitizedVersionedMessage,
11 VersionedMessage,
12 },
13 solana_signature::Signature,
14 solana_transaction_error::{TransactionError, TransactionResult},
15 std::collections::HashSet,
16};
17#[cfg(feature = "blake3")]
18use {crate::Transaction, solana_sanitize::Sanitize};
19
20pub const MAX_TX_ACCOUNT_LOCKS: usize = 128;
24
25#[derive(Debug, Clone, Eq, PartialEq)]
27pub struct SanitizedTransaction {
28 message: SanitizedMessage,
29 message_hash: Hash,
30 is_simple_vote_tx: bool,
31 signatures: Vec<Signature>,
32}
33
34#[derive(Debug, Clone, Default, Eq, PartialEq)]
36pub struct TransactionAccountLocks<'a> {
37 pub readonly: Vec<&'a Address>,
39 pub writable: Vec<&'a Address>,
41}
42
43pub enum MessageHash {
46 Precomputed(Hash),
47 Compute,
48}
49
50impl From<Hash> for MessageHash {
51 fn from(hash: Hash) -> Self {
52 Self::Precomputed(hash)
53 }
54}
55
56impl SanitizedTransaction {
57 pub fn try_new(
61 tx: SanitizedVersionedTransaction,
62 message_hash: Hash,
63 is_simple_vote_tx: bool,
64 address_loader: impl AddressLoader,
65 reserved_account_keys: &HashSet<Address>,
66 ) -> TransactionResult<Self> {
67 let signatures = tx.signatures;
68 let SanitizedVersionedMessage { message } = tx.message;
69 let message = match message {
70 VersionedMessage::Legacy(message) => {
71 SanitizedMessage::Legacy(LegacyMessage::new(message, reserved_account_keys))
72 }
73 VersionedMessage::V0(message) => {
74 let loaded_addresses =
75 address_loader.load_addresses(&message.address_table_lookups)?;
76 SanitizedMessage::V0(v0::LoadedMessage::new(
77 message,
78 loaded_addresses,
79 reserved_account_keys,
80 ))
81 }
82 VersionedMessage::V1(message) => {
83 SanitizedMessage::V1(CachedMessage::new(message, reserved_account_keys))
84 }
85 };
86
87 Ok(Self {
88 message,
89 message_hash,
90 is_simple_vote_tx,
91 signatures,
92 })
93 }
94
95 #[cfg(feature = "blake3")]
96 pub fn try_create(
100 tx: VersionedTransaction,
101 message_hash: impl Into<MessageHash>,
102 is_simple_vote_tx: Option<bool>,
103 address_loader: impl AddressLoader,
104 reserved_account_keys: &HashSet<Address>,
105 ) -> TransactionResult<Self> {
106 let sanitized_versioned_tx = SanitizedVersionedTransaction::try_from(tx)?;
107 let is_simple_vote_tx = is_simple_vote_tx.unwrap_or_else(|| {
108 crate::simple_vote_transaction_checker::is_simple_vote_transaction(
109 &sanitized_versioned_tx,
110 )
111 });
112 let message_hash = match message_hash.into() {
113 MessageHash::Compute => sanitized_versioned_tx.message.message.hash(),
114 MessageHash::Precomputed(hash) => hash,
115 };
116 Self::try_new(
117 sanitized_versioned_tx,
118 message_hash,
119 is_simple_vote_tx,
120 address_loader,
121 reserved_account_keys,
122 )
123 }
124
125 #[cfg(feature = "blake3")]
127 pub fn try_from_legacy_transaction(
128 tx: Transaction,
129 reserved_account_keys: &HashSet<Address>,
130 ) -> TransactionResult<Self> {
131 tx.sanitize()?;
132
133 Ok(Self {
134 message_hash: tx.message.hash(),
135 message: SanitizedMessage::Legacy(LegacyMessage::new(
136 tx.message,
137 reserved_account_keys,
138 )),
139 is_simple_vote_tx: false,
140 signatures: tx.signatures,
141 })
142 }
143
144 #[cfg(feature = "blake3")]
146 pub fn from_transaction_for_tests(tx: Transaction) -> Self {
147 let empty_key_set = HashSet::default();
148 Self::try_from_legacy_transaction(tx, &empty_key_set).unwrap()
149 }
150
151 pub fn try_new_from_fields(
154 message: SanitizedMessage,
155 message_hash: Hash,
156 is_simple_vote_tx: bool,
157 signatures: Vec<Signature>,
158 ) -> TransactionResult<Self> {
159 VersionedTransaction::sanitize_signatures_inner(
160 usize::from(message.header().num_required_signatures),
161 message.static_account_keys().len(),
162 signatures.len(),
163 )?;
164
165 Ok(Self {
166 message,
167 message_hash,
168 signatures,
169 is_simple_vote_tx,
170 })
171 }
172
173 pub fn signature(&self) -> &Signature {
181 &self.signatures[0]
182 }
183
184 pub fn signatures(&self) -> &[Signature] {
186 &self.signatures
187 }
188
189 pub fn message(&self) -> &SanitizedMessage {
191 &self.message
192 }
193
194 pub fn message_hash(&self) -> &Hash {
196 &self.message_hash
197 }
198
199 pub fn is_simple_vote_transaction(&self) -> bool {
201 self.is_simple_vote_tx
202 }
203
204 pub fn to_versioned_transaction(&self) -> VersionedTransaction {
207 let signatures = self.signatures.clone();
208 match &self.message {
209 SanitizedMessage::Legacy(legacy_message) => VersionedTransaction {
210 message: VersionedMessage::Legacy(legacy::Message::clone(&legacy_message.message)),
211 signatures,
212 },
213 SanitizedMessage::V0(sanitized_msg) => VersionedTransaction {
214 signatures,
215 message: VersionedMessage::V0(v0::Message::clone(&sanitized_msg.message)),
216 },
217 SanitizedMessage::V1(sanitized_msg) => VersionedTransaction {
218 message: VersionedMessage::V1(v1::Message::clone(&sanitized_msg.message)),
219 signatures,
220 },
221 }
222 }
223
224 pub fn get_account_locks(
226 &self,
227 tx_account_lock_limit: usize,
228 ) -> TransactionResult<TransactionAccountLocks<'_>> {
229 Self::validate_account_locks(self.message(), tx_account_lock_limit)?;
230 Ok(self.get_account_locks_unchecked())
231 }
232
233 pub fn get_account_locks_unchecked(&self) -> TransactionAccountLocks<'_> {
235 let message = &self.message;
236 let account_keys = message.account_keys();
237 let num_readonly_accounts = message.num_readonly_accounts();
238 let num_writable_accounts = account_keys.len().saturating_sub(num_readonly_accounts);
239
240 let mut account_locks = TransactionAccountLocks {
241 writable: Vec::with_capacity(num_writable_accounts),
242 readonly: Vec::with_capacity(num_readonly_accounts),
243 };
244
245 for (i, key) in account_keys.iter().enumerate() {
246 if message.is_writable(i) {
247 account_locks.writable.push(key);
248 } else {
249 account_locks.readonly.push(key);
250 }
251 }
252
253 account_locks
254 }
255
256 pub fn get_loaded_addresses(&self) -> LoadedAddresses {
258 match &self.message {
259 SanitizedMessage::Legacy(_) | SanitizedMessage::V1(_) => LoadedAddresses::default(),
260 SanitizedMessage::V0(message) => LoadedAddresses::clone(&message.loaded_addresses),
261 }
262 }
263
264 #[cfg(feature = "verify")]
265 fn message_data(&self) -> Vec<u8> {
267 match &self.message {
268 SanitizedMessage::Legacy(legacy_message) => legacy_message.message.serialize(),
269 SanitizedMessage::V0(loaded_msg) => loaded_msg.message.serialize(),
270 SanitizedMessage::V1(cached_msg) => cached_msg.message.serialize(),
271 }
272 }
273
274 #[cfg(feature = "verify")]
275 pub fn verify(&self) -> TransactionResult<()> {
277 let message_bytes = self.message_data();
278 crate::verify_signatures(
279 &self.signatures,
280 self.message.static_account_keys(),
281 &message_bytes,
282 )
283 }
284
285 pub fn validate_account_locks(
287 message: &SanitizedMessage,
288 tx_account_lock_limit: usize,
289 ) -> TransactionResult<()> {
290 if message.has_duplicates() {
291 Err(TransactionError::AccountLoadedTwice)
292 } else if message.account_keys().len() > tx_account_lock_limit {
293 Err(TransactionError::TooManyAccountLocks)
294 } else {
295 Ok(())
296 }
297 }
298
299 #[cfg(feature = "dev-context-only-utils")]
300 pub fn new_for_tests(
301 message: SanitizedMessage,
302 signatures: Vec<Signature>,
303 is_simple_vote_tx: bool,
304 ) -> SanitizedTransaction {
305 SanitizedTransaction {
306 message,
307 message_hash: Hash::new_unique(),
308 signatures,
309 is_simple_vote_tx,
310 }
311 }
312}
313
314#[cfg(test)]
315#[allow(clippy::arithmetic_side_effects)]
316mod tests {
317 use {
318 super::*,
319 alloc::vec,
320 solana_instruction::{AccountMeta, Instruction},
321 solana_keypair::Keypair,
322 solana_message::{MessageHeader, SimpleAddressLoader},
323 solana_signer::Signer,
324 solana_vote_interface::{instruction, state::Vote},
325 };
326
327 #[test]
328 fn test_try_create_simple_vote_tx() {
329 let bank_hash = Hash::default();
330 let block_hash = Hash::default();
331 let empty_key_set = HashSet::default();
332 let vote_keypair = Keypair::new();
333 let node_keypair = Keypair::new();
334 let auth_keypair = Keypair::new();
335 let votes = Vote::new(vec![1, 2, 3], bank_hash);
336 let vote_ix = instruction::vote(&vote_keypair.pubkey(), &auth_keypair.pubkey(), votes);
337 let mut vote_tx = Transaction::new_with_payer(&[vote_ix], Some(&node_keypair.pubkey()));
338 vote_tx.partial_sign(&[&node_keypair], block_hash);
339 vote_tx.partial_sign(&[&auth_keypair], block_hash);
340
341 {
343 let vote_transaction = SanitizedTransaction::try_create(
344 VersionedTransaction::from(vote_tx.clone()),
345 MessageHash::Compute,
346 None,
347 SimpleAddressLoader::Disabled,
348 &empty_key_set,
349 )
350 .unwrap();
351 assert!(vote_transaction.is_simple_vote_transaction());
352 }
353
354 {
355 let vote_transaction = SanitizedTransaction::try_create(
357 VersionedTransaction::from(vote_tx.clone()),
358 MessageHash::Compute,
359 Some(false),
360 SimpleAddressLoader::Disabled,
361 &empty_key_set,
362 )
363 .unwrap();
364 assert!(!vote_transaction.is_simple_vote_transaction());
365 }
366
367 vote_tx.signatures.push(Signature::default());
369 vote_tx.message.header.num_required_signatures = 3;
370 {
371 let vote_transaction = SanitizedTransaction::try_create(
372 VersionedTransaction::from(vote_tx.clone()),
373 MessageHash::Compute,
374 None,
375 SimpleAddressLoader::Disabled,
376 &empty_key_set,
377 )
378 .unwrap();
379 assert!(!vote_transaction.is_simple_vote_transaction());
380 }
381
382 {
383 let vote_transaction = SanitizedTransaction::try_create(
385 VersionedTransaction::from(vote_tx),
386 MessageHash::Compute,
387 Some(true),
388 SimpleAddressLoader::Disabled,
389 &empty_key_set,
390 )
391 .unwrap();
392 assert!(vote_transaction.is_simple_vote_transaction());
393 }
394 }
395
396 #[test]
397 fn test_try_new_from_fields() {
398 let legacy_message = SanitizedMessage::try_from_legacy_message(
399 legacy::Message {
400 header: MessageHeader {
401 num_required_signatures: 2,
402 num_readonly_signed_accounts: 1,
403 num_readonly_unsigned_accounts: 1,
404 },
405 account_keys: vec![
406 Address::new_unique(),
407 Address::new_unique(),
408 Address::new_unique(),
409 ],
410 ..legacy::Message::default()
411 },
412 &HashSet::default(),
413 )
414 .unwrap();
415
416 for is_simple_vote_tx in [false, true] {
417 assert!(SanitizedTransaction::try_new_from_fields(
419 legacy_message.clone(),
420 Hash::new_unique(),
421 is_simple_vote_tx,
422 vec![],
423 )
424 .is_err());
425 assert!(SanitizedTransaction::try_new_from_fields(
427 legacy_message.clone(),
428 Hash::new_unique(),
429 is_simple_vote_tx,
430 vec![
431 Signature::default(),
432 Signature::default(),
433 Signature::default()
434 ],
435 )
436 .is_err());
437 assert!(SanitizedTransaction::try_new_from_fields(
439 legacy_message.clone(),
440 Hash::new_unique(),
441 is_simple_vote_tx,
442 vec![Signature::default(), Signature::default()]
443 )
444 .is_ok());
445 }
446 }
447
448 #[test]
449 fn test_verify_v1_message_data_includes_prefix() {
450 let payer = Keypair::new();
451 let program_id = Address::new_unique();
452 let instruction = Instruction::new_with_bytes(
453 program_id,
454 &[1, 2, 3],
455 vec![AccountMeta::new(payer.pubkey(), true)],
456 );
457 let message = v1::Message::try_compile_with_config(
458 &payer.pubkey(),
459 &[instruction],
460 Hash::new_unique(),
461 v1::TransactionConfig::empty(),
462 )
463 .unwrap();
464 let versioned_tx =
465 VersionedTransaction::try_new(VersionedMessage::V1(message), &[&payer]).unwrap();
466
467 let sanitized = SanitizedTransaction::try_create(
468 versioned_tx,
469 MessageHash::Compute,
470 None,
471 SimpleAddressLoader::Disabled,
472 &HashSet::default(),
473 )
474 .unwrap();
475
476 let signed_bytes = sanitized.message_data();
478 assert_eq!(signed_bytes[0], solana_message::v1::V1_PREFIX);
479
480 sanitized.verify().unwrap();
481 }
482}