Skip to main content

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