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 = "wincode")]
266 pub fn get_durable_nonce(&self) -> Option<&Address> {
267 self.message.get_durable_nonce()
268 }
269
270 #[cfg(feature = "verify")]
271 fn message_data(&self) -> Vec<u8> {
273 match &self.message {
274 SanitizedMessage::Legacy(legacy_message) => legacy_message.message.serialize(),
275 SanitizedMessage::V0(loaded_msg) => loaded_msg.message.serialize(),
276 SanitizedMessage::V1(cached_msg) => cached_msg.message.serialize(),
277 }
278 }
279
280 #[cfg(feature = "verify")]
281 pub fn verify(&self) -> TransactionResult<()> {
283 let message_bytes = self.message_data();
284 if self
285 .signatures
286 .iter()
287 .zip(self.message.account_keys().iter())
288 .map(|(signature, pubkey)| signature.verify(pubkey.as_ref(), &message_bytes))
289 .any(|verified| !verified)
290 {
291 Err(TransactionError::SignatureFailure)
292 } else {
293 Ok(())
294 }
295 }
296
297 pub fn validate_account_locks(
299 message: &SanitizedMessage,
300 tx_account_lock_limit: usize,
301 ) -> TransactionResult<()> {
302 if message.has_duplicates() {
303 Err(TransactionError::AccountLoadedTwice)
304 } else if message.account_keys().len() > tx_account_lock_limit {
305 Err(TransactionError::TooManyAccountLocks)
306 } else {
307 Ok(())
308 }
309 }
310
311 #[cfg(feature = "dev-context-only-utils")]
312 pub fn new_for_tests(
313 message: SanitizedMessage,
314 signatures: Vec<Signature>,
315 is_simple_vote_tx: bool,
316 ) -> SanitizedTransaction {
317 SanitizedTransaction {
318 message,
319 message_hash: Hash::new_unique(),
320 signatures,
321 is_simple_vote_tx,
322 }
323 }
324}
325
326#[cfg(test)]
327#[allow(clippy::arithmetic_side_effects)]
328mod tests {
329 use {
330 super::*,
331 alloc::vec,
332 solana_instruction::{AccountMeta, Instruction},
333 solana_keypair::Keypair,
334 solana_message::{MessageHeader, SimpleAddressLoader},
335 solana_signer::Signer,
336 solana_vote_interface::{instruction, state::Vote},
337 };
338
339 #[test]
340 fn test_try_create_simple_vote_tx() {
341 let bank_hash = Hash::default();
342 let block_hash = Hash::default();
343 let empty_key_set = HashSet::default();
344 let vote_keypair = Keypair::new();
345 let node_keypair = Keypair::new();
346 let auth_keypair = Keypair::new();
347 let votes = Vote::new(vec![1, 2, 3], bank_hash);
348 let vote_ix = instruction::vote(&vote_keypair.pubkey(), &auth_keypair.pubkey(), votes);
349 let mut vote_tx = Transaction::new_with_payer(&[vote_ix], Some(&node_keypair.pubkey()));
350 vote_tx.partial_sign(&[&node_keypair], block_hash);
351 vote_tx.partial_sign(&[&auth_keypair], block_hash);
352
353 {
355 let vote_transaction = SanitizedTransaction::try_create(
356 VersionedTransaction::from(vote_tx.clone()),
357 MessageHash::Compute,
358 None,
359 SimpleAddressLoader::Disabled,
360 &empty_key_set,
361 )
362 .unwrap();
363 assert!(vote_transaction.is_simple_vote_transaction());
364 }
365
366 {
367 let vote_transaction = SanitizedTransaction::try_create(
369 VersionedTransaction::from(vote_tx.clone()),
370 MessageHash::Compute,
371 Some(false),
372 SimpleAddressLoader::Disabled,
373 &empty_key_set,
374 )
375 .unwrap();
376 assert!(!vote_transaction.is_simple_vote_transaction());
377 }
378
379 vote_tx.signatures.push(Signature::default());
381 vote_tx.message.header.num_required_signatures = 3;
382 {
383 let vote_transaction = SanitizedTransaction::try_create(
384 VersionedTransaction::from(vote_tx.clone()),
385 MessageHash::Compute,
386 None,
387 SimpleAddressLoader::Disabled,
388 &empty_key_set,
389 )
390 .unwrap();
391 assert!(!vote_transaction.is_simple_vote_transaction());
392 }
393
394 {
395 let vote_transaction = SanitizedTransaction::try_create(
397 VersionedTransaction::from(vote_tx),
398 MessageHash::Compute,
399 Some(true),
400 SimpleAddressLoader::Disabled,
401 &empty_key_set,
402 )
403 .unwrap();
404 assert!(vote_transaction.is_simple_vote_transaction());
405 }
406 }
407
408 #[test]
409 fn test_try_new_from_fields() {
410 let legacy_message = SanitizedMessage::try_from_legacy_message(
411 legacy::Message {
412 header: MessageHeader {
413 num_required_signatures: 2,
414 num_readonly_signed_accounts: 1,
415 num_readonly_unsigned_accounts: 1,
416 },
417 account_keys: vec![
418 Address::new_unique(),
419 Address::new_unique(),
420 Address::new_unique(),
421 ],
422 ..legacy::Message::default()
423 },
424 &HashSet::default(),
425 )
426 .unwrap();
427
428 for is_simple_vote_tx in [false, true] {
429 assert!(SanitizedTransaction::try_new_from_fields(
431 legacy_message.clone(),
432 Hash::new_unique(),
433 is_simple_vote_tx,
434 vec![],
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![
443 Signature::default(),
444 Signature::default(),
445 Signature::default()
446 ],
447 )
448 .is_err());
449 assert!(SanitizedTransaction::try_new_from_fields(
451 legacy_message.clone(),
452 Hash::new_unique(),
453 is_simple_vote_tx,
454 vec![Signature::default(), Signature::default()]
455 )
456 .is_ok());
457 }
458 }
459
460 #[test]
461 fn test_verify_v1_message_data_includes_prefix() {
462 let payer = Keypair::new();
463 let program_id = Address::new_unique();
464 let instruction = Instruction::new_with_bytes(
465 program_id,
466 &[1, 2, 3],
467 vec![AccountMeta::new(payer.pubkey(), true)],
468 );
469 let message =
470 v1::Message::try_compile(&payer.pubkey(), &[instruction], Hash::new_unique()).unwrap();
471 let versioned_tx =
472 VersionedTransaction::try_new(VersionedMessage::V1(message), &[&payer]).unwrap();
473
474 let sanitized = SanitizedTransaction::try_create(
475 versioned_tx,
476 MessageHash::Compute,
477 None,
478 SimpleAddressLoader::Disabled,
479 &HashSet::default(),
480 )
481 .unwrap();
482
483 let signed_bytes = sanitized.message_data();
485 assert_eq!(signed_bytes[0], solana_message::v1::V1_PREFIX);
486
487 sanitized.verify().unwrap();
488 }
489}