clone_solana_transaction/versioned/
mod.rs

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