solana_sdk/transaction/
sanitized.rs

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        sanitize::Sanitize,
16        signature::Signature,
17        solana_sdk::feature_set,
18        transaction::{Result, Transaction, TransactionError, VersionedTransaction},
19    },
20    solana_program::message::SanitizedVersionedMessage,
21    std::sync::Arc,
22};
23
24/// Maximum number of accounts that a transaction may lock.
25/// 128 was chosen because it is the minimum number of accounts
26/// needed for the Neon EVM implementation.
27pub const MAX_TX_ACCOUNT_LOCKS: usize = 128;
28
29/// Sanitized transaction and the hash of its message
30#[derive(Debug, Clone)]
31pub struct SanitizedTransaction {
32    message: SanitizedMessage,
33    message_hash: Hash,
34    is_simple_vote_tx: bool,
35    signatures: Vec<Signature>,
36}
37
38/// Set of accounts that must be locked for safe transaction processing
39#[derive(Debug, Clone, Default)]
40pub struct TransactionAccountLocks<'a> {
41    /// List of readonly account key locks
42    pub readonly: Vec<&'a Pubkey>,
43    /// List of writable account key locks
44    pub writable: Vec<&'a Pubkey>,
45}
46
47/// Type that represents whether the transaction message has been precomputed or
48/// not.
49pub enum MessageHash {
50    Precomputed(Hash),
51    Compute,
52}
53
54impl From<Hash> for MessageHash {
55    fn from(hash: Hash) -> Self {
56        Self::Precomputed(hash)
57    }
58}
59
60impl SanitizedTransaction {
61    /// Create a sanitized transaction from a sanitized versioned transaction.
62    /// If the input transaction uses address tables, attempt to lookup the
63    /// address for each table index.
64    pub fn try_new(
65        tx: SanitizedVersionedTransaction,
66        message_hash: Hash,
67        is_simple_vote_tx: bool,
68        address_loader: impl AddressLoader,
69    ) -> Result<Self> {
70        let signatures = tx.signatures;
71        let SanitizedVersionedMessage { message } = tx.message;
72        let message = match message {
73            VersionedMessage::Legacy(message) => {
74                SanitizedMessage::Legacy(LegacyMessage::new(message))
75            }
76            VersionedMessage::V0(message) => {
77                let loaded_addresses =
78                    address_loader.load_addresses(&message.address_table_lookups)?;
79                SanitizedMessage::V0(v0::LoadedMessage::new(message, loaded_addresses))
80            }
81        };
82
83        Ok(Self {
84            message,
85            message_hash,
86            is_simple_vote_tx,
87            signatures,
88        })
89    }
90
91    /// Create a sanitized transaction from an un-sanitized versioned
92    /// transaction.  If the input transaction uses address tables, attempt to
93    /// lookup the address for each table index.
94    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        require_static_program_ids: bool,
100    ) -> Result<Self> {
101        tx.sanitize(require_static_program_ids)?;
102
103        let message_hash = match message_hash.into() {
104            MessageHash::Compute => tx.message.hash(),
105            MessageHash::Precomputed(hash) => hash,
106        };
107
108        let signatures = tx.signatures;
109        let message = match tx.message {
110            VersionedMessage::Legacy(message) => {
111                SanitizedMessage::Legacy(LegacyMessage::new(message))
112            }
113            VersionedMessage::V0(message) => {
114                let loaded_addresses =
115                    address_loader.load_addresses(&message.address_table_lookups)?;
116                SanitizedMessage::V0(v0::LoadedMessage::new(message, loaded_addresses))
117            }
118        };
119
120        let is_simple_vote_tx = is_simple_vote_tx.unwrap_or_else(|| {
121            // TODO: Move to `vote_parser` runtime module
122            let mut ix_iter = message.program_instructions_iter();
123            ix_iter.next().map(|(program_id, _ix)| program_id) == Some(&crate::vote::program::id())
124        });
125
126        Ok(Self {
127            message,
128            message_hash,
129            is_simple_vote_tx,
130            signatures,
131        })
132    }
133
134    pub fn try_from_legacy_transaction(tx: Transaction) -> Result<Self> {
135        tx.sanitize()?;
136
137        Ok(Self {
138            message_hash: tx.message.hash(),
139            message: SanitizedMessage::Legacy(LegacyMessage::new(tx.message)),
140            is_simple_vote_tx: false,
141            signatures: tx.signatures,
142        })
143    }
144
145    /// Create a sanitized transaction from a legacy transaction. Used for tests only.
146    pub fn from_transaction_for_tests(tx: Transaction) -> Self {
147        Self::try_from_legacy_transaction(tx).unwrap()
148    }
149
150    /// Return the first signature for this transaction.
151    ///
152    /// Notes:
153    ///
154    /// Sanitized transactions must have at least one signature because the
155    /// number of signatures must be greater than or equal to the message header
156    /// value `num_required_signatures` which must be greater than 0 itself.
157    pub fn signature(&self) -> &Signature {
158        &self.signatures[0]
159    }
160
161    /// Return the list of signatures for this transaction
162    pub fn signatures(&self) -> &[Signature] {
163        &self.signatures
164    }
165
166    /// Return the signed message
167    pub fn message(&self) -> &SanitizedMessage {
168        &self.message
169    }
170
171    /// Return the hash of the signed message
172    pub fn message_hash(&self) -> &Hash {
173        &self.message_hash
174    }
175
176    /// Returns true if this transaction is a simple vote
177    pub fn is_simple_vote_transaction(&self) -> bool {
178        self.is_simple_vote_tx
179    }
180
181    /// Convert this sanitized transaction into a versioned transaction for
182    /// recording in the ledger.
183    pub fn to_versioned_transaction(&self) -> VersionedTransaction {
184        let signatures = self.signatures.clone();
185        match &self.message {
186            SanitizedMessage::V0(sanitized_msg) => VersionedTransaction {
187                signatures,
188                message: VersionedMessage::V0(v0::Message::clone(&sanitized_msg.message)),
189            },
190            SanitizedMessage::Legacy(legacy_message) => VersionedTransaction {
191                signatures,
192                message: VersionedMessage::Legacy(legacy::Message::clone(&legacy_message.message)),
193            },
194        }
195    }
196
197    /// Validate and return the account keys locked by this transaction
198    pub fn get_account_locks(
199        &self,
200        tx_account_lock_limit: usize,
201    ) -> Result<TransactionAccountLocks> {
202        if self.message.has_duplicates() {
203            Err(TransactionError::AccountLoadedTwice)
204        } else if self.message.account_keys().len() > tx_account_lock_limit {
205            Err(TransactionError::TooManyAccountLocks)
206        } else {
207            Ok(self.get_account_locks_unchecked())
208        }
209    }
210
211    /// Return the list of accounts that must be locked during processing this transaction.
212    pub fn get_account_locks_unchecked(&self) -> TransactionAccountLocks {
213        let message = &self.message;
214        let account_keys = message.account_keys();
215        let num_readonly_accounts = message.num_readonly_accounts();
216        let num_writable_accounts = account_keys.len().saturating_sub(num_readonly_accounts);
217
218        let mut account_locks = TransactionAccountLocks {
219            writable: Vec::with_capacity(num_writable_accounts),
220            readonly: Vec::with_capacity(num_readonly_accounts),
221        };
222
223        for (i, key) in account_keys.iter().enumerate() {
224            if message.is_writable(i) {
225                account_locks.writable.push(key);
226            } else {
227                account_locks.readonly.push(key);
228            }
229        }
230
231        account_locks
232    }
233
234    /// Return the list of addresses loaded from on-chain address lookup tables
235    pub fn get_loaded_addresses(&self) -> LoadedAddresses {
236        match &self.message {
237            SanitizedMessage::Legacy(_) => LoadedAddresses::default(),
238            SanitizedMessage::V0(message) => LoadedAddresses::clone(&message.loaded_addresses),
239        }
240    }
241
242    /// If the transaction uses a durable nonce, return the pubkey of the nonce account
243    pub fn get_durable_nonce(&self) -> Option<&Pubkey> {
244        self.message.get_durable_nonce()
245    }
246
247    /// Return the serialized message data to sign.
248    fn message_data(&self) -> Vec<u8> {
249        match &self.message {
250            SanitizedMessage::Legacy(legacy_message) => legacy_message.message.serialize(),
251            SanitizedMessage::V0(loaded_msg) => loaded_msg.message.serialize(),
252        }
253    }
254
255    /// Verify the transaction signatures
256    pub fn verify(&self) -> Result<()> {
257        let message_bytes = self.message_data();
258        if self
259            .signatures
260            .iter()
261            .zip(self.message.account_keys().iter())
262            .map(|(signature, pubkey)| signature.verify(pubkey.as_ref(), &message_bytes))
263            .any(|verified| !verified)
264        {
265            Err(TransactionError::SignatureFailure)
266        } else {
267            Ok(())
268        }
269    }
270
271    /// Verify the precompiled programs in this transaction
272    pub fn verify_precompiles(&self, feature_set: &Arc<feature_set::FeatureSet>) -> Result<()> {
273        for (program_id, instruction) in self.message.program_instructions_iter() {
274            verify_if_precompile(
275                program_id,
276                instruction,
277                self.message().instructions(),
278                feature_set,
279            )
280            .map_err(|_| TransactionError::InvalidAccountIndex)?;
281        }
282        Ok(())
283    }
284}