solana_sdk/transaction/versioned/
mod.rs

1//! Defines a transaction which supports multiple versions of messages.
2
3#![cfg(feature = "full")]
4
5use {
6    crate::{
7        hash::Hash,
8        message::VersionedMessage,
9        sanitize::SanitizeError,
10        short_vec,
11        signature::Signature,
12        signer::SignerError,
13        signers::Signers,
14        transaction::{Result, Transaction, TransactionError},
15    },
16    serde::Serialize,
17    std::cmp::Ordering,
18};
19
20mod sanitized;
21
22pub use sanitized::*;
23use {
24    crate::program_utils::limited_deserialize,
25    solana_program::{
26        nonce::NONCED_TX_MARKER_IX_INDEX, system_instruction::SystemInstruction, system_program,
27    },
28};
29
30/// Type that serializes to the string "legacy"
31#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(rename_all = "camelCase")]
33pub enum Legacy {
34    Legacy,
35}
36
37#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(rename_all = "camelCase", untagged)]
39pub enum TransactionVersion {
40    Legacy(Legacy),
41    Number(u8),
42}
43
44impl TransactionVersion {
45    pub const LEGACY: Self = Self::Legacy(Legacy::Legacy);
46}
47
48// NOTE: Serialization-related changes must be paired with the direct read at sigverify.
49/// An atomic transaction
50#[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize, AbiExample)]
51pub struct VersionedTransaction {
52    /// List of signatures
53    #[serde(with = "short_vec")]
54    pub signatures: Vec<Signature>,
55    /// Message to sign.
56    pub message: VersionedMessage,
57}
58
59impl From<Transaction> for VersionedTransaction {
60    fn from(transaction: Transaction) -> Self {
61        Self {
62            signatures: transaction.signatures,
63            message: VersionedMessage::Legacy(transaction.message),
64        }
65    }
66}
67
68impl VersionedTransaction {
69    /// Signs a versioned message and if successful, returns a signed
70    /// transaction.
71    pub fn try_new<T: Signers>(
72        message: VersionedMessage,
73        keypairs: &T,
74    ) -> std::result::Result<Self, SignerError> {
75        let static_account_keys = message.static_account_keys();
76        if static_account_keys.len() < message.header().num_required_signatures as usize {
77            return Err(SignerError::InvalidInput("invalid message".to_string()));
78        }
79
80        let signer_keys = keypairs.try_pubkeys()?;
81        let expected_signer_keys =
82            &static_account_keys[0..message.header().num_required_signatures as usize];
83
84        match signer_keys.len().cmp(&expected_signer_keys.len()) {
85            Ordering::Greater => Err(SignerError::TooManySigners),
86            Ordering::Less => Err(SignerError::NotEnoughSigners),
87            Ordering::Equal => Ok(()),
88        }?;
89
90        let message_data = message.serialize();
91        let signature_indexes: Vec<usize> = expected_signer_keys
92            .iter()
93            .map(|signer_key| {
94                signer_keys
95                    .iter()
96                    .position(|key| key == signer_key)
97                    .ok_or(SignerError::KeypairPubkeyMismatch)
98            })
99            .collect::<std::result::Result<_, SignerError>>()?;
100
101        let unordered_signatures = keypairs.try_sign_message(&message_data)?;
102        let signatures: Vec<Signature> = signature_indexes
103            .into_iter()
104            .map(|index| {
105                unordered_signatures
106                    .get(index)
107                    .copied()
108                    .ok_or_else(|| SignerError::InvalidInput("invalid keypairs".to_string()))
109            })
110            .collect::<std::result::Result<_, SignerError>>()?;
111
112        Ok(Self {
113            signatures,
114            message,
115        })
116    }
117
118    pub fn sanitize(
119        &self,
120        require_static_program_ids: bool,
121    ) -> std::result::Result<(), SanitizeError> {
122        self.message.sanitize(require_static_program_ids)?;
123        self.sanitize_signatures()?;
124        Ok(())
125    }
126
127    pub(crate) fn sanitize_signatures(&self) -> std::result::Result<(), SanitizeError> {
128        let num_required_signatures = usize::from(self.message.header().num_required_signatures);
129        match num_required_signatures.cmp(&self.signatures.len()) {
130            Ordering::Greater => Err(SanitizeError::IndexOutOfBounds),
131            Ordering::Less => Err(SanitizeError::InvalidValue),
132            Ordering::Equal => Ok(()),
133        }?;
134
135        // Signatures are verified before message keys are loaded so all signers
136        // must correspond to static account keys.
137        if self.signatures.len() > self.message.static_account_keys().len() {
138            return Err(SanitizeError::IndexOutOfBounds);
139        }
140
141        Ok(())
142    }
143
144    /// Returns the version of the transaction
145    pub fn version(&self) -> TransactionVersion {
146        match self.message {
147            VersionedMessage::Legacy(_) => TransactionVersion::LEGACY,
148            VersionedMessage::V0(_) => TransactionVersion::Number(0),
149        }
150    }
151
152    /// Returns a legacy transaction if the transaction message is legacy.
153    pub fn into_legacy_transaction(self) -> Option<Transaction> {
154        match self.message {
155            VersionedMessage::Legacy(message) => Some(Transaction {
156                signatures: self.signatures,
157                message,
158            }),
159            _ => None,
160        }
161    }
162
163    /// Verify the transaction and hash its message
164    pub fn verify_and_hash_message(&self) -> Result<Hash> {
165        let message_bytes = self.message.serialize();
166        if !self
167            ._verify_with_results(&message_bytes)
168            .iter()
169            .all(|verify_result| *verify_result)
170        {
171            Err(TransactionError::SignatureFailure)
172        } else {
173            Ok(VersionedMessage::hash_raw_message(&message_bytes))
174        }
175    }
176
177    /// Verify the transaction and return a list of verification results
178    pub fn verify_with_results(&self) -> Vec<bool> {
179        let message_bytes = self.message.serialize();
180        self._verify_with_results(&message_bytes)
181    }
182
183    fn _verify_with_results(&self, message_bytes: &[u8]) -> Vec<bool> {
184        self.signatures
185            .iter()
186            .zip(self.message.static_account_keys().iter())
187            .map(|(signature, pubkey)| signature.verify(pubkey.as_ref(), message_bytes))
188            .collect()
189    }
190
191    /// Returns true if transaction begins with a valid advance nonce
192    /// instruction. Since dynamically loaded addresses can't have write locks
193    /// demoted without loading addresses, this shouldn't be used in the
194    /// runtime.
195    pub fn uses_durable_nonce(&self) -> bool {
196        let message = &self.message;
197        message
198            .instructions()
199            .get(NONCED_TX_MARKER_IX_INDEX as usize)
200            .filter(|instruction| {
201                // Is system program
202                matches!(
203                    message.static_account_keys().get(instruction.program_id_index as usize),
204                    Some(program_id) if system_program::check_id(program_id)
205                )
206                // Is a nonce advance instruction
207                && matches!(
208                    limited_deserialize(&instruction.data),
209                    Ok(SystemInstruction::AdvanceNonceAccount)
210                )
211                // Nonce account is writable
212                && matches!(
213                    instruction.accounts.first(),
214                    Some(index) if message.is_maybe_writable(*index as usize)
215                )
216            })
217            .is_some()
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use {
224        super::*,
225        crate::{
226            message::Message as LegacyMessage,
227            signer::{keypair::Keypair, Signer},
228            system_instruction, sysvar,
229        },
230        solana_program::{
231            instruction::{AccountMeta, Instruction},
232            pubkey::Pubkey,
233        },
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_ro_nonce_account() {
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 account_metas = vec![
340            AccountMeta::new_readonly(nonce_pubkey, false),
341            #[allow(deprecated)]
342            AccountMeta::new_readonly(sysvar::recent_blockhashes::id(), false),
343            AccountMeta::new_readonly(nonce_pubkey, true),
344        ];
345        let nonce_instruction = Instruction::new_with_bincode(
346            system_program::id(),
347            &system_instruction::SystemInstruction::AdvanceNonceAccount,
348            account_metas,
349        );
350        let tx = Transaction::new_signed_with_payer(
351            &[nonce_instruction],
352            Some(&from_pubkey),
353            &[&from_keypair, &nonce_keypair],
354            Hash::default(),
355        );
356        let tx = VersionedTransaction::from(tx);
357        assert!(!tx.uses_durable_nonce());
358    }
359
360    #[test]
361    fn tx_uses_nonce_wrong_first_nonce_ix_fail() {
362        let from_keypair = Keypair::new();
363        let from_pubkey = from_keypair.pubkey();
364        let nonce_keypair = Keypair::new();
365        let nonce_pubkey = nonce_keypair.pubkey();
366        let instructions = [
367            system_instruction::withdraw_nonce_account(
368                &nonce_pubkey,
369                &nonce_pubkey,
370                &from_pubkey,
371                42,
372            ),
373            system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
374        ];
375        let message = LegacyMessage::new(&instructions, Some(&nonce_pubkey));
376        let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default());
377        let tx = VersionedTransaction::from(tx);
378        assert!(!tx.uses_durable_nonce());
379    }
380}