1#![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#[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#[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize, AbiExample)]
51pub struct VersionedTransaction {
52 #[serde(with = "short_vec")]
54 pub signatures: Vec<Signature>,
55 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 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 if self.signatures.len() > self.message.static_account_keys().len() {
138 return Err(SanitizeError::IndexOutOfBounds);
139 }
140
141 Ok(())
142 }
143
144 pub fn version(&self) -> TransactionVersion {
146 match self.message {
147 VersionedMessage::Legacy(_) => TransactionVersion::LEGACY,
148 VersionedMessage::V0(_) => TransactionVersion::Number(0),
149 }
150 }
151
152 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 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 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 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 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 && matches!(
208 limited_deserialize(&instruction.data),
209 Ok(SystemInstruction::AdvanceNonceAccount)
210 )
211 && 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}