1use {
2 crate::versioned::{sanitized::SanitizedVersionedTransaction, VersionedTransaction},
3 solana_hash::Hash,
4 solana_message::{
5 legacy,
6 v0::{self, LoadedAddresses},
7 AddressLoader, LegacyMessage, SanitizedMessage, SanitizedVersionedMessage,
8 VersionedMessage,
9 },
10 solana_pubkey::Pubkey,
11 solana_signature::Signature,
12 solana_transaction_error::{TransactionError, TransactionResult as Result},
13 std::collections::HashSet,
14};
15#[cfg(feature = "blake3")]
16use {crate::Transaction, solana_sanitize::Sanitize};
17
18pub const MAX_TX_ACCOUNT_LOCKS: usize = 128;
22
23#[derive(Debug, Clone, Eq, PartialEq)]
25pub struct SanitizedTransaction {
26 message: SanitizedMessage,
27 message_hash: Hash,
28 is_simple_vote_tx: bool,
29 signatures: Vec<Signature>,
30}
31
32#[derive(Debug, Clone, Default, Eq, PartialEq)]
34pub struct TransactionAccountLocks<'a> {
35 pub readonly: Vec<&'a Pubkey>,
37 pub writable: Vec<&'a Pubkey>,
39}
40
41pub enum MessageHash {
44 Precomputed(Hash),
45 Compute,
46}
47
48impl From<Hash> for MessageHash {
49 fn from(hash: Hash) -> Self {
50 Self::Precomputed(hash)
51 }
52}
53
54impl SanitizedTransaction {
55 pub fn try_new(
59 tx: SanitizedVersionedTransaction,
60 message_hash: Hash,
61 is_simple_vote_tx: bool,
62 address_loader: impl AddressLoader,
63 reserved_account_keys: &HashSet<Pubkey>,
64 ) -> Result<Self> {
65 let signatures = tx.signatures;
66 let SanitizedVersionedMessage { message } = tx.message;
67 let message = match message {
68 VersionedMessage::Legacy(message) => {
69 SanitizedMessage::Legacy(LegacyMessage::new(message, reserved_account_keys))
70 }
71 VersionedMessage::V0(message) => {
72 let loaded_addresses =
73 address_loader.load_addresses(&message.address_table_lookups)?;
74 SanitizedMessage::V0(v0::LoadedMessage::new(
75 message,
76 loaded_addresses,
77 reserved_account_keys,
78 ))
79 }
80 };
81
82 Ok(Self {
83 message,
84 message_hash,
85 is_simple_vote_tx,
86 signatures,
87 })
88 }
89
90 #[cfg(feature = "blake3")]
91 pub fn try_create(
95 tx: VersionedTransaction,
96 message_hash: impl Into<MessageHash>,
97 is_simple_vote_tx: Option<bool>,
98 address_loader: impl AddressLoader,
99 reserved_account_keys: &HashSet<Pubkey>,
100 ) -> Result<Self> {
101 let sanitized_versioned_tx = SanitizedVersionedTransaction::try_from(tx)?;
102 let is_simple_vote_tx = is_simple_vote_tx.unwrap_or_else(|| {
103 crate::simple_vote_transaction_checker::is_simple_vote_transaction(
104 &sanitized_versioned_tx,
105 )
106 });
107 let message_hash = match message_hash.into() {
108 MessageHash::Compute => sanitized_versioned_tx.message.message.hash(),
109 MessageHash::Precomputed(hash) => hash,
110 };
111 Self::try_new(
112 sanitized_versioned_tx,
113 message_hash,
114 is_simple_vote_tx,
115 address_loader,
116 reserved_account_keys,
117 )
118 }
119
120 #[cfg(feature = "blake3")]
122 pub fn try_from_legacy_transaction(
123 tx: Transaction,
124 reserved_account_keys: &HashSet<Pubkey>,
125 ) -> Result<Self> {
126 tx.sanitize()?;
127
128 Ok(Self {
129 message_hash: tx.message.hash(),
130 message: SanitizedMessage::Legacy(LegacyMessage::new(
131 tx.message,
132 reserved_account_keys,
133 )),
134 is_simple_vote_tx: false,
135 signatures: tx.signatures,
136 })
137 }
138
139 #[cfg(feature = "blake3")]
141 pub fn from_transaction_for_tests(tx: Transaction) -> Self {
142 let empty_key_set = HashSet::default();
143 Self::try_from_legacy_transaction(tx, &empty_key_set).unwrap()
144 }
145
146 pub fn try_new_from_fields(
149 message: SanitizedMessage,
150 message_hash: Hash,
151 is_simple_vote_tx: bool,
152 signatures: Vec<Signature>,
153 ) -> Result<Self> {
154 VersionedTransaction::sanitize_signatures_inner(
155 usize::from(message.header().num_required_signatures),
156 message.static_account_keys().len(),
157 signatures.len(),
158 )?;
159
160 Ok(Self {
161 message,
162 message_hash,
163 signatures,
164 is_simple_vote_tx,
165 })
166 }
167
168 pub fn signature(&self) -> &Signature {
176 &self.signatures[0]
177 }
178
179 pub fn signatures(&self) -> &[Signature] {
181 &self.signatures
182 }
183
184 pub fn message(&self) -> &SanitizedMessage {
186 &self.message
187 }
188
189 pub fn message_hash(&self) -> &Hash {
191 &self.message_hash
192 }
193
194 pub fn is_simple_vote_transaction(&self) -> bool {
196 self.is_simple_vote_tx
197 }
198
199 pub fn to_versioned_transaction(&self) -> VersionedTransaction {
202 let signatures = self.signatures.clone();
203 match &self.message {
204 SanitizedMessage::V0(sanitized_msg) => VersionedTransaction {
205 signatures,
206 message: VersionedMessage::V0(v0::Message::clone(&sanitized_msg.message)),
207 },
208 SanitizedMessage::Legacy(legacy_message) => VersionedTransaction {
209 signatures,
210 message: VersionedMessage::Legacy(legacy::Message::clone(&legacy_message.message)),
211 },
212 }
213 }
214
215 pub fn get_account_locks(
217 &self,
218 tx_account_lock_limit: usize,
219 ) -> Result<TransactionAccountLocks<'_>> {
220 Self::validate_account_locks(self.message(), tx_account_lock_limit)?;
221 Ok(self.get_account_locks_unchecked())
222 }
223
224 pub fn get_account_locks_unchecked(&self) -> TransactionAccountLocks<'_> {
226 let message = &self.message;
227 let account_keys = message.account_keys();
228 let num_readonly_accounts = message.num_readonly_accounts();
229 let num_writable_accounts = account_keys.len().saturating_sub(num_readonly_accounts);
230
231 let mut account_locks = TransactionAccountLocks {
232 writable: Vec::with_capacity(num_writable_accounts),
233 readonly: Vec::with_capacity(num_readonly_accounts),
234 };
235
236 for (i, key) in account_keys.iter().enumerate() {
237 if message.is_writable(i) {
238 account_locks.writable.push(key);
239 } else {
240 account_locks.readonly.push(key);
241 }
242 }
243
244 account_locks
245 }
246
247 pub fn get_loaded_addresses(&self) -> LoadedAddresses {
249 match &self.message {
250 SanitizedMessage::Legacy(_) => LoadedAddresses::default(),
251 SanitizedMessage::V0(message) => LoadedAddresses::clone(&message.loaded_addresses),
252 }
253 }
254
255 #[cfg(feature = "bincode")]
257 pub fn get_durable_nonce(&self) -> Option<&Pubkey> {
258 self.message.get_durable_nonce()
259 }
260
261 #[cfg(feature = "verify")]
262 fn message_data(&self) -> Vec<u8> {
264 match &self.message {
265 SanitizedMessage::Legacy(legacy_message) => legacy_message.message.serialize(),
266 SanitizedMessage::V0(loaded_msg) => loaded_msg.message.serialize(),
267 }
268 }
269
270 #[cfg(feature = "verify")]
271 pub fn verify(&self) -> Result<()> {
273 let message_bytes = self.message_data();
274 if self
275 .signatures
276 .iter()
277 .zip(self.message.account_keys().iter())
278 .map(|(signature, pubkey)| signature.verify(pubkey.as_ref(), &message_bytes))
279 .any(|verified| !verified)
280 {
281 Err(TransactionError::SignatureFailure)
282 } else {
283 Ok(())
284 }
285 }
286
287 pub fn validate_account_locks(
289 message: &SanitizedMessage,
290 tx_account_lock_limit: usize,
291 ) -> Result<()> {
292 if message.has_duplicates() {
293 Err(TransactionError::AccountLoadedTwice)
294 } else if message.account_keys().len() > tx_account_lock_limit {
295 Err(TransactionError::TooManyAccountLocks)
296 } else {
297 Ok(())
298 }
299 }
300
301 #[cfg(feature = "dev-context-only-utils")]
302 pub fn new_for_tests(
303 message: SanitizedMessage,
304 signatures: Vec<Signature>,
305 is_simple_vote_tx: bool,
306 ) -> SanitizedTransaction {
307 SanitizedTransaction {
308 message,
309 message_hash: Hash::new_unique(),
310 signatures,
311 is_simple_vote_tx,
312 }
313 }
314}
315
316#[cfg(test)]
317#[allow(clippy::arithmetic_side_effects)]
318mod tests {
319 use {
320 super::*,
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 Pubkey::new_unique(),
407 Pubkey::new_unique(),
408 Pubkey::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}