solana_sdk/transaction/versioned/
mod.rs1#![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#[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#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
52#[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize)]
53pub struct VersionedTransaction {
54 #[serde(with = "short_vec")]
56 pub signatures: Vec<Signature>,
57 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 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 if self.signatures.len() > self.message.static_account_keys().len() {
137 return Err(SanitizeError::IndexOutOfBounds);
138 }
139
140 Ok(())
141 }
142
143 pub fn version(&self) -> TransactionVersion {
145 match self.message {
146 VersionedMessage::Legacy(_) => TransactionVersion::LEGACY,
147 VersionedMessage::V0(_) => TransactionVersion::Number(0),
148 }
149 }
150
151 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 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 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 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 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 && 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}