solana_transaction/versioned/
mod.rs

1//! Defines a transaction which supports multiple versions of messages.
2
3#[cfg(feature = "bincode")]
4use solana_signer::{signers::Signers, SignerError};
5use {
6    crate::Transaction,
7    solana_message::{inline_nonce::is_advance_nonce_instruction_data, VersionedMessage},
8    solana_sanitize::SanitizeError,
9    solana_sdk_ids::system_program,
10    solana_signature::Signature,
11    std::cmp::Ordering,
12};
13#[cfg(feature = "serde")]
14use {
15    serde_derive::{Deserialize, Serialize},
16    solana_short_vec as short_vec,
17};
18
19pub mod sanitized;
20
21/// Type that serializes to the string "legacy"
22#[cfg_attr(
23    feature = "serde",
24    derive(Deserialize, Serialize),
25    serde(rename_all = "camelCase")
26)]
27#[derive(Clone, Debug, PartialEq, Eq)]
28pub enum Legacy {
29    Legacy,
30}
31
32#[cfg_attr(
33    feature = "serde",
34    derive(Deserialize, Serialize),
35    serde(rename_all = "camelCase", untagged)
36)]
37#[derive(Clone, Debug, PartialEq, Eq)]
38pub enum TransactionVersion {
39    Legacy(Legacy),
40    Number(u8),
41}
42
43impl TransactionVersion {
44    pub const LEGACY: Self = Self::Legacy(Legacy::Legacy);
45}
46
47// NOTE: Serialization-related changes must be paired with the direct read at sigverify.
48/// An atomic transaction
49#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
50#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
51#[derive(Debug, PartialEq, Default, Eq, Clone)]
52pub struct VersionedTransaction {
53    /// List of signatures
54    #[cfg_attr(feature = "serde", serde(with = "short_vec"))]
55    pub signatures: Vec<Signature>,
56    /// Message to sign.
57    pub message: VersionedMessage,
58}
59
60impl From<Transaction> for VersionedTransaction {
61    fn from(transaction: Transaction) -> Self {
62        Self {
63            signatures: transaction.signatures,
64            message: VersionedMessage::Legacy(transaction.message),
65        }
66    }
67}
68
69impl VersionedTransaction {
70    /// Signs a versioned message and if successful, returns a signed
71    /// transaction.
72    #[cfg(feature = "bincode")]
73    pub fn try_new<T: Signers + ?Sized>(
74        message: VersionedMessage,
75        keypairs: &T,
76    ) -> std::result::Result<Self, SignerError> {
77        let static_account_keys = message.static_account_keys();
78        if static_account_keys.len() < message.header().num_required_signatures as usize {
79            return Err(SignerError::InvalidInput("invalid message".to_string()));
80        }
81
82        let signer_keys = keypairs.try_pubkeys()?;
83        let expected_signer_keys =
84            &static_account_keys[0..message.header().num_required_signatures as usize];
85
86        match signer_keys.len().cmp(&expected_signer_keys.len()) {
87            Ordering::Greater => Err(SignerError::TooManySigners),
88            Ordering::Less => Err(SignerError::NotEnoughSigners),
89            Ordering::Equal => Ok(()),
90        }?;
91
92        let message_data = message.serialize();
93        let signature_indexes: Vec<usize> = expected_signer_keys
94            .iter()
95            .map(|signer_key| {
96                signer_keys
97                    .iter()
98                    .position(|key| key == signer_key)
99                    .ok_or(SignerError::KeypairPubkeyMismatch)
100            })
101            .collect::<std::result::Result<_, SignerError>>()?;
102
103        let unordered_signatures = keypairs.try_sign_message(&message_data)?;
104        let signatures: Vec<Signature> = signature_indexes
105            .into_iter()
106            .map(|index| {
107                unordered_signatures
108                    .get(index)
109                    .copied()
110                    .ok_or_else(|| SignerError::InvalidInput("invalid keypairs".to_string()))
111            })
112            .collect::<std::result::Result<_, SignerError>>()?;
113
114        Ok(Self {
115            signatures,
116            message,
117        })
118    }
119
120    pub fn sanitize(&self) -> std::result::Result<(), SanitizeError> {
121        self.message.sanitize()?;
122        self.sanitize_signatures()?;
123        Ok(())
124    }
125
126    pub(crate) fn sanitize_signatures(&self) -> std::result::Result<(), SanitizeError> {
127        Self::sanitize_signatures_inner(
128            usize::from(self.message.header().num_required_signatures),
129            self.message.static_account_keys().len(),
130            self.signatures.len(),
131        )
132    }
133
134    pub(crate) fn sanitize_signatures_inner(
135        num_required_signatures: usize,
136        num_static_account_keys: usize,
137        num_signatures: usize,
138    ) -> std::result::Result<(), SanitizeError> {
139        match num_required_signatures.cmp(&num_signatures) {
140            Ordering::Greater => Err(SanitizeError::IndexOutOfBounds),
141            Ordering::Less => Err(SanitizeError::InvalidValue),
142            Ordering::Equal => Ok(()),
143        }?;
144
145        // Signatures are verified before message keys are loaded so all signers
146        // must correspond to static account keys.
147        if num_signatures > num_static_account_keys {
148            return Err(SanitizeError::IndexOutOfBounds);
149        }
150
151        Ok(())
152    }
153
154    /// Returns the version of the transaction
155    pub fn version(&self) -> TransactionVersion {
156        match self.message {
157            VersionedMessage::Legacy(_) => TransactionVersion::LEGACY,
158            VersionedMessage::V0(_) => TransactionVersion::Number(0),
159        }
160    }
161
162    /// Returns a legacy transaction if the transaction message is legacy.
163    pub fn into_legacy_transaction(self) -> Option<Transaction> {
164        match self.message {
165            VersionedMessage::Legacy(message) => Some(Transaction {
166                signatures: self.signatures,
167                message,
168            }),
169            _ => None,
170        }
171    }
172
173    #[cfg(feature = "verify")]
174    /// Verify the transaction and hash its message
175    pub fn verify_and_hash_message(
176        &self,
177    ) -> solana_transaction_error::TransactionResult<solana_hash::Hash> {
178        let message_bytes = self.message.serialize();
179        if !self
180            ._verify_with_results(&message_bytes)
181            .iter()
182            .all(|verify_result| *verify_result)
183        {
184            Err(solana_transaction_error::TransactionError::SignatureFailure)
185        } else {
186            Ok(VersionedMessage::hash_raw_message(&message_bytes))
187        }
188    }
189
190    #[cfg(feature = "verify")]
191    /// Verify the transaction and return a list of verification results
192    pub fn verify_with_results(&self) -> Vec<bool> {
193        let message_bytes = self.message.serialize();
194        self._verify_with_results(&message_bytes)
195    }
196
197    #[cfg(feature = "verify")]
198    fn _verify_with_results(&self, message_bytes: &[u8]) -> Vec<bool> {
199        self.signatures
200            .iter()
201            .zip(self.message.static_account_keys().iter())
202            .map(|(signature, pubkey)| signature.verify(pubkey.as_ref(), message_bytes))
203            .collect()
204    }
205
206    /// Returns true if transaction begins with an advance nonce instruction.
207    pub fn uses_durable_nonce(&self) -> bool {
208        let message = &self.message;
209        message
210            .instructions()
211            .get(crate::NONCED_TX_MARKER_IX_INDEX as usize)
212            .filter(|instruction| {
213                // Is system program
214                matches!(
215                    message.static_account_keys().get(instruction.program_id_index as usize),
216                    Some(program_id) if system_program::check_id(program_id)
217                ) && is_advance_nonce_instruction_data(&instruction.data)
218            })
219            .is_some()
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use {
226        super::*,
227        solana_hash::Hash,
228        solana_instruction::{AccountMeta, Instruction},
229        solana_keypair::Keypair,
230        solana_message::Message as LegacyMessage,
231        solana_pubkey::Pubkey,
232        solana_signer::Signer,
233        solana_system_interface::instruction as system_instruction,
234    };
235
236    #[test]
237    fn test_try_new() {
238        let keypair0 = Keypair::new();
239        let keypair1 = Keypair::new();
240        let keypair2 = Keypair::new();
241
242        let message = VersionedMessage::Legacy(LegacyMessage::new(
243            &[Instruction::new_with_bytes(
244                Pubkey::new_unique(),
245                &[],
246                vec![
247                    AccountMeta::new_readonly(keypair1.pubkey(), true),
248                    AccountMeta::new_readonly(keypair2.pubkey(), false),
249                ],
250            )],
251            Some(&keypair0.pubkey()),
252        ));
253
254        assert_eq!(
255            VersionedTransaction::try_new(message.clone(), &[&keypair0]),
256            Err(SignerError::NotEnoughSigners)
257        );
258
259        assert_eq!(
260            VersionedTransaction::try_new(message.clone(), &[&keypair0, &keypair0]),
261            Err(SignerError::KeypairPubkeyMismatch)
262        );
263
264        assert_eq!(
265            VersionedTransaction::try_new(message.clone(), &[&keypair1, &keypair2]),
266            Err(SignerError::KeypairPubkeyMismatch)
267        );
268
269        match VersionedTransaction::try_new(message.clone(), &[&keypair0, &keypair1]) {
270            Ok(tx) => assert_eq!(tx.verify_with_results(), vec![true; 2]),
271            Err(err) => assert_eq!(Some(err), None),
272        }
273
274        match VersionedTransaction::try_new(message, &[&keypair1, &keypair0]) {
275            Ok(tx) => assert_eq!(tx.verify_with_results(), vec![true; 2]),
276            Err(err) => assert_eq!(Some(err), None),
277        }
278    }
279
280    fn nonced_transfer_tx() -> (Pubkey, Pubkey, VersionedTransaction) {
281        let from_keypair = Keypair::new();
282        let from_pubkey = from_keypair.pubkey();
283        let nonce_keypair = Keypair::new();
284        let nonce_pubkey = nonce_keypair.pubkey();
285        let instructions = [
286            system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
287            system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
288        ];
289        let message = LegacyMessage::new(&instructions, Some(&nonce_pubkey));
290        let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default());
291        (from_pubkey, nonce_pubkey, tx.into())
292    }
293
294    #[test]
295    fn tx_uses_nonce_ok() {
296        let (_, _, tx) = nonced_transfer_tx();
297        assert!(tx.uses_durable_nonce());
298    }
299
300    #[test]
301    fn tx_uses_nonce_empty_ix_fail() {
302        assert!(!VersionedTransaction::default().uses_durable_nonce());
303    }
304
305    #[test]
306    fn tx_uses_nonce_bad_prog_id_idx_fail() {
307        let (_, _, mut tx) = nonced_transfer_tx();
308        match &mut tx.message {
309            VersionedMessage::Legacy(message) => {
310                message.instructions.get_mut(0).unwrap().program_id_index = 255u8;
311            }
312            VersionedMessage::V0(_) => unreachable!(),
313        };
314        assert!(!tx.uses_durable_nonce());
315    }
316
317    #[test]
318    fn tx_uses_nonce_first_prog_id_not_nonce_fail() {
319        let from_keypair = Keypair::new();
320        let from_pubkey = from_keypair.pubkey();
321        let nonce_keypair = Keypair::new();
322        let nonce_pubkey = nonce_keypair.pubkey();
323        let instructions = [
324            system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
325            system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
326        ];
327        let message = LegacyMessage::new(&instructions, Some(&from_pubkey));
328        let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default());
329        let tx = VersionedTransaction::from(tx);
330        assert!(!tx.uses_durable_nonce());
331    }
332
333    #[test]
334    fn tx_uses_nonce_wrong_first_nonce_ix_fail() {
335        let from_keypair = Keypair::new();
336        let from_pubkey = from_keypair.pubkey();
337        let nonce_keypair = Keypair::new();
338        let nonce_pubkey = nonce_keypair.pubkey();
339        let instructions = [
340            system_instruction::withdraw_nonce_account(
341                &nonce_pubkey,
342                &nonce_pubkey,
343                &from_pubkey,
344                42,
345            ),
346            system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
347        ];
348        let message = LegacyMessage::new(&instructions, Some(&nonce_pubkey));
349        let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default());
350        let tx = VersionedTransaction::from(tx);
351        assert!(!tx.uses_durable_nonce());
352    }
353
354    #[test]
355    fn test_sanitize_signatures_inner() {
356        assert_eq!(
357            VersionedTransaction::sanitize_signatures_inner(1, 1, 0),
358            Err(SanitizeError::IndexOutOfBounds)
359        );
360        assert_eq!(
361            VersionedTransaction::sanitize_signatures_inner(1, 1, 2),
362            Err(SanitizeError::InvalidValue)
363        );
364        assert_eq!(
365            VersionedTransaction::sanitize_signatures_inner(2, 1, 2),
366            Err(SanitizeError::IndexOutOfBounds)
367        );
368        assert_eq!(
369            VersionedTransaction::sanitize_signatures_inner(1, 1, 1),
370            Ok(())
371        );
372    }
373}