1#![cfg(feature = "full")]
2
3pub use crate::message::{AddressLoader, SimpleAddressLoader};
4use {
5 super::SanitizedVersionedTransaction,
6 crate::{
7 hash::Hash,
8 message::{
9 legacy,
10 v0::{self, LoadedAddresses},
11 LegacyMessage, SanitizedMessage, VersionedMessage,
12 },
13 precompiles::verify_if_precompile,
14 pubkey::Pubkey,
15 reserved_account_keys::ReservedAccountKeys,
16 signature::Signature,
17 simple_vote_transaction_checker::is_simple_vote_transaction,
18 transaction::{Result, Transaction, VersionedTransaction},
19 },
20 solana_feature_set as feature_set,
21 solana_program::{instruction::InstructionError, message::SanitizedVersionedMessage},
22 solana_sanitize::Sanitize,
23 solana_transaction_error::TransactionError,
24 std::collections::HashSet,
25};
26
27pub const MAX_TX_ACCOUNT_LOCKS: usize = 128;
31
32#[derive(Debug, Clone, Eq, PartialEq)]
34pub struct SanitizedTransaction {
35 message: SanitizedMessage,
36 message_hash: Hash,
37 is_simple_vote_tx: bool,
38 signatures: Vec<Signature>,
39}
40
41#[derive(Debug, Clone, Default, Eq, PartialEq)]
43pub struct TransactionAccountLocks<'a> {
44 pub readonly: Vec<&'a Pubkey>,
46 pub writable: Vec<&'a Pubkey>,
48}
49
50pub enum MessageHash {
53 Precomputed(Hash),
54 Compute,
55}
56
57impl From<Hash> for MessageHash {
58 fn from(hash: Hash) -> Self {
59 Self::Precomputed(hash)
60 }
61}
62
63impl SanitizedTransaction {
64 pub fn try_new(
68 tx: SanitizedVersionedTransaction,
69 message_hash: Hash,
70 is_simple_vote_tx: bool,
71 address_loader: impl AddressLoader,
72 reserved_account_keys: &HashSet<Pubkey>,
73 ) -> Result<Self> {
74 let signatures = tx.signatures;
75 let SanitizedVersionedMessage { message } = tx.message;
76 let message = match message {
77 VersionedMessage::Legacy(message) => {
78 SanitizedMessage::Legacy(LegacyMessage::new(message, reserved_account_keys))
79 }
80 VersionedMessage::V0(message) => {
81 let loaded_addresses =
82 address_loader.load_addresses(&message.address_table_lookups)?;
83 SanitizedMessage::V0(v0::LoadedMessage::new(
84 message,
85 loaded_addresses,
86 reserved_account_keys,
87 ))
88 }
89 };
90
91 Ok(Self {
92 message,
93 message_hash,
94 is_simple_vote_tx,
95 signatures,
96 })
97 }
98
99 pub fn try_create(
103 tx: VersionedTransaction,
104 message_hash: impl Into<MessageHash>,
105 is_simple_vote_tx: Option<bool>,
106 address_loader: impl AddressLoader,
107 reserved_account_keys: &HashSet<Pubkey>,
108 ) -> Result<Self> {
109 let sanitized_versioned_tx = SanitizedVersionedTransaction::try_from(tx)?;
110 let is_simple_vote_tx = is_simple_vote_tx
111 .unwrap_or_else(|| is_simple_vote_transaction(&sanitized_versioned_tx));
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 pub fn try_from_legacy_transaction(
127 tx: Transaction,
128 reserved_account_keys: &HashSet<Pubkey>,
129 ) -> Result<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 pub fn from_transaction_for_tests(tx: Transaction) -> Self {
145 Self::try_from_legacy_transaction(tx, &ReservedAccountKeys::empty_key_set()).unwrap()
146 }
147
148 pub fn signature(&self) -> &Signature {
156 &self.signatures[0]
157 }
158
159 pub fn signatures(&self) -> &[Signature] {
161 &self.signatures
162 }
163
164 pub fn message(&self) -> &SanitizedMessage {
166 &self.message
167 }
168
169 pub fn message_hash(&self) -> &Hash {
171 &self.message_hash
172 }
173
174 pub fn is_simple_vote_transaction(&self) -> bool {
176 self.is_simple_vote_tx
177 }
178
179 pub fn to_versioned_transaction(&self) -> VersionedTransaction {
182 let signatures = self.signatures.clone();
183 match &self.message {
184 SanitizedMessage::V0(sanitized_msg) => VersionedTransaction {
185 signatures,
186 message: VersionedMessage::V0(v0::Message::clone(&sanitized_msg.message)),
187 },
188 SanitizedMessage::Legacy(legacy_message) => VersionedTransaction {
189 signatures,
190 message: VersionedMessage::Legacy(legacy::Message::clone(&legacy_message.message)),
191 },
192 }
193 }
194
195 pub fn get_account_locks(
197 &self,
198 tx_account_lock_limit: usize,
199 ) -> Result<TransactionAccountLocks> {
200 Self::validate_account_locks(self.message(), tx_account_lock_limit)?;
201 Ok(self.get_account_locks_unchecked())
202 }
203
204 pub fn get_account_locks_unchecked(&self) -> TransactionAccountLocks {
206 let message = &self.message;
207 let account_keys = message.account_keys();
208 let num_readonly_accounts = message.num_readonly_accounts();
209 let num_writable_accounts = account_keys.len().saturating_sub(num_readonly_accounts);
210
211 let mut account_locks = TransactionAccountLocks {
212 writable: Vec::with_capacity(num_writable_accounts),
213 readonly: Vec::with_capacity(num_readonly_accounts),
214 };
215
216 for (i, key) in account_keys.iter().enumerate() {
217 if message.is_writable(i) {
218 account_locks.writable.push(key);
219 } else {
220 account_locks.readonly.push(key);
221 }
222 }
223
224 account_locks
225 }
226
227 pub fn get_loaded_addresses(&self) -> LoadedAddresses {
229 match &self.message {
230 SanitizedMessage::Legacy(_) => LoadedAddresses::default(),
231 SanitizedMessage::V0(message) => LoadedAddresses::clone(&message.loaded_addresses),
232 }
233 }
234
235 pub fn get_durable_nonce(&self) -> Option<&Pubkey> {
237 self.message.get_durable_nonce()
238 }
239
240 fn message_data(&self) -> Vec<u8> {
242 match &self.message {
243 SanitizedMessage::Legacy(legacy_message) => legacy_message.message.serialize(),
244 SanitizedMessage::V0(loaded_msg) => loaded_msg.message.serialize(),
245 }
246 }
247
248 pub fn verify(&self) -> Result<()> {
250 let message_bytes = self.message_data();
251 if self
252 .signatures
253 .iter()
254 .zip(self.message.account_keys().iter())
255 .map(|(signature, pubkey)| signature.verify(pubkey.as_ref(), &message_bytes))
256 .any(|verified| !verified)
257 {
258 Err(TransactionError::SignatureFailure)
259 } else {
260 Ok(())
261 }
262 }
263
264 pub fn verify_precompiles(&self, feature_set: &feature_set::FeatureSet) -> Result<()> {
266 for (index, (program_id, instruction)) in
267 self.message.program_instructions_iter().enumerate()
268 {
269 verify_if_precompile(
270 program_id,
271 instruction,
272 self.message().instructions(),
273 feature_set,
274 )
275 .map_err(|err| {
276 TransactionError::InstructionError(
277 index as u8,
278 InstructionError::Custom(err as u32),
279 )
280 })?;
281 }
282 Ok(())
283 }
284
285 pub fn validate_account_locks(
287 message: &SanitizedMessage,
288 tx_account_lock_limit: usize,
289 ) -> Result<()> {
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 crate::{
320 reserved_account_keys::ReservedAccountKeys,
321 signer::{keypair::Keypair, Signer},
322 },
323 solana_program::vote::{self, state::Vote},
324 };
325
326 #[test]
327 fn test_try_create_simple_vote_tx() {
328 let bank_hash = Hash::default();
329 let block_hash = Hash::default();
330 let vote_keypair = Keypair::new();
331 let node_keypair = Keypair::new();
332 let auth_keypair = Keypair::new();
333 let votes = Vote::new(vec![1, 2, 3], bank_hash);
334 let vote_ix =
335 vote::instruction::vote(&vote_keypair.pubkey(), &auth_keypair.pubkey(), votes);
336 let mut vote_tx = Transaction::new_with_payer(&[vote_ix], Some(&node_keypair.pubkey()));
337 vote_tx.partial_sign(&[&node_keypair], block_hash);
338 vote_tx.partial_sign(&[&auth_keypair], block_hash);
339
340 {
342 let vote_transaction = SanitizedTransaction::try_create(
343 VersionedTransaction::from(vote_tx.clone()),
344 MessageHash::Compute,
345 None,
346 SimpleAddressLoader::Disabled,
347 &ReservedAccountKeys::empty_key_set(),
348 )
349 .unwrap();
350 assert!(vote_transaction.is_simple_vote_transaction());
351 }
352
353 {
354 let vote_transaction = SanitizedTransaction::try_create(
356 VersionedTransaction::from(vote_tx.clone()),
357 MessageHash::Compute,
358 Some(false),
359 SimpleAddressLoader::Disabled,
360 &ReservedAccountKeys::empty_key_set(),
361 )
362 .unwrap();
363 assert!(!vote_transaction.is_simple_vote_transaction());
364 }
365
366 vote_tx.signatures.push(Signature::default());
368 vote_tx.message.header.num_required_signatures = 3;
369 {
370 let vote_transaction = SanitizedTransaction::try_create(
371 VersionedTransaction::from(vote_tx.clone()),
372 MessageHash::Compute,
373 None,
374 SimpleAddressLoader::Disabled,
375 &ReservedAccountKeys::empty_key_set(),
376 )
377 .unwrap();
378 assert!(!vote_transaction.is_simple_vote_transaction());
379 }
380
381 {
382 let vote_transaction = SanitizedTransaction::try_create(
384 VersionedTransaction::from(vote_tx),
385 MessageHash::Compute,
386 Some(true),
387 SimpleAddressLoader::Disabled,
388 &ReservedAccountKeys::empty_key_set(),
389 )
390 .unwrap();
391 assert!(vote_transaction.is_simple_vote_transaction());
392 }
393 }
394}