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