solana_transaction_status_wasm/
lib.rs

1#![allow(clippy::arithmetic_side_effects)]
2
3use std::collections::HashSet;
4
5use agave_reserved_account_keys::ReservedAccountKeys;
6use base64::Engine;
7use base64::prelude::BASE64_STANDARD;
8use solana_clock::Slot;
9use solana_clock::UnixTimestamp;
10use solana_hash::Hash;
11use solana_instruction::TRANSACTION_LEVEL_STACK_HEIGHT;
12use solana_message::AccountKeys;
13use solana_message::Message;
14use solana_message::VersionedMessage;
15use solana_message::compiled_instruction::CompiledInstruction;
16use solana_message::v0::LoadedAddresses;
17use solana_message::v0::LoadedMessage;
18use solana_message::v0::{self};
19use solana_pubkey::Pubkey;
20pub use solana_reward_info::RewardType;
21use solana_signature::Signature;
22use solana_transaction::Transaction;
23use solana_transaction::versioned::TransactionVersion;
24use solana_transaction::versioned::VersionedTransaction;
25use solana_transaction_error::TransactionError;
26pub use solana_transaction_status_client_types_wasm::ConfirmedTransactionStatusWithSignature;
27pub use solana_transaction_status_client_types_wasm::EncodeError;
28pub use solana_transaction_status_client_types_wasm::EncodedConfirmedBlock;
29pub use solana_transaction_status_client_types_wasm::EncodedConfirmedTransactionWithStatusMeta;
30pub use solana_transaction_status_client_types_wasm::EncodedTransaction;
31pub use solana_transaction_status_client_types_wasm::EncodedTransactionWithStatusMeta;
32pub use solana_transaction_status_client_types_wasm::InnerInstruction;
33pub use solana_transaction_status_client_types_wasm::InnerInstructions;
34pub use solana_transaction_status_client_types_wasm::Reward;
35pub use solana_transaction_status_client_types_wasm::Rewards;
36pub use solana_transaction_status_client_types_wasm::TransactionBinaryEncoding;
37pub use solana_transaction_status_client_types_wasm::TransactionConfirmationStatus;
38pub use solana_transaction_status_client_types_wasm::TransactionDetails;
39pub use solana_transaction_status_client_types_wasm::TransactionStatus;
40pub use solana_transaction_status_client_types_wasm::TransactionStatusMeta;
41pub use solana_transaction_status_client_types_wasm::TransactionTokenBalance;
42pub use solana_transaction_status_client_types_wasm::UiAccountsList;
43pub use solana_transaction_status_client_types_wasm::UiAddressTableLookup;
44pub use solana_transaction_status_client_types_wasm::UiCompiledInstruction;
45pub use solana_transaction_status_client_types_wasm::UiConfirmedBlock;
46pub use solana_transaction_status_client_types_wasm::UiInnerInstructions;
47pub use solana_transaction_status_client_types_wasm::UiInstruction;
48pub use solana_transaction_status_client_types_wasm::UiLoadedAddresses;
49pub use solana_transaction_status_client_types_wasm::UiMessage;
50pub use solana_transaction_status_client_types_wasm::UiParsedInstruction;
51pub use solana_transaction_status_client_types_wasm::UiParsedMessage;
52pub use solana_transaction_status_client_types_wasm::UiPartiallyDecodedInstruction;
53pub use solana_transaction_status_client_types_wasm::UiRawMessage;
54pub use solana_transaction_status_client_types_wasm::UiReturnDataEncoding;
55pub use solana_transaction_status_client_types_wasm::UiTransaction;
56pub use solana_transaction_status_client_types_wasm::UiTransactionEncoding;
57pub use solana_transaction_status_client_types_wasm::UiTransactionReturnData;
58pub use solana_transaction_status_client_types_wasm::UiTransactionStatusMeta;
59pub use solana_transaction_status_client_types_wasm::option_serializer;
60use thiserror::Error;
61
62pub use crate::extract_memos::extract_and_fmt_memos;
63use crate::parse_accounts::parse_legacy_message_accounts;
64use crate::parse_accounts::parse_v0_message_accounts;
65use crate::parse_instruction::parse;
66
67#[macro_use]
68extern crate serde_derive;
69
70pub mod extract_memos;
71pub mod parse_accounts;
72pub mod parse_address_lookup_table;
73pub mod parse_associated_token;
74pub mod parse_bpf_loader;
75pub mod parse_instruction;
76pub mod parse_stake;
77pub mod parse_system;
78pub mod parse_token;
79pub mod parse_vote;
80pub mod token_balances;
81
82pub struct BlockEncodingOptions {
83	pub transaction_details: TransactionDetails,
84	pub show_rewards: bool,
85	pub max_supported_transaction_version: Option<u8>,
86}
87
88/// Represents types that can be encoded into one of several encoding formats
89pub trait Encodable {
90	type Encoded;
91	fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded;
92}
93
94/// Represents types that can be encoded into one of several encoding formats
95pub trait EncodableWithMeta {
96	type Encoded;
97	fn encode_with_meta(
98		&self,
99		encoding: UiTransactionEncoding,
100		meta: &TransactionStatusMeta,
101	) -> Self::Encoded;
102	fn json_encode(&self) -> Self::Encoded;
103}
104
105trait JsonAccounts {
106	type Encoded;
107	fn build_json_accounts(&self) -> Self::Encoded;
108}
109
110fn make_ui_partially_decoded_instruction(
111	instruction: &CompiledInstruction,
112	account_keys: &AccountKeys,
113	stack_height: Option<u32>,
114) -> UiPartiallyDecodedInstruction {
115	UiPartiallyDecodedInstruction {
116		program_id: account_keys[instruction.program_id_index as usize],
117		accounts: instruction
118			.accounts
119			.iter()
120			.map(|&i| account_keys[i as usize])
121			.collect(),
122		data: bs58::encode(instruction.data.clone()).into_string(),
123		stack_height,
124	}
125}
126
127pub fn parse_ui_instruction(
128	instruction: &CompiledInstruction,
129	account_keys: &AccountKeys,
130	stack_height: Option<u32>,
131) -> UiInstruction {
132	let program_id = &account_keys[instruction.program_id_index as usize];
133	if let Ok(parsed_instruction) = parse(program_id, instruction, account_keys, stack_height) {
134		UiInstruction::Parsed(UiParsedInstruction::Parsed(parsed_instruction))
135	} else {
136		UiInstruction::Parsed(UiParsedInstruction::PartiallyDecoded(
137			make_ui_partially_decoded_instruction(instruction, account_keys, stack_height),
138		))
139	}
140}
141
142/// Maps a list of inner instructions from `solana_sdk` into a list of this
143/// crate's representation of inner instructions (with instruction indices).
144pub fn map_inner_instructions(
145	inner_instructions: solana_message::inner_instruction::InnerInstructionsList,
146) -> impl Iterator<Item = InnerInstructions> {
147	inner_instructions
148		.into_iter()
149		.enumerate()
150		.map(|(index, instructions)| {
151			InnerInstructions {
152				index: index as u8,
153				instructions: instructions
154					.into_iter()
155					.map(|info| {
156						InnerInstruction {
157							stack_height: Some(u32::from(info.stack_height)),
158							instruction: info.instruction,
159						}
160					})
161					.collect(),
162			}
163		})
164		.filter(|i| !i.instructions.is_empty())
165}
166
167pub fn parse_ui_inner_instructions(
168	inner_instructions: InnerInstructions,
169	account_keys: &AccountKeys,
170) -> UiInnerInstructions {
171	UiInnerInstructions {
172		index: inner_instructions.index,
173		instructions: inner_instructions
174			.instructions
175			.iter()
176			.map(
177				|InnerInstruction {
178				     instruction: ix,
179				     stack_height,
180				 }| { parse_ui_instruction(ix, account_keys, *stack_height) },
181			)
182			.collect(),
183	}
184}
185
186fn build_simple_ui_transaction_status_meta(
187	meta: TransactionStatusMeta,
188	show_rewards: bool,
189) -> UiTransactionStatusMeta {
190	UiTransactionStatusMeta {
191		err: meta.status.clone().map_err(Into::into).err(),
192		status: meta.status.map_err(Into::into),
193		fee: meta.fee,
194		pre_balances: meta.pre_balances,
195		post_balances: meta.post_balances,
196		inner_instructions: None,
197		log_messages: None,
198		pre_token_balances: meta
199			.pre_token_balances
200			.map(|balance| balance.into_iter().collect()),
201		post_token_balances: meta
202			.post_token_balances
203			.map(|balance| balance.into_iter().collect()),
204		rewards: if show_rewards { meta.rewards } else { None },
205		loaded_addresses: None,
206		return_data: None,
207		compute_units_consumed: None,
208		cost_units: None,
209	}
210}
211
212fn parse_ui_transaction_status_meta(
213	meta: TransactionStatusMeta,
214	static_keys: &[Pubkey],
215	show_rewards: bool,
216) -> UiTransactionStatusMeta {
217	let account_keys = AccountKeys::new(static_keys, Some(&meta.loaded_addresses));
218	UiTransactionStatusMeta {
219		err: meta.status.clone().map_err(Into::into).err(),
220		status: meta.status.map_err(Into::into),
221		fee: meta.fee,
222		pre_balances: meta.pre_balances,
223		post_balances: meta.post_balances,
224		inner_instructions: meta.inner_instructions.map(|ixs| {
225			ixs.into_iter()
226				.map(|ix| parse_ui_inner_instructions(ix, &account_keys))
227				.collect()
228		}),
229		log_messages: meta.log_messages,
230		pre_token_balances: meta
231			.pre_token_balances
232			.map(|balance| balance.into_iter().collect()),
233		post_token_balances: meta
234			.post_token_balances
235			.map(|balance| balance.into_iter().collect()),
236		rewards: if show_rewards { meta.rewards } else { None },
237		loaded_addresses: None,
238		return_data: meta.return_data.map(|return_data| return_data.into()),
239		compute_units_consumed: meta.compute_units_consumed,
240		cost_units: meta.cost_units,
241	}
242}
243
244#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
245pub struct RewardsAndNumPartitions {
246	pub rewards: Rewards,
247	pub num_partitions: Option<u64>,
248}
249
250#[derive(Debug, Error)]
251pub enum ConvertBlockError {
252	#[error("transactions missing after converted, before: {0}, after: {1}")]
253	TransactionsMissing(usize, usize),
254}
255
256#[derive(Clone, Debug, PartialEq)]
257pub struct ConfirmedBlock {
258	pub previous_blockhash: Hash,
259	pub blockhash: Hash,
260	pub parent_slot: Slot,
261	pub transactions: Vec<TransactionWithStatusMeta>,
262	pub rewards: Rewards,
263	pub num_partitions: Option<u64>,
264	pub block_time: Option<UnixTimestamp>,
265	pub block_height: Option<u64>,
266}
267
268// Confirmed block with type guarantees that transaction metadata
269// is always present. Used for uploading to BigTable.
270#[derive(Clone, Debug, PartialEq)]
271pub struct VersionedConfirmedBlock {
272	pub previous_blockhash: Hash,
273	pub blockhash: Hash,
274	pub parent_slot: Slot,
275	pub transactions: Vec<VersionedTransactionWithStatusMeta>,
276	pub rewards: Rewards,
277	pub num_partitions: Option<u64>,
278	pub block_time: Option<UnixTimestamp>,
279	pub block_height: Option<u64>,
280}
281
282impl From<VersionedConfirmedBlock> for ConfirmedBlock {
283	fn from(block: VersionedConfirmedBlock) -> Self {
284		Self {
285			previous_blockhash: block.previous_blockhash,
286			blockhash: block.blockhash,
287			parent_slot: block.parent_slot,
288			transactions: block
289				.transactions
290				.into_iter()
291				.map(TransactionWithStatusMeta::Complete)
292				.collect(),
293			rewards: block.rewards,
294			num_partitions: block.num_partitions,
295			block_time: block.block_time,
296			block_height: block.block_height,
297		}
298	}
299}
300
301impl TryFrom<ConfirmedBlock> for VersionedConfirmedBlock {
302	type Error = ConvertBlockError;
303
304	fn try_from(block: ConfirmedBlock) -> Result<Self, Self::Error> {
305		let expected_transaction_count = block.transactions.len();
306
307		let txs: Vec<_> = block
308			.transactions
309			.into_iter()
310			.filter_map(|tx| {
311				match tx {
312					TransactionWithStatusMeta::MissingMetadata(_) => None,
313					TransactionWithStatusMeta::Complete(tx) => Some(tx),
314				}
315			})
316			.collect();
317
318		if txs.len() != expected_transaction_count {
319			return Err(ConvertBlockError::TransactionsMissing(
320				expected_transaction_count,
321				txs.len(),
322			));
323		}
324
325		Ok(Self {
326			previous_blockhash: block.previous_blockhash,
327			blockhash: block.blockhash,
328			parent_slot: block.parent_slot,
329			transactions: txs,
330			rewards: block.rewards,
331			num_partitions: block.num_partitions,
332			block_time: block.block_time,
333			block_height: block.block_height,
334		})
335	}
336}
337
338impl ConfirmedBlock {
339	pub fn encode_with_options(
340		self,
341		encoding: UiTransactionEncoding,
342		options: BlockEncodingOptions,
343	) -> Result<UiConfirmedBlock, EncodeError> {
344		let (transactions, signatures) = match options.transaction_details {
345			TransactionDetails::Full => {
346				(
347					Some(
348						self.transactions
349							.into_iter()
350							.map(|tx_with_meta| {
351								tx_with_meta.encode(
352									encoding,
353									options.max_supported_transaction_version,
354									options.show_rewards,
355								)
356							})
357							.collect::<Result<Vec<_>, _>>()?,
358					),
359					None,
360				)
361			}
362			TransactionDetails::Signatures => {
363				(
364					None,
365					Some(
366						self.transactions
367							.into_iter()
368							.map(|tx_with_meta| *tx_with_meta.transaction_signature())
369							.collect(),
370					),
371				)
372			}
373			TransactionDetails::None => (None, None),
374			TransactionDetails::Accounts => {
375				(
376					Some(
377						self.transactions
378							.into_iter()
379							.map(|tx_with_meta| {
380								tx_with_meta.build_json_accounts(
381									options.max_supported_transaction_version,
382									options.show_rewards,
383								)
384							})
385							.collect::<Result<Vec<_>, _>>()?,
386					),
387					None,
388				)
389			}
390		};
391		Ok(UiConfirmedBlock {
392			previous_blockhash: self.previous_blockhash,
393			blockhash: self.blockhash,
394			parent_slot: self.parent_slot,
395			transactions,
396			signatures,
397			rewards: if options.show_rewards {
398				Some(self.rewards)
399			} else {
400				None
401			},
402			num_reward_partitions: self.num_partitions,
403			block_time: self.block_time,
404			block_height: self.block_height,
405		})
406	}
407}
408
409// Confirmed block with type guarantees that transaction metadata is always
410// present, as well as a list of the entry data needed to cryptographically
411// verify the block. Used for uploading to BigTable.
412pub struct VersionedConfirmedBlockWithEntries {
413	pub block: VersionedConfirmedBlock,
414	pub entries: Vec<EntrySummary>,
415}
416
417// Data needed to reconstruct an Entry, given an ordered list of transactions in
418// a block. Used for uploading to BigTable.
419pub struct EntrySummary {
420	pub num_hashes: u64,
421	pub hash: Hash,
422	pub num_transactions: u64,
423	pub starting_transaction_index: usize,
424}
425
426#[derive(Clone, Debug, PartialEq)]
427#[allow(clippy::large_enum_variant)]
428pub enum TransactionWithStatusMeta {
429	// Very old transactions may be missing metadata
430	MissingMetadata(Transaction),
431	// Versioned stored transaction always have metadata
432	Complete(VersionedTransactionWithStatusMeta),
433}
434
435#[derive(Clone, Debug, PartialEq)]
436pub struct VersionedTransactionWithStatusMeta {
437	pub transaction: VersionedTransaction,
438	pub meta: TransactionStatusMeta,
439}
440
441impl TransactionWithStatusMeta {
442	pub fn get_status_meta(&self) -> Option<TransactionStatusMeta> {
443		match self {
444			Self::MissingMetadata(_) => None,
445			Self::Complete(tx_with_meta) => Some(tx_with_meta.meta.clone()),
446		}
447	}
448
449	pub fn get_transaction(&self) -> VersionedTransaction {
450		match self {
451			Self::MissingMetadata(transaction) => VersionedTransaction::from(transaction.clone()),
452			Self::Complete(tx_with_meta) => tx_with_meta.transaction.clone(),
453		}
454	}
455
456	pub fn transaction_signature(&self) -> &Signature {
457		match self {
458			Self::MissingMetadata(transaction) => &transaction.signatures[0],
459			Self::Complete(VersionedTransactionWithStatusMeta { transaction, .. }) => {
460				&transaction.signatures[0]
461			}
462		}
463	}
464
465	pub fn encode(
466		self,
467		encoding: UiTransactionEncoding,
468		max_supported_transaction_version: Option<u8>,
469		show_rewards: bool,
470	) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
471		match self {
472			Self::MissingMetadata(ref transaction) => {
473				Ok(EncodedTransactionWithStatusMeta {
474					version: None,
475					transaction: transaction.encode(encoding),
476					meta: None,
477				})
478			}
479			Self::Complete(tx_with_meta) => {
480				tx_with_meta.encode(encoding, max_supported_transaction_version, show_rewards)
481			}
482		}
483	}
484
485	pub fn account_keys(&'_ self) -> AccountKeys<'_> {
486		match self {
487			Self::MissingMetadata(tx) => AccountKeys::new(&tx.message.account_keys, None),
488			Self::Complete(tx_with_meta) => tx_with_meta.account_keys(),
489		}
490	}
491
492	fn build_json_accounts(
493		self,
494		max_supported_transaction_version: Option<u8>,
495		show_rewards: bool,
496	) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
497		match self {
498			Self::MissingMetadata(ref transaction) => {
499				Ok(EncodedTransactionWithStatusMeta {
500					version: None,
501					transaction: transaction.build_json_accounts(),
502					meta: None,
503				})
504			}
505			Self::Complete(tx_with_meta) => {
506				tx_with_meta.build_json_accounts(max_supported_transaction_version, show_rewards)
507			}
508		}
509	}
510}
511
512impl VersionedTransactionWithStatusMeta {
513	fn validate_version(
514		&self,
515		max_supported_transaction_version: Option<u8>,
516	) -> Result<Option<TransactionVersion>, EncodeError> {
517		match (
518			max_supported_transaction_version,
519			self.transaction.version(),
520		) {
521			// Set to none because old clients can't handle this field
522			(None, TransactionVersion::LEGACY) => Ok(None),
523			(None, TransactionVersion::Number(version)) => {
524				Err(EncodeError::UnsupportedTransactionVersion(version))
525			}
526			(Some(_), TransactionVersion::LEGACY) => Ok(Some(TransactionVersion::LEGACY)),
527			(Some(max_version), TransactionVersion::Number(version)) => {
528				if version <= max_version {
529					Ok(Some(TransactionVersion::Number(version)))
530				} else {
531					Err(EncodeError::UnsupportedTransactionVersion(version))
532				}
533			}
534		}
535	}
536
537	pub fn encode(
538		self,
539		encoding: UiTransactionEncoding,
540		max_supported_transaction_version: Option<u8>,
541		show_rewards: bool,
542	) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
543		let version = self.validate_version(max_supported_transaction_version)?;
544
545		Ok(EncodedTransactionWithStatusMeta {
546			transaction: self.transaction.encode_with_meta(encoding, &self.meta),
547			meta: Some(match encoding {
548				UiTransactionEncoding::JsonParsed => {
549					parse_ui_transaction_status_meta(
550						self.meta,
551						self.transaction.message.static_account_keys(),
552						show_rewards,
553					)
554				}
555				_ => {
556					let mut meta = UiTransactionStatusMeta::from(self.meta);
557					if !show_rewards {
558						meta.rewards = None;
559					}
560					meta
561				}
562			}),
563			version,
564		})
565	}
566
567	pub fn account_keys(&'_ self) -> AccountKeys<'_> {
568		AccountKeys::new(
569			self.transaction.message.static_account_keys(),
570			Some(&self.meta.loaded_addresses),
571		)
572	}
573
574	fn build_json_accounts(
575		self,
576		max_supported_transaction_version: Option<u8>,
577		show_rewards: bool,
578	) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
579		let version = self.validate_version(max_supported_transaction_version)?;
580		let reserved_account_keys = ReservedAccountKeys::new_all_activated();
581
582		let account_keys = match &self.transaction.message {
583			VersionedMessage::Legacy(message) => parse_legacy_message_accounts(message),
584			VersionedMessage::V0(message) => {
585				let loaded_message = LoadedMessage::new_borrowed(
586					message,
587					&self.meta.loaded_addresses,
588					&reserved_account_keys.active,
589				);
590				parse_v0_message_accounts(&loaded_message)
591			}
592		};
593
594		Ok(EncodedTransactionWithStatusMeta {
595			transaction: EncodedTransaction::Accounts(UiAccountsList {
596				signatures: self.transaction.signatures.clone(),
597				account_keys,
598			}),
599			meta: Some(build_simple_ui_transaction_status_meta(
600				self.meta,
601				show_rewards,
602			)),
603			version,
604		})
605	}
606}
607
608#[derive(Debug, Clone, PartialEq)]
609pub struct ConfirmedTransactionWithStatusMeta {
610	pub slot: Slot,
611	pub tx_with_meta: TransactionWithStatusMeta,
612	pub block_time: Option<UnixTimestamp>,
613}
614
615#[derive(Debug, Clone, PartialEq)]
616pub struct VersionedConfirmedTransactionWithStatusMeta {
617	pub slot: Slot,
618	pub tx_with_meta: VersionedTransactionWithStatusMeta,
619	pub block_time: Option<UnixTimestamp>,
620}
621
622impl ConfirmedTransactionWithStatusMeta {
623	pub fn encode(
624		self,
625		encoding: UiTransactionEncoding,
626		max_supported_transaction_version: Option<u8>,
627	) -> Result<EncodedConfirmedTransactionWithStatusMeta, EncodeError> {
628		Ok(EncodedConfirmedTransactionWithStatusMeta {
629			slot: self.slot,
630			transaction: self.tx_with_meta.encode(
631				encoding,
632				max_supported_transaction_version,
633				true,
634			)?,
635			block_time: self.block_time,
636		})
637	}
638
639	pub fn get_transaction(&self) -> VersionedTransaction {
640		self.tx_with_meta.get_transaction()
641	}
642}
643
644impl EncodableWithMeta for VersionedTransaction {
645	type Encoded = EncodedTransaction;
646
647	fn encode_with_meta(
648		&self,
649		encoding: UiTransactionEncoding,
650		meta: &TransactionStatusMeta,
651	) -> Self::Encoded {
652		match encoding {
653			UiTransactionEncoding::Binary => {
654				EncodedTransaction::LegacyBinary(
655					bs58::encode(bincode::serialize(self).unwrap()).into_string(),
656				)
657			}
658			UiTransactionEncoding::Base58 => {
659				EncodedTransaction::Binary(
660					bs58::encode(bincode::serialize(self).unwrap()).into_string(),
661					TransactionBinaryEncoding::Base58,
662				)
663			}
664			UiTransactionEncoding::Base64 => {
665				EncodedTransaction::Binary(
666					BASE64_STANDARD.encode(bincode::serialize(self).unwrap()),
667					TransactionBinaryEncoding::Base64,
668				)
669			}
670			UiTransactionEncoding::Json => self.json_encode(),
671			UiTransactionEncoding::JsonParsed => {
672				EncodedTransaction::Json(UiTransaction {
673					signatures: self.signatures.clone(),
674					message: match &self.message {
675						VersionedMessage::Legacy(message) => {
676							message.encode(UiTransactionEncoding::JsonParsed)
677						}
678						VersionedMessage::V0(message) => {
679							message.encode_with_meta(UiTransactionEncoding::JsonParsed, meta)
680						}
681					},
682				})
683			}
684		}
685	}
686
687	fn json_encode(&self) -> Self::Encoded {
688		EncodedTransaction::Json(UiTransaction {
689			signatures: self.signatures.clone(),
690			message: match &self.message {
691				VersionedMessage::Legacy(message) => message.encode(UiTransactionEncoding::Json),
692				VersionedMessage::V0(message) => message.json_encode(),
693			},
694		})
695	}
696}
697
698impl Encodable for VersionedTransaction {
699	type Encoded = EncodedTransaction;
700
701	fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
702		match encoding {
703			UiTransactionEncoding::Binary => {
704				EncodedTransaction::LegacyBinary(
705					bs58::encode(bincode::serialize(self).unwrap()).into_string(),
706				)
707			}
708			UiTransactionEncoding::Base58 => {
709				EncodedTransaction::Binary(
710					bs58::encode(bincode::serialize(self).unwrap()).into_string(),
711					TransactionBinaryEncoding::Base58,
712				)
713			}
714			UiTransactionEncoding::Base64 => {
715				EncodedTransaction::Binary(
716					BASE64_STANDARD.encode(bincode::serialize(self).unwrap()),
717					TransactionBinaryEncoding::Base64,
718				)
719			}
720			UiTransactionEncoding::Json | UiTransactionEncoding::JsonParsed => {
721				EncodedTransaction::Json(UiTransaction {
722					signatures: self.signatures.clone(),
723					message: match &self.message {
724						VersionedMessage::Legacy(message) => {
725							message.encode(UiTransactionEncoding::JsonParsed)
726						}
727						VersionedMessage::V0(message) => {
728							message.encode(UiTransactionEncoding::JsonParsed)
729						}
730					},
731				})
732			}
733		}
734	}
735}
736
737impl Encodable for Transaction {
738	type Encoded = EncodedTransaction;
739
740	fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
741		match encoding {
742			UiTransactionEncoding::Binary => {
743				EncodedTransaction::LegacyBinary(
744					bs58::encode(bincode::serialize(self).unwrap()).into_string(),
745				)
746			}
747			UiTransactionEncoding::Base58 => {
748				EncodedTransaction::Binary(
749					bs58::encode(bincode::serialize(self).unwrap()).into_string(),
750					TransactionBinaryEncoding::Base58,
751				)
752			}
753			UiTransactionEncoding::Base64 => {
754				EncodedTransaction::Binary(
755					BASE64_STANDARD.encode(bincode::serialize(self).unwrap()),
756					TransactionBinaryEncoding::Base64,
757				)
758			}
759			UiTransactionEncoding::Json | UiTransactionEncoding::JsonParsed => {
760				EncodedTransaction::Json(UiTransaction {
761					signatures: self.signatures.clone(),
762					message: self.message.encode(encoding),
763				})
764			}
765		}
766	}
767}
768
769impl JsonAccounts for Transaction {
770	type Encoded = EncodedTransaction;
771
772	fn build_json_accounts(&self) -> Self::Encoded {
773		EncodedTransaction::Accounts(UiAccountsList {
774			signatures: self.signatures.clone(),
775			account_keys: parse_legacy_message_accounts(&self.message),
776		})
777	}
778}
779
780impl Encodable for Message {
781	type Encoded = UiMessage;
782
783	fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
784		if encoding == UiTransactionEncoding::JsonParsed {
785			let account_keys = AccountKeys::new(&self.account_keys, None);
786			UiMessage::Parsed(UiParsedMessage {
787				account_keys: parse_legacy_message_accounts(self),
788				recent_blockhash: self.recent_blockhash,
789				instructions: self
790					.instructions
791					.iter()
792					.map(|instruction| {
793						parse_ui_instruction(
794							instruction,
795							&account_keys,
796							Some(TRANSACTION_LEVEL_STACK_HEIGHT as u32),
797						)
798					})
799					.collect(),
800				address_table_lookups: None,
801			})
802		} else {
803			UiMessage::Raw(UiRawMessage {
804				header: self.header,
805				account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
806				recent_blockhash: self.recent_blockhash.to_string(),
807				instructions: self
808					.instructions
809					.iter()
810					.map(|ix| {
811						UiCompiledInstruction::from(ix, Some(TRANSACTION_LEVEL_STACK_HEIGHT as u32))
812					})
813					.collect(),
814				address_table_lookups: None,
815			})
816		}
817	}
818}
819
820impl Encodable for v0::Message {
821	type Encoded = UiMessage;
822
823	fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
824		if encoding == UiTransactionEncoding::JsonParsed {
825			let account_keys = AccountKeys::new(&self.account_keys, None);
826			let loaded_addresses = LoadedAddresses::default();
827			let loaded_message =
828				LoadedMessage::new_borrowed(self, &loaded_addresses, &HashSet::new());
829			UiMessage::Parsed(UiParsedMessage {
830				account_keys: parse_v0_message_accounts(&loaded_message),
831				recent_blockhash: self.recent_blockhash,
832				instructions: self
833					.instructions
834					.iter()
835					.map(|instruction| {
836						parse_ui_instruction(
837							instruction,
838							&account_keys,
839							Some(TRANSACTION_LEVEL_STACK_HEIGHT as u32),
840						)
841					})
842					.collect(),
843				address_table_lookups: None,
844			})
845		} else {
846			UiMessage::Raw(UiRawMessage {
847				header: self.header,
848				account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
849				recent_blockhash: self.recent_blockhash.to_string(),
850				instructions: self
851					.instructions
852					.iter()
853					.map(|ix| {
854						UiCompiledInstruction::from(ix, Some(TRANSACTION_LEVEL_STACK_HEIGHT as u32))
855					})
856					.collect(),
857				address_table_lookups: None,
858			})
859		}
860	}
861}
862
863impl EncodableWithMeta for v0::Message {
864	type Encoded = UiMessage;
865
866	fn encode_with_meta(
867		&self,
868		encoding: UiTransactionEncoding,
869		meta: &TransactionStatusMeta,
870	) -> Self::Encoded {
871		if encoding == UiTransactionEncoding::JsonParsed {
872			let reserved_account_keys = ReservedAccountKeys::new_all_activated();
873			let account_keys = AccountKeys::new(&self.account_keys, Some(&meta.loaded_addresses));
874			let loaded_message = LoadedMessage::new_borrowed(
875				self,
876				&meta.loaded_addresses,
877				&reserved_account_keys.active,
878			);
879			UiMessage::Parsed(UiParsedMessage {
880				account_keys: parse_v0_message_accounts(&loaded_message),
881				recent_blockhash: self.recent_blockhash,
882				instructions: self
883					.instructions
884					.iter()
885					.map(|instruction| {
886						parse_ui_instruction(
887							instruction,
888							&account_keys,
889							Some(TRANSACTION_LEVEL_STACK_HEIGHT as u32),
890						)
891					})
892					.collect(),
893				address_table_lookups: Some(
894					self.address_table_lookups.iter().map(Into::into).collect(),
895				),
896			})
897		} else {
898			self.json_encode()
899		}
900	}
901
902	fn json_encode(&self) -> Self::Encoded {
903		UiMessage::Raw(UiRawMessage {
904			header: self.header,
905			account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
906			recent_blockhash: self.recent_blockhash.to_string(),
907			instructions: self
908				.instructions
909				.iter()
910				.map(|ix| {
911					UiCompiledInstruction::from(ix, Some(TRANSACTION_LEVEL_STACK_HEIGHT as u32))
912				})
913				.collect(),
914			address_table_lookups: Some(
915				self.address_table_lookups.iter().map(Into::into).collect(),
916			),
917		})
918	}
919}
920
921// A serialized `Vec<TransactionByAddrInfo>` is stored in the `tx-by-addr`
922// table.  The row keys are the one's compliment of the slot so that rows may be
923// listed in reverse order
924#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
925pub struct TransactionByAddrInfo {
926	pub signature: Signature,          // The transaction signature
927	pub err: Option<TransactionError>, // None if the transaction executed successfully
928	pub index: u32,                    // Where the transaction is located in the block
929	pub memo: Option<String>,          // Transaction memo
930	pub block_time: Option<UnixTimestamp>,
931}
932
933#[cfg(test)]
934mod test {
935	use super::*;
936
937	#[test]
938	fn test_ui_transaction_status_meta_ctors_serialization() {
939		let meta = TransactionStatusMeta {
940			status: Ok(()),
941			fee: 1234,
942			pre_balances: vec![1, 2, 3],
943			post_balances: vec![4, 5, 6],
944			inner_instructions: None,
945			log_messages: None,
946			pre_token_balances: None,
947			post_token_balances: None,
948			rewards: None,
949			loaded_addresses: LoadedAddresses {
950				writable: vec![],
951				readonly: vec![],
952			},
953			return_data: None,
954			compute_units_consumed: None,
955			cost_units: None,
956		};
957		#[rustfmt::skip]
958		let expected_json_output_value: serde_json::Value = serde_json::from_str(
959			"{\
960             \"err\":null,\
961             \"status\":{\"Ok\":null},\
962             \"fee\":1234,\
963             \"preBalances\":[1,2,3],\
964             \"postBalances\":[4,5,6],\
965             \"innerInstructions\":null,\
966             \"logMessages\":null,\
967             \"preTokenBalances\":null,\
968             \"postTokenBalances\":null,\
969             \"rewards\":null,\
970             \"loadedAddresses\":{\
971                 \"readonly\": [],\
972                 \"writable\": []\
973             }\
974             }",
975		)
976		.unwrap();
977		let ui_meta_from: UiTransactionStatusMeta = meta.clone().into();
978		assert_eq!(
979			serde_json::to_value(ui_meta_from).unwrap(),
980			expected_json_output_value
981		);
982
983		#[rustfmt::skip]
984		let expected_json_output_value: serde_json::Value = serde_json::from_str(
985			"{\
986             \"err\":null,\
987             \"status\":{\"Ok\":null},\
988             \"fee\":1234,\
989             \"preBalances\":[1,2,3],\
990             \"postBalances\":[4,5,6],\
991             \"innerInstructions\":null,\
992             \"logMessages\":null,\
993             \"preTokenBalances\":null,\
994             \"postTokenBalances\":null,\
995             \"rewards\":null\
996             }",
997		)
998		.unwrap();
999		let ui_meta_parse_with_rewards = parse_ui_transaction_status_meta(meta.clone(), &[], true);
1000		assert_eq!(
1001			serde_json::to_value(ui_meta_parse_with_rewards).unwrap(),
1002			expected_json_output_value
1003		);
1004
1005		let ui_meta_parse_no_rewards = parse_ui_transaction_status_meta(meta, &[], false);
1006		assert_eq!(
1007			serde_json::to_value(ui_meta_parse_no_rewards).unwrap(),
1008			expected_json_output_value
1009		);
1010	}
1011}