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        signature::Signature,
10        signer::SignerError,
11        signers::Signers,
12        transaction::{Result, Transaction},
13    },
14    serde::Serialize,
15    solana_sanitize::SanitizeError,
16    solana_short_vec as short_vec,
17    solana_transaction_error::TransactionError,
18    std::cmp::Ordering,
19};
20
21mod sanitized;
22
23pub use sanitized::*;
24use {
25    crate::program_utils::limited_deserialize,
26    solana_program::{
27        nonce::NONCED_TX_MARKER_IX_INDEX, system_instruction::SystemInstruction, system_program,
28    },
29};
30
31/// Type that serializes to the string "legacy"
32#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
33#[serde(rename_all = "camelCase")]
34pub enum Legacy {
35    Legacy,
36}
37
38#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
39#[serde(rename_all = "camelCase", untagged)]
40pub enum TransactionVersion {
41    Legacy(Legacy),
42    Number(u8),
43}
44
45impl TransactionVersion {
46    pub const LEGACY: Self = Self::Legacy(Legacy::Legacy);
47}
48
49// NOTE: Serialization-related changes must be paired with the direct read at sigverify.
50/// An atomic transaction
51#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
52#[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize)]
53pub struct VersionedTransaction {
54    /// List of signatures
55    #[serde(with = "short_vec")]
56    pub signatures: Vec<Signature>,
57    /// Message to sign.
58    pub message: VersionedMessage,
59}
60
61impl From<Transaction> for VersionedTransaction {
62    fn from(transaction: Transaction) -> Self {
63        Self {
64            signatures: transaction.signatures,
65            message: VersionedMessage::Legacy(transaction.message),
66        }
67    }
68}
69
70impl VersionedTransaction {
71    /// Signs a versioned message and if successful, returns a signed
72    /// transaction.
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        let num_required_signatures = usize::from(self.message.header().num_required_signatures);
128        match num_required_signatures.cmp(&self.signatures.len()) {
129            Ordering::Greater => Err(SanitizeError::IndexOutOfBounds),
130            Ordering::Less => Err(SanitizeError::InvalidValue),
131            Ordering::Equal => Ok(()),
132        }?;
133
134        // Signatures are verified before message keys are loaded so all signers
135        // must correspond to static account keys.
136        if self.signatures.len() > self.message.static_account_keys().len() {
137            return Err(SanitizeError::IndexOutOfBounds);
138        }
139
140        Ok(())
141    }
142
143    /// Returns the version of the transaction
144    pub fn version(&self) -> TransactionVersion {
145        match self.message {
146            VersionedMessage::Legacy(_) => TransactionVersion::LEGACY,
147            VersionedMessage::V0(_) => TransactionVersion::Number(0),
148        }
149    }
150
151    /// Returns a legacy transaction if the transaction message is legacy.
152    pub fn into_legacy_transaction(self) -> Option<Transaction> {
153        match self.message {
154            VersionedMessage::Legacy(message) => Some(Transaction {
155                signatures: self.signatures,
156                message,
157            }),
158            _ => None,
159        }
160    }
161
162    /// Verify the transaction and hash its message
163    pub fn verify_and_hash_message(&self) -> Result<Hash> {
164        let message_bytes = self.message.serialize();
165        if !self
166            ._verify_with_results(&message_bytes)
167            .iter()
168            .all(|verify_result| *verify_result)
169        {
170            Err(TransactionError::SignatureFailure)
171        } else {
172            Ok(VersionedMessage::hash_raw_message(&message_bytes))
173        }
174    }
175
176    /// Verify the transaction and return a list of verification results
177    pub fn verify_with_results(&self) -> Vec<bool> {
178        let message_bytes = self.message.serialize();
179        self._verify_with_results(&message_bytes)
180    }
181
182    fn _verify_with_results(&self, message_bytes: &[u8]) -> Vec<bool> {
183        self.signatures
184            .iter()
185            .zip(self.message.static_account_keys().iter())
186            .map(|(signature, pubkey)| signature.verify(pubkey.as_ref(), message_bytes))
187            .collect()
188    }
189
190    /// Returns true if transaction begins with an advance nonce instruction.
191    pub fn uses_durable_nonce(&self) -> bool {
192        let message = &self.message;
193        message
194            .instructions()
195            .get(NONCED_TX_MARKER_IX_INDEX as usize)
196            .filter(|instruction| {
197                // Is system program
198                matches!(
199                    message.static_account_keys().get(instruction.program_id_index as usize),
200                    Some(program_id) if system_program::check_id(program_id)
201                )
202                // Is a nonce advance instruction
203                && matches!(
204                    limited_deserialize(&instruction.data),
205                    Ok(SystemInstruction::AdvanceNonceAccount)
206                )
207            })
208            .is_some()
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use {
215        super::*,
216        crate::{
217            message::Message as LegacyMessage,
218            signer::{keypair::Keypair, Signer},
219            system_instruction,
220        },
221        solana_program::{
222            instruction::{AccountMeta, Instruction},
223            pubkey::Pubkey,
224        },
225    };
226
227    #[test]
228    fn test_try_new() {
229        let keypair0 = Keypair::new();
230        let keypair1 = Keypair::new();
231        let keypair2 = Keypair::new();
232
233        let message = VersionedMessage::Legacy(LegacyMessage::new(
234            &[Instruction::new_with_bytes(
235                Pubkey::new_unique(),
236                &[],
237                vec![
238                    AccountMeta::new_readonly(keypair1.pubkey(), true),
239                    AccountMeta::new_readonly(keypair2.pubkey(), false),
240                ],
241            )],
242            Some(&keypair0.pubkey()),
243        ));
244
245        assert_eq!(
246            VersionedTransaction::try_new(message.clone(), &[&keypair0]),
247            Err(SignerError::NotEnoughSigners)
248        );
249
250        assert_eq!(
251            VersionedTransaction::try_new(message.clone(), &[&keypair0, &keypair0]),
252            Err(SignerError::KeypairPubkeyMismatch)
253        );
254
255        assert_eq!(
256            VersionedTransaction::try_new(message.clone(), &[&keypair1, &keypair2]),
257            Err(SignerError::KeypairPubkeyMismatch)
258        );
259
260        match VersionedTransaction::try_new(message.clone(), &[&keypair0, &keypair1]) {
261            Ok(tx) => assert_eq!(tx.verify_with_results(), vec![true; 2]),
262            Err(err) => assert_eq!(Some(err), None),
263        }
264
265        match VersionedTransaction::try_new(message, &[&keypair1, &keypair0]) {
266            Ok(tx) => assert_eq!(tx.verify_with_results(), vec![true; 2]),
267            Err(err) => assert_eq!(Some(err), None),
268        }
269    }
270
271    fn nonced_transfer_tx() -> (Pubkey, Pubkey, VersionedTransaction) {
272        let from_keypair = Keypair::new();
273        let from_pubkey = from_keypair.pubkey();
274        let nonce_keypair = Keypair::new();
275        let nonce_pubkey = nonce_keypair.pubkey();
276        let instructions = [
277            system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
278            system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
279        ];
280        let message = LegacyMessage::new(&instructions, Some(&nonce_pubkey));
281        let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default());
282        (from_pubkey, nonce_pubkey, tx.into())
283    }
284
285    #[test]
286    fn tx_uses_nonce_ok() {
287        let (_, _, tx) = nonced_transfer_tx();
288        assert!(tx.uses_durable_nonce());
289    }
290
291    #[test]
292    fn tx_uses_nonce_empty_ix_fail() {
293        assert!(!VersionedTransaction::default().uses_durable_nonce());
294    }
295
296    #[test]
297    fn tx_uses_nonce_bad_prog_id_idx_fail() {
298        let (_, _, mut tx) = nonced_transfer_tx();
299        match &mut tx.message {
300            VersionedMessage::Legacy(message) => {
301                message.instructions.get_mut(0).unwrap().program_id_index = 255u8;
302            }
303            VersionedMessage::V0(_) => unreachable!(),
304        };
305        assert!(!tx.uses_durable_nonce());
306    }
307
308    #[test]
309    fn tx_uses_nonce_first_prog_id_not_nonce_fail() {
310        let from_keypair = Keypair::new();
311        let from_pubkey = from_keypair.pubkey();
312        let nonce_keypair = Keypair::new();
313        let nonce_pubkey = nonce_keypair.pubkey();
314        let instructions = [
315            system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
316            system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
317        ];
318        let message = LegacyMessage::new(&instructions, Some(&from_pubkey));
319        let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default());
320        let tx = VersionedTransaction::from(tx);
321        assert!(!tx.uses_durable_nonce());
322    }
323
324    #[test]
325    fn tx_uses_nonce_wrong_first_nonce_ix_fail() {
326        let from_keypair = Keypair::new();
327        let from_pubkey = from_keypair.pubkey();
328        let nonce_keypair = Keypair::new();
329        let nonce_pubkey = nonce_keypair.pubkey();
330        let instructions = [
331            system_instruction::withdraw_nonce_account(
332                &nonce_pubkey,
333                &nonce_pubkey,
334                &from_pubkey,
335                42,
336            ),
337            system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
338        ];
339        let message = LegacyMessage::new(&instructions, Some(&nonce_pubkey));
340        let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default());
341        let tx = VersionedTransaction::from(tx);
342        assert!(!tx.uses_durable_nonce());
343    }
344}