solana_transaction/versioned/
mod.rs1#[cfg(feature = "bincode")]
4use solana_signer::{signers::Signers, SignerError};
5use {
6 crate::Transaction,
7 solana_message::{inline_nonce::is_advance_nonce_instruction_data, VersionedMessage},
8 solana_sanitize::SanitizeError,
9 solana_sdk_ids::system_program,
10 solana_signature::Signature,
11 std::cmp::Ordering,
12};
13#[cfg(feature = "serde")]
14use {
15 serde_derive::{Deserialize, Serialize},
16 solana_short_vec as short_vec,
17};
18
19pub mod sanitized;
20
21#[cfg_attr(
23 feature = "serde",
24 derive(Deserialize, Serialize),
25 serde(rename_all = "camelCase")
26)]
27#[derive(Clone, Debug, PartialEq, Eq)]
28pub enum Legacy {
29 Legacy,
30}
31
32#[cfg_attr(
33 feature = "serde",
34 derive(Deserialize, Serialize),
35 serde(rename_all = "camelCase", untagged)
36)]
37#[derive(Clone, Debug, PartialEq, Eq)]
38pub enum TransactionVersion {
39 Legacy(Legacy),
40 Number(u8),
41}
42
43impl TransactionVersion {
44 pub const LEGACY: Self = Self::Legacy(Legacy::Legacy);
45}
46
47#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
50#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
51#[derive(Debug, PartialEq, Default, Eq, Clone)]
52pub struct VersionedTransaction {
53 #[cfg_attr(feature = "serde", serde(with = "short_vec"))]
55 pub signatures: Vec<Signature>,
56 pub message: VersionedMessage,
58}
59
60impl From<Transaction> for VersionedTransaction {
61 fn from(transaction: Transaction) -> Self {
62 Self {
63 signatures: transaction.signatures,
64 message: VersionedMessage::Legacy(transaction.message),
65 }
66 }
67}
68
69impl VersionedTransaction {
70 #[cfg(feature = "bincode")]
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 Self::sanitize_signatures_inner(
128 usize::from(self.message.header().num_required_signatures),
129 self.message.static_account_keys().len(),
130 self.signatures.len(),
131 )
132 }
133
134 pub(crate) fn sanitize_signatures_inner(
135 num_required_signatures: usize,
136 num_static_account_keys: usize,
137 num_signatures: usize,
138 ) -> std::result::Result<(), SanitizeError> {
139 match num_required_signatures.cmp(&num_signatures) {
140 Ordering::Greater => Err(SanitizeError::IndexOutOfBounds),
141 Ordering::Less => Err(SanitizeError::InvalidValue),
142 Ordering::Equal => Ok(()),
143 }?;
144
145 if num_signatures > num_static_account_keys {
148 return Err(SanitizeError::IndexOutOfBounds);
149 }
150
151 Ok(())
152 }
153
154 pub fn version(&self) -> TransactionVersion {
156 match self.message {
157 VersionedMessage::Legacy(_) => TransactionVersion::LEGACY,
158 VersionedMessage::V0(_) => TransactionVersion::Number(0),
159 }
160 }
161
162 pub fn into_legacy_transaction(self) -> Option<Transaction> {
164 match self.message {
165 VersionedMessage::Legacy(message) => Some(Transaction {
166 signatures: self.signatures,
167 message,
168 }),
169 _ => None,
170 }
171 }
172
173 #[cfg(feature = "verify")]
174 pub fn verify_and_hash_message(
176 &self,
177 ) -> solana_transaction_error::TransactionResult<solana_hash::Hash> {
178 let message_bytes = self.message.serialize();
179 if !self
180 ._verify_with_results(&message_bytes)
181 .iter()
182 .all(|verify_result| *verify_result)
183 {
184 Err(solana_transaction_error::TransactionError::SignatureFailure)
185 } else {
186 Ok(VersionedMessage::hash_raw_message(&message_bytes))
187 }
188 }
189
190 #[cfg(feature = "verify")]
191 pub fn verify_with_results(&self) -> Vec<bool> {
193 let message_bytes = self.message.serialize();
194 self._verify_with_results(&message_bytes)
195 }
196
197 #[cfg(feature = "verify")]
198 fn _verify_with_results(&self, message_bytes: &[u8]) -> Vec<bool> {
199 self.signatures
200 .iter()
201 .zip(self.message.static_account_keys().iter())
202 .map(|(signature, pubkey)| signature.verify(pubkey.as_ref(), message_bytes))
203 .collect()
204 }
205
206 pub fn uses_durable_nonce(&self) -> bool {
208 let message = &self.message;
209 message
210 .instructions()
211 .get(crate::NONCED_TX_MARKER_IX_INDEX as usize)
212 .filter(|instruction| {
213 matches!(
215 message.static_account_keys().get(instruction.program_id_index as usize),
216 Some(program_id) if system_program::check_id(program_id)
217 ) && is_advance_nonce_instruction_data(&instruction.data)
218 })
219 .is_some()
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use {
226 super::*,
227 solana_hash::Hash,
228 solana_instruction::{AccountMeta, Instruction},
229 solana_keypair::Keypair,
230 solana_message::Message as LegacyMessage,
231 solana_pubkey::Pubkey,
232 solana_signer::Signer,
233 solana_system_interface::instruction as system_instruction,
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_nonce_wrong_first_nonce_ix_fail() {
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 instructions = [
340 system_instruction::withdraw_nonce_account(
341 &nonce_pubkey,
342 &nonce_pubkey,
343 &from_pubkey,
344 42,
345 ),
346 system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
347 ];
348 let message = LegacyMessage::new(&instructions, Some(&nonce_pubkey));
349 let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default());
350 let tx = VersionedTransaction::from(tx);
351 assert!(!tx.uses_durable_nonce());
352 }
353
354 #[test]
355 fn test_sanitize_signatures_inner() {
356 assert_eq!(
357 VersionedTransaction::sanitize_signatures_inner(1, 1, 0),
358 Err(SanitizeError::IndexOutOfBounds)
359 );
360 assert_eq!(
361 VersionedTransaction::sanitize_signatures_inner(1, 1, 2),
362 Err(SanitizeError::InvalidValue)
363 );
364 assert_eq!(
365 VersionedTransaction::sanitize_signatures_inner(2, 1, 2),
366 Err(SanitizeError::IndexOutOfBounds)
367 );
368 assert_eq!(
369 VersionedTransaction::sanitize_signatures_inner(1, 1, 1),
370 Ok(())
371 );
372 }
373}