1use {
2 crate::{
3 errors::{
4 decode_error::DecodeError,
5 },
6 block::{
7 encoded_block::{
8 EncodedTransaction,
9 EncodedTransactionWithStatusMeta,
10 },
11 },
12 message::{
13 message::Message,
14 message_v0::Message as MessageV0,
15 versioned_message::VersionedMessage,
16 },
17 transaction::{
18 tx_status_meta::TransactionStatusMeta,
19 },
20 decodable::{
21 Decodable,
22 DecodableWithMeta,
23 },
24 },
25 serde::{
26 Deserialize, Serialize,
27 },
28 solana_short_vec as short_vec,
29 solana_signature::Signature,
30 solana_transaction::{
31 versioned::TransactionVersion
32 },
33 solana_transaction_status_client_types::{
34 UiMessage,
35 UiTransactionEncoding,
36 },
37 base64::{Engine, prelude::BASE64_STANDARD},
38};
39
40#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
41pub struct VersionedTransactionWithStatusMeta {
42 pub transaction: VersionedTransaction,
43 pub meta: TransactionStatusMeta,
44}
45
46impl VersionedTransactionWithStatusMeta {
47 pub fn decode(
48 encoded: EncodedTransactionWithStatusMeta,
49 encoding: UiTransactionEncoding,
50 ) -> Result<Self, DecodeError> {
51 let transaction = match VersionedTransaction::decode_with_meta(encoded.transaction, encoding, encoded.version ) {
53 Ok(decoded) => decoded,
54 Err(e) => return Err(e),
55 };
56
57 let meta = match encoded.meta {
59 Some(ui_meta) => match TransactionStatusMeta::try_from(ui_meta) {
60 Ok(meta) => meta,
61 Err(_) => return Err(DecodeError::InvalidData),
62 },
63 None => return Err(DecodeError::InvalidData),
64 };
65
66 Ok(Self {
67 transaction,
68 meta,
69 })
70 }
71}
72
73#[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize)]
74pub struct VersionedTransaction {
75 #[serde(with = "short_vec")]
77 pub signatures: Vec<Signature>,
78 pub message: VersionedMessage,
80}
81
82
83impl DecodableWithMeta for VersionedTransaction {
84 type Encoded = EncodedTransaction;
85 type Decoded = VersionedTransaction;
86
87 fn decode_with_meta(
88 encoded: Self::Encoded,
89 decoding: UiTransactionEncoding,
90 version: Option<TransactionVersion>
91 ) -> Result<Self::Decoded, DecodeError> {
92 match decoding {
93 UiTransactionEncoding::Binary | UiTransactionEncoding::Base58 => {
94 if let EncodedTransaction::LegacyBinary(encoded_string) = encoded {
95 let decoded_bytes = bs58::decode(encoded_string).into_vec().unwrap();
96 let decoded: Self::Decoded =
97 bincode::deserialize(&decoded_bytes).map_err(|_| DecodeError::DeserializeFailed)?;
98 Ok(decoded)
99 } else {
100 Err(DecodeError::UnsupportedEncoding)
101 }
102 }
103 UiTransactionEncoding::Base64 => {
104 if let EncodedTransaction::Binary(encoded_string, _) = encoded {
105 let decoded_bytes = BASE64_STANDARD.decode(encoded_string).unwrap();
106 let decoded: Self::Decoded =
107 bincode::deserialize(&decoded_bytes).map_err(|_| DecodeError::DeserializeFailed)?;
108 Ok(decoded)
109 } else {
110 Err(DecodeError::UnsupportedEncoding)
111 }
112 }
113 UiTransactionEncoding::Json => Self::json_decode(encoded, version),
114 UiTransactionEncoding::JsonParsed => Err(DecodeError::UnsupportedEncoding),
115 }
116 }
117
118 fn json_decode(encoded: Self::Encoded, version: Option<TransactionVersion>) -> Result<Self::Decoded, DecodeError> {
119 if let EncodedTransaction::Json(ui_transaction) = encoded {
120 let signatures = ui_transaction
121 .signatures
122 .iter()
123 .map(|s| s.parse::<Signature>())
124 .collect::<Result<Vec<_>, _>>()
125 .map_err(|err| DecodeError::ParseSignatureFailed(err))?;
126
127 let message = match ui_transaction.message {
128 UiMessage::Raw(_) => {
129 match version {
130 Some(TransactionVersion::Number(0)) => {
131 let v0_message = MessageV0::json_decode(ui_transaction.message, version)?;
133 VersionedMessage::V0(v0_message)
134 }
135 Some(TransactionVersion::Legacy(_)) | None => {
136 let legacy_message = Message::decode(&ui_transaction.message)?;
138 VersionedMessage::Legacy(legacy_message)
139 }
140 _ => {
142 return Err(DecodeError::UnsupportedVersion);
144 }
145 }
146 }
147 UiMessage::Parsed(_) => {
148 return Err(DecodeError::UnsupportedEncoding);
149 }
150 };
151
152 Ok(Self {
153 signatures,
154 message,
155 })
156 } else {
157 Err(DecodeError::UnsupportedEncoding)
158 }
159 }
160}
161
162impl From<VersionedTransaction> for solana_transaction::versioned::VersionedTransaction {
163 fn from(tx: VersionedTransaction) -> Self {
164 Self {
165 signatures: tx.signatures,
166 message: tx.message.into(),
167 }
168 }
169}
170
171impl From<VersionedTransactionWithStatusMeta> for solana_transaction_status::VersionedTransactionWithStatusMeta {
172 fn from(tx: VersionedTransactionWithStatusMeta) -> Self {
173 Self {
174 transaction: tx.transaction.into(),
175 meta: tx.meta.into(),
176 }
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use std::str::FromStr;
184
185 use solana_transaction_status_client_types::{
186 UiTransactionEncoding, UiTransaction, UiMessage, UiRawMessage,
187 UiTransactionStatusMeta,
188 };
189 use solana_transaction_status::TransactionBinaryEncoding;
190 use serde_json::json;
191 use solana_signature::SIGNATURE_BYTES;
192
193 fn make_valid_ui_meta() -> UiTransactionStatusMeta {
194 serde_json::from_value(json!({
195 "status": { "Ok": null },
196 "fee": 0,
197 "preBalances": [100u64, 50u64],
198 "postBalances": [90u64, 50u64],
199 "rewards": [],
200 "logMessages": [],
201 "innerInstructions": [],
202 "preTokenBalances": null,
203 "postTokenBalances": null,
204 "loadedAddresses": { "readonly": [], "writable": [] },
205 "returnData": null,
206 "computeUnitsConsumed": 0
207 })).expect("Failed to deserialize a valid UiTransactionStatusMeta")
208 }
209
210 fn make_invalid_ui_meta() -> UiTransactionStatusMeta {
211 serde_json::from_value(json!({
212 "invalid": "meta"
213 })).expect("Unexpectedly parsed 'invalid' meta successfully")
214 }
215
216 #[test]
217 fn test_decode_versioned_transaction_with_status_meta_success() {
218 let valid_transaction = VersionedTransaction {
220 signatures: vec![Signature::default()],
221 message: VersionedMessage::Legacy(Message::default()),
222 };
223
224 let raw_bytes = bincode::serialize(&valid_transaction)
226 .expect("Failed to serialize VersionedTransaction");
227
228 let encoded_string = base64::engine::general_purpose::STANDARD.encode(&raw_bytes);
229
230 println!("Base64 Encoded Transaction: {}", encoded_string); let encoded_tx = EncodedTransaction::Binary(
234 encoded_string,
235 TransactionBinaryEncoding::Base64,
236 );
237
238 let meta_json = json!({
240 "status": { "Ok": null },
241 "fee": 0,
242 "preBalances": [100u64, 50u64],
243 "postBalances": [90u64, 50u64],
244 "rewards": [],
245 "logMessages": [],
246 "innerInstructions": [],
247 "preTokenBalances": null,
248 "postTokenBalances": null,
249 "loadedAddresses": { "readonly": [], "writable": [] },
250 "returnData": null,
251 "computeUnitsConsumed": 0
252 });
253
254 println!("Meta JSON: {}", meta_json.to_string()); let encoded_meta = Some(serde_json::from_value(meta_json)
257 .expect("Failed to create valid meta"));
258
259 println!("Parsed Meta: {:?}", encoded_meta); let encoded = EncodedTransactionWithStatusMeta {
262 transaction: encoded_tx,
263 meta: encoded_meta,
264 version: None,
265 };
266
267 let result = dbg!(VersionedTransactionWithStatusMeta::decode(encoded, UiTransactionEncoding::Base64));
269
270 assert!(result.is_ok(), "Expected successful decoding, but got {:?}", result);
272 }
273
274 #[test]
275 fn test_decode_versioned_transaction_with_status_meta_invalid_data() {
276 let valid_transaction = VersionedTransaction {
278 signatures: vec![Signature::default()], message: VersionedMessage::Legacy(Message::default()), };
281
282 let raw_bytes = bincode::serialize(&valid_transaction)
283 .expect("Failed to serialize VersionedTransaction");
284
285 let encoded_string = base64::engine::general_purpose::STANDARD.encode(&raw_bytes);
286
287 let encoded_tx = EncodedTransaction::Binary(
288 encoded_string,
289 TransactionBinaryEncoding::Base64,
290 );
291
292 let encoded_meta = Some(serde_json::from_value(json!({
294 "status": { "Ok": null },
295 "fee": u64::MAX,
296 "preBalances": [],
297 "postBalances": [],
298 "rewards": [],
299 "logMessages": None::<Vec<String>>,
300 "innerInstructions": None::<Vec<String>>
301 })).expect("Failed to create invalid meta"));
302
303 let encoded = EncodedTransactionWithStatusMeta {
304 transaction: encoded_tx,
305 meta: encoded_meta,
306 version: None,
307 };
308
309 let result = VersionedTransactionWithStatusMeta::decode(encoded, UiTransactionEncoding::Base64);
311
312 match result {
314 Err(DecodeError::InvalidData) => {}
315 Ok(_) => panic!("Expected InvalidData error, but decoding succeeded!"),
316 Err(e) => panic!("Expected InvalidData error, got {:?}", e),
317 }
318 }
319
320 #[test]
321 fn test_decode_versioned_transaction_with_status_meta_no_meta() {
322 let valid_transaction = VersionedTransaction {
323 signatures: vec![Signature::default()],
324 message: VersionedMessage::Legacy(Message::default()),
325 };
326
327 let raw_bytes = bincode::serialize(&valid_transaction).unwrap();
328 let encoded_string = base64::engine::general_purpose::STANDARD.encode(&raw_bytes);
329 let encoded_tx = EncodedTransaction::Binary(encoded_string, TransactionBinaryEncoding::Base64);
330
331 let encoded = EncodedTransactionWithStatusMeta {
332 transaction: encoded_tx,
333 meta: None,
334 version: None,
335 };
336
337 let result = VersionedTransactionWithStatusMeta::decode(encoded, UiTransactionEncoding::Base64);
338 assert!(matches!(result, Err(DecodeError::InvalidData)));
339 }
340
341 #[test]
342 fn test_from_versioned_transaction() {
343 let valid_signature_bytes = [0u8; SIGNATURE_BYTES]; let valid_signature_str = bs58::encode(valid_signature_bytes).into_string();
346
347 let signature = Signature::from_str(&valid_signature_str)
349 .expect("Failed to parse valid base58 signature");
350
351 let tx = VersionedTransaction {
352 signatures: vec![signature],
353 message: VersionedMessage::Legacy(Message::default()),
354 };
355
356 let converted: solana_transaction::versioned::VersionedTransaction = tx.into();
357
358 assert_eq!(converted.signatures.len(), 1);
360 }
361
362
363
364 #[test]
369 fn test_decode_versioned_transaction_success_legacy_binary() {
370 let raw_bytes = bincode::serialize(&VersionedTransaction {
372 signatures: vec![Signature::default()],
373 message: VersionedMessage::Legacy(Message::default()),
374 }).expect("Failed to serialize VersionedTransaction");
375
376 let encoded_string = bs58::encode(&raw_bytes).into_string();
378
379 let encoded = EncodedTransaction::LegacyBinary(encoded_string);
381
382 let result = VersionedTransaction::decode_with_meta(
383 encoded,
384 UiTransactionEncoding::Base58,
385 None,
386 );
387
388 assert!(result.is_ok(), "Expected successful decoding, but got {:?}", result);
390 }
391
392 #[test]
393 fn test_decode_versioned_transaction_invalid_base58_format() {
394 let encoded = EncodedTransaction::LegacyBinary("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".to_string());
395 let result = VersionedTransaction::decode_with_meta(encoded, UiTransactionEncoding::Base58, None);
396 assert!(matches!(result, Err(DecodeError::DeserializeFailed)));
397 }
398
399 #[test]
400 fn test_decode_versioned_transaction_invalid_legacy_binary() {
401 let encoded = EncodedTransaction::Binary(
403 "invalid-base58".to_string(),
404 TransactionBinaryEncoding::Base58,
405 );
406
407 let result = VersionedTransaction::decode_with_meta(
408 encoded,
409 UiTransactionEncoding::Base58,
410 None,
411 );
412
413 assert!(result.is_err(), "Expected decoding to fail for invalid encoding type");
415 }
416
417
418 #[test]
423 fn test_decode_versioned_transaction_success_base64() {
424 let raw_bytes = bincode::serialize(&VersionedTransaction {
426 signatures: vec![Signature::default()],
427 message: VersionedMessage::Legacy(Message::default()),
428 }).unwrap();
429
430 let encoded_string = base64::engine::general_purpose::STANDARD.encode(&raw_bytes);
431
432 let encoded = EncodedTransaction::Binary(
433 encoded_string,
434 TransactionBinaryEncoding::Base64,
435 );
436
437 let result = VersionedTransaction::decode_with_meta(
438 encoded,
439 UiTransactionEncoding::Base64,
440 None,
441 );
442
443 assert!(result.is_ok());
444 }
445
446 #[test]
447 fn test_decode_versioned_transaction_success_base64_v0() {
448 let raw_bytes = bincode::serialize(&VersionedTransaction {
450 signatures: vec![Signature::default()],
451 message: VersionedMessage::Legacy(Message::default()),
452 })
453 .unwrap();
454
455 let encoded_string = base64::engine::general_purpose::STANDARD.encode(&raw_bytes);
456
457 let encoded = EncodedTransaction::Binary(
458 encoded_string,
459 TransactionBinaryEncoding::Base64,
460 );
461
462 let result = VersionedTransaction::decode_with_meta(
463 encoded,
464 UiTransactionEncoding::Base64,
465 Some(TransactionVersion::Number(0)),
466 );
467
468 assert!(result.is_ok());
469 }
470
471 #[test]
472 #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidByte")]
473 fn test_decode_versioned_transaction_invalid_base64() {
474 let encoded = EncodedTransaction::Binary(
476 "invalid-base64".to_string(),
477 TransactionBinaryEncoding::Base64,
478 );
479
480 let _ = VersionedTransaction::decode_with_meta(
483 encoded,
484 UiTransactionEncoding::Base64,
485 None,
486 );
487 }
488
489 #[test]
490 #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidByte")]
491 fn test_decode_versioned_transaction_invalid_base64_v0() {
492 let encoded = EncodedTransaction::Binary(
494 "invalid-base64".to_string(),
495 TransactionBinaryEncoding::Base64,
496 );
497
498 let _ = VersionedTransaction::decode_with_meta(
501 encoded,
502 UiTransactionEncoding::Base64,
503 Some(TransactionVersion::Number(0)),
504 );
505 }
506
507 #[test]
508 fn test_decode_versioned_transaction_invalid_base64_format() {
509 let encoded = EncodedTransaction::Binary("aGVsbG8gd29ybGQ=".to_string(), TransactionBinaryEncoding::Base64);
510 let result = VersionedTransaction::decode_with_meta(encoded, UiTransactionEncoding::Base64, None);
511 assert!(matches!(result, Err(DecodeError::DeserializeFailed)));
512 }
513
514
515 #[test]
520 fn test_decode_versioned_transaction_json_invalid_signature() {
521 let ui_transaction = UiTransaction {
522 signatures: vec!["invalid-signature".to_string()],
523 message: UiMessage::Raw(serde_json::from_value(json!({
524 "header": {
525 "numRequiredSignatures": 0,
526 "numReadonlySignedAccounts": 0,
527 "numReadonlyUnsignedAccounts": 0
528 },
529 "accountKeys": [],
530 "instructions": [],
531 "recentBlockhash": "some-blockhash"
532 })).unwrap()),
533 };
534
535 let json_encoded = EncodedTransaction::Json(ui_transaction);
536 let result = VersionedTransaction::decode_with_meta(json_encoded, UiTransactionEncoding::Json, None);
537 assert!(matches!(result, Err(DecodeError::ParseSignatureFailed(_))));
538 }
539
540 #[test]
541 fn test_decode_versioned_transaction_json_invalid_version() {
542 let ui_transaction = UiTransaction {
543 signatures: vec![Signature::default().to_string()],
544 message: UiMessage::Raw(serde_json::from_value(json!({
545 "header": {
546 "numRequiredSignatures": 0,
547 "numReadonlySignedAccounts": 0,
548 "numReadonlyUnsignedAccounts": 0
549 },
550 "accountKeys": [],
551 "instructions": [],
552 "recentBlockhash": "some-blockhash"
553 })).unwrap()),
554 };
555
556 let json_encoded = EncodedTransaction::Json(ui_transaction);
557 let result = VersionedTransaction::decode_with_meta(
558 json_encoded,
559 UiTransactionEncoding::Json,
560 Some(TransactionVersion::Number(99))
561 );
562 assert!(matches!(result, Err(DecodeError::UnsupportedVersion)));
563 }
564
565 #[test]
566 fn test_decode_versioned_transaction_invalid_json_encoding() {
567 let ui_transaction = UiTransaction {
568 signatures: vec![Signature::default().to_string()],
569 message: UiMessage::Parsed(serde_json::from_value(json!({
570 "accountKeys": [],
571 "recentBlockhash": "some-blockhash",
572 "instructions": []
573 })).unwrap()),
574 };
575
576 let json_encoded = EncodedTransaction::Json(ui_transaction);
577 let result = VersionedTransaction::decode_with_meta(
578 json_encoded,
579 UiTransactionEncoding::JsonParsed,
580 None
581 );
582 assert!(matches!(result, Err(DecodeError::UnsupportedEncoding)));
583 }
584
585 #[test]
586 fn test_decode_versioned_transaction_json() {
587 let ui_transaction = UiTransaction {
588 signatures: vec!["signature".to_string()],
589 message: UiMessage::Raw(
590 serde_json::from_value(json!({
591 "header": {
592 "numRequiredSignatures": 0,
593 "numReadonlySignedAccounts": 0,
594 "numReadonlyUnsignedAccounts": 0
595 },
596 "accountKeys": [],
597 "instructions": [],
598 "recentBlockhash": "some-blockhash"
599 })).unwrap()
600 ),
601 };
602
603 let json_encoded = EncodedTransaction::Json(ui_transaction);
604
605 let result = VersionedTransaction::decode_with_meta(
606 json_encoded,
607 UiTransactionEncoding::Json,
608 Some(TransactionVersion::Number(0)),
609 );
610
611 assert!(result.is_err());
612 }
613
614
615 #[test]
620 fn test_decode_versioned_transaction_unsupported_encoding() {
621 let ui_transaction = UiTransaction {
623 signatures: vec!["signature".to_string()],
624 message: UiMessage::Parsed(serde_json::from_value(json!({
625 "accountKeys": [],
626 "recentBlockhash": "some-blockhash",
627 "instructions": []
628 })).expect("Failed to construct valid UiMessage::Parsed")),
629 };
630
631 let json_encoded = EncodedTransaction::Json(ui_transaction);
632
633 let result = VersionedTransaction::decode_with_meta(
634 json_encoded,
635 UiTransactionEncoding::JsonParsed, None,
637 );
638
639 assert!(result.is_err(), "Expected decoding to fail with UnsupportedEncoding");
641 }
642
643 #[test]
644 fn test_decode_versioned_transaction_unsupported_encoding_variant() {
645 let encoded = EncodedTransaction::Binary("invalid".to_string(), TransactionBinaryEncoding::Base64);
646 let result = VersionedTransaction::decode_with_meta(
647 encoded,
648 UiTransactionEncoding::JsonParsed,
649 None
650 );
651 assert!(matches!(result, Err(DecodeError::UnsupportedEncoding)));
652 }
653}