1#[cfg(feature = "bincode")]
4use solana_signer::{signers::Signers, SignerError};
5#[cfg(feature = "wincode")]
6use wincode::{containers, len::ShortU16Len, SchemaRead, SchemaWrite};
7use {
8 crate::Transaction,
9 solana_message::{inline_nonce::is_advance_nonce_instruction_data, VersionedMessage},
10 solana_sanitize::SanitizeError,
11 solana_sdk_ids::system_program,
12 solana_signature::Signature,
13 std::cmp::Ordering,
14};
15#[cfg(feature = "serde")]
16use {
17 serde_derive::{Deserialize, Serialize},
18 solana_short_vec as short_vec,
19};
20
21pub mod sanitized;
22
23#[cfg_attr(
25 feature = "serde",
26 derive(Deserialize, Serialize),
27 serde(rename_all = "camelCase")
28)]
29#[derive(Clone, Debug, PartialEq, Eq)]
30pub enum Legacy {
31 Legacy,
32}
33
34#[cfg_attr(
35 feature = "serde",
36 derive(Deserialize, Serialize),
37 serde(rename_all = "camelCase", untagged)
38)]
39#[derive(Clone, Debug, PartialEq, Eq)]
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#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
52#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
53#[cfg_attr(feature = "wincode", derive(SchemaWrite, SchemaRead))]
54#[derive(Debug, PartialEq, Default, Eq, Clone)]
55pub struct VersionedTransaction {
56 #[cfg_attr(feature = "serde", serde(with = "short_vec"))]
58 #[cfg_attr(feature = "wincode", wincode(with = "containers::Vec<_, ShortU16Len>"))]
59 pub signatures: Vec<Signature>,
60 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 #[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 if num_signatures > num_static_account_keys {
152 return Err(SanitizeError::IndexOutOfBounds);
153 }
154
155 Ok(())
156 }
157
158 pub fn version(&self) -> TransactionVersion {
160 match self.message {
161 VersionedMessage::Legacy(_) => TransactionVersion::LEGACY,
162 VersionedMessage::V0(_) => TransactionVersion::Number(0),
163 }
164 }
165
166 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 pub fn verify_and_hash_message(
180 &self,
181 ) -> solana_transaction_error::TransactionResult<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(solana_transaction_error::TransactionError::SignatureFailure)
189 } else {
190 Ok(VersionedMessage::hash_raw_message(&message_bytes))
191 }
192 }
193
194 #[cfg(feature = "verify")]
195 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 pub fn uses_durable_nonce(&self) -> bool {
212 let message = &self.message;
213 message
214 .instructions()
215 .get(crate::NONCED_TX_MARKER_IX_INDEX as usize)
216 .filter(|instruction| {
217 matches!(
219 message.static_account_keys().get(instruction.program_id_index as usize),
220 Some(program_id) if system_program::check_id(program_id)
221 ) && is_advance_nonce_instruction_data(&instruction.data)
222 })
223 .is_some()
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use {
230 super::*,
231 solana_hash::Hash,
232 solana_instruction::{AccountMeta, Instruction},
233 solana_keypair::Keypair,
234 solana_message::Message as LegacyMessage,
235 solana_pubkey::Pubkey,
236 solana_signer::Signer,
237 solana_system_interface::instruction as system_instruction,
238 };
239
240 #[test]
241 fn test_try_new() {
242 let keypair0 = Keypair::new();
243 let keypair1 = Keypair::new();
244 let keypair2 = Keypair::new();
245
246 let message = VersionedMessage::Legacy(LegacyMessage::new(
247 &[Instruction::new_with_bytes(
248 Pubkey::new_unique(),
249 &[],
250 vec![
251 AccountMeta::new_readonly(keypair1.pubkey(), true),
252 AccountMeta::new_readonly(keypair2.pubkey(), false),
253 ],
254 )],
255 Some(&keypair0.pubkey()),
256 ));
257
258 assert_eq!(
259 VersionedTransaction::try_new(message.clone(), &[&keypair0]),
260 Err(SignerError::NotEnoughSigners)
261 );
262
263 assert_eq!(
264 VersionedTransaction::try_new(message.clone(), &[&keypair0, &keypair0]),
265 Err(SignerError::KeypairPubkeyMismatch)
266 );
267
268 assert_eq!(
269 VersionedTransaction::try_new(message.clone(), &[&keypair1, &keypair2]),
270 Err(SignerError::KeypairPubkeyMismatch)
271 );
272
273 match VersionedTransaction::try_new(message.clone(), &[&keypair0, &keypair1]) {
274 Ok(tx) => assert_eq!(tx.verify_with_results(), vec![true; 2]),
275 Err(err) => assert_eq!(Some(err), None),
276 }
277
278 match VersionedTransaction::try_new(message, &[&keypair1, &keypair0]) {
279 Ok(tx) => assert_eq!(tx.verify_with_results(), vec![true; 2]),
280 Err(err) => assert_eq!(Some(err), None),
281 }
282 }
283
284 fn nonced_transfer_tx() -> (Pubkey, Pubkey, VersionedTransaction) {
285 let from_keypair = Keypair::new();
286 let from_pubkey = from_keypair.pubkey();
287 let nonce_keypair = Keypair::new();
288 let nonce_pubkey = nonce_keypair.pubkey();
289 let instructions = [
290 system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
291 system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
292 ];
293 let message = LegacyMessage::new(&instructions, Some(&nonce_pubkey));
294 let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default());
295 (from_pubkey, nonce_pubkey, tx.into())
296 }
297
298 #[test]
299 fn tx_uses_nonce_ok() {
300 let (_, _, tx) = nonced_transfer_tx();
301 assert!(tx.uses_durable_nonce());
302 }
303
304 #[test]
305 fn tx_uses_nonce_empty_ix_fail() {
306 assert!(!VersionedTransaction::default().uses_durable_nonce());
307 }
308
309 #[test]
310 fn tx_uses_nonce_bad_prog_id_idx_fail() {
311 let (_, _, mut tx) = nonced_transfer_tx();
312 match &mut tx.message {
313 VersionedMessage::Legacy(message) => {
314 message.instructions.get_mut(0).unwrap().program_id_index = 255u8;
315 }
316 VersionedMessage::V0(_) => unreachable!(),
317 };
318 assert!(!tx.uses_durable_nonce());
319 }
320
321 #[test]
322 fn tx_uses_nonce_first_prog_id_not_nonce_fail() {
323 let from_keypair = Keypair::new();
324 let from_pubkey = from_keypair.pubkey();
325 let nonce_keypair = Keypair::new();
326 let nonce_pubkey = nonce_keypair.pubkey();
327 let instructions = [
328 system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
329 system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
330 ];
331 let message = LegacyMessage::new(&instructions, Some(&from_pubkey));
332 let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default());
333 let tx = VersionedTransaction::from(tx);
334 assert!(!tx.uses_durable_nonce());
335 }
336
337 #[test]
338 fn tx_uses_nonce_wrong_first_nonce_ix_fail() {
339 let from_keypair = Keypair::new();
340 let from_pubkey = from_keypair.pubkey();
341 let nonce_keypair = Keypair::new();
342 let nonce_pubkey = nonce_keypair.pubkey();
343 let instructions = [
344 system_instruction::withdraw_nonce_account(
345 &nonce_pubkey,
346 &nonce_pubkey,
347 &from_pubkey,
348 42,
349 ),
350 system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
351 ];
352 let message = LegacyMessage::new(&instructions, Some(&nonce_pubkey));
353 let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default());
354 let tx = VersionedTransaction::from(tx);
355 assert!(!tx.uses_durable_nonce());
356 }
357
358 #[test]
359 fn test_sanitize_signatures_inner() {
360 assert_eq!(
361 VersionedTransaction::sanitize_signatures_inner(1, 1, 0),
362 Err(SanitizeError::IndexOutOfBounds)
363 );
364 assert_eq!(
365 VersionedTransaction::sanitize_signatures_inner(1, 1, 2),
366 Err(SanitizeError::InvalidValue)
367 );
368 assert_eq!(
369 VersionedTransaction::sanitize_signatures_inner(2, 1, 2),
370 Err(SanitizeError::IndexOutOfBounds)
371 );
372 assert_eq!(
373 VersionedTransaction::sanitize_signatures_inner(1, 1, 1),
374 Ok(())
375 );
376 }
377
378 #[cfg(feature = "wincode")]
379 #[test]
380 fn versioned_transaction_wincode_bincode_roundtrip() {
381 use {
382 super::*,
383 proptest::prelude::*,
384 solana_address::{Address, ADDRESS_BYTES},
385 solana_hash::{Hash, HASH_BYTES},
386 solana_message::{
387 compiled_instruction::CompiledInstruction,
388 v0::{self, MessageAddressTableLookup},
389 Message as LegacyMessage, MessageHeader,
390 },
391 solana_signature::SIGNATURE_BYTES,
392 };
393
394 fn strat_byte_vec(max_len: usize) -> impl Strategy<Value = Vec<u8>> {
395 proptest::collection::vec(any::<u8>(), 0..=max_len)
396 }
397
398 fn strat_signature() -> impl Strategy<Value = Signature> {
399 any::<[u8; SIGNATURE_BYTES]>().prop_map(Signature::from)
400 }
401
402 fn strat_address() -> impl Strategy<Value = Address> {
403 any::<[u8; ADDRESS_BYTES]>().prop_map(Address::new_from_array)
404 }
405
406 fn strat_hash() -> impl Strategy<Value = Hash> {
407 any::<[u8; HASH_BYTES]>().prop_map(Hash::new_from_array)
408 }
409
410 fn strat_message_header() -> impl Strategy<Value = MessageHeader> {
411 (0u8..128, any::<u8>(), any::<u8>()).prop_map(|(a, b, c)| MessageHeader {
412 num_required_signatures: a,
413 num_readonly_signed_accounts: b,
414 num_readonly_unsigned_accounts: c,
415 })
416 }
417
418 fn strat_compiled_instruction() -> impl Strategy<Value = CompiledInstruction> {
419 (any::<u8>(), strat_byte_vec(128), strat_byte_vec(128)).prop_map(
420 |(program_id_index, accounts, data)| {
421 CompiledInstruction::new_from_raw_parts(program_id_index, accounts, data)
422 },
423 )
424 }
425
426 fn strat_address_table_lookup() -> impl Strategy<Value = MessageAddressTableLookup> {
427 (strat_address(), strat_byte_vec(128), strat_byte_vec(128)).prop_map(
428 |(account_key, writable_indexes, readonly_indexes)| MessageAddressTableLookup {
429 account_key,
430 writable_indexes,
431 readonly_indexes,
432 },
433 )
434 }
435
436 fn strat_legacy_message() -> impl Strategy<Value = LegacyMessage> {
437 (
438 strat_message_header(),
439 proptest::collection::vec(strat_address(), 0..=8),
440 strat_hash(),
441 proptest::collection::vec(strat_compiled_instruction(), 0..=8),
442 )
443 .prop_map(|(header, account_keys, recent_blockhash, instructions)| {
444 LegacyMessage {
445 header,
446 account_keys,
447 recent_blockhash,
448 instructions,
449 }
450 })
451 }
452
453 fn strat_v0_message() -> impl Strategy<Value = v0::Message> {
454 (
455 strat_message_header(),
456 proptest::collection::vec(strat_address(), 0..=8),
457 strat_hash(),
458 proptest::collection::vec(strat_compiled_instruction(), 0..=4),
459 proptest::collection::vec(strat_address_table_lookup(), 0..=4),
460 )
461 .prop_map(
462 |(
463 header,
464 account_keys,
465 recent_blockhash,
466 instructions,
467 address_table_lookups,
468 )| {
469 v0::Message {
470 header,
471 account_keys,
472 recent_blockhash,
473 instructions,
474 address_table_lookups,
475 }
476 },
477 )
478 }
479
480 fn strat_versioned_message() -> impl Strategy<Value = VersionedMessage> {
481 prop_oneof![
482 strat_legacy_message().prop_map(VersionedMessage::Legacy),
483 strat_v0_message().prop_map(VersionedMessage::V0),
484 ]
485 }
486
487 fn strat_versioned_transaction() -> impl Strategy<Value = VersionedTransaction> {
488 (
489 proptest::collection::vec(strat_signature(), 0..=8),
490 strat_versioned_message(),
491 )
492 .prop_map(|(signatures, message)| VersionedTransaction {
493 signatures,
494 message,
495 })
496 }
497
498 proptest!(|(tx in strat_versioned_transaction())| {
499 let bincode_serialized = bincode::serialize(&tx).unwrap();
500 let wincode_serialized = wincode::serialize(&tx).unwrap();
501 assert_eq!(bincode_serialized, wincode_serialized);
502
503 let bincode_deserialized: VersionedTransaction = bincode::deserialize(&bincode_serialized).unwrap();
504 let wincode_deserialized = wincode::deserialize(&wincode_serialized).unwrap();
505 assert_eq!(&bincode_deserialized, &wincode_deserialized);
506 assert_eq!(wincode_deserialized, tx);
507 });
508 }
509}