2021-02-16 13:48:20 -08:00
|
|
|
#![allow(clippy::integer_arithmetic)]
|
2020-06-19 15:15:13 -07:00
|
|
|
|
2022-08-04 23:20:27 -07:00
|
|
|
pub use {crate::extract_memos::extract_and_fmt_memos, solana_sdk::reward_type::RewardType};
|
2021-10-13 20:46:52 -07:00
|
|
|
use {
|
|
|
|
crate::{
|
2022-09-14 15:58:50 -07:00
|
|
|
option_serializer::OptionSerializer,
|
2022-09-06 08:40:39 -07:00
|
|
|
parse_accounts::{parse_legacy_message_accounts, parse_v0_message_accounts, ParsedAccount},
|
2021-10-13 20:46:52 -07:00
|
|
|
parse_instruction::{parse, ParsedInstruction},
|
|
|
|
},
|
2023-05-11 11:34:58 -07:00
|
|
|
base64::{prelude::BASE64_STANDARD, Engine},
|
2021-10-13 20:46:52 -07:00
|
|
|
solana_account_decoder::parse_token::UiTokenAmount,
|
|
|
|
solana_sdk::{
|
|
|
|
clock::{Slot, UnixTimestamp},
|
|
|
|
commitment_config::CommitmentConfig,
|
|
|
|
instruction::CompiledInstruction,
|
2022-03-07 23:20:34 -08:00
|
|
|
message::{
|
|
|
|
v0::{self, LoadedAddresses, LoadedMessage, MessageAddressTableLookup},
|
|
|
|
AccountKeys, Message, MessageHeader, VersionedMessage,
|
|
|
|
},
|
|
|
|
pubkey::Pubkey,
|
2021-10-13 20:46:52 -07:00
|
|
|
signature::Signature,
|
2022-03-07 23:20:34 -08:00
|
|
|
transaction::{
|
|
|
|
Result as TransactionResult, Transaction, TransactionError, TransactionVersion,
|
|
|
|
VersionedTransaction,
|
|
|
|
},
|
2022-03-22 15:17:05 -07:00
|
|
|
transaction_context::TransactionReturnData,
|
2021-10-13 20:46:52 -07:00
|
|
|
},
|
|
|
|
std::fmt,
|
2022-03-07 23:20:34 -08:00
|
|
|
thiserror::Error,
|
2020-08-04 23:58:58 -07:00
|
|
|
};
|
2022-01-03 07:45:18 -08:00
|
|
|
|
2022-03-16 20:43:04 -07:00
|
|
|
#[macro_use]
|
|
|
|
extern crate lazy_static;
|
|
|
|
#[macro_use]
|
|
|
|
extern crate serde_derive;
|
|
|
|
|
|
|
|
pub mod extract_memos;
|
2022-09-14 15:58:50 -07:00
|
|
|
pub mod option_serializer;
|
2022-03-16 20:43:04 -07:00
|
|
|
pub mod parse_accounts;
|
2022-08-24 09:17:53 -07:00
|
|
|
pub mod parse_address_lookup_table;
|
2022-03-16 20:43:04 -07:00
|
|
|
pub mod parse_associated_token;
|
|
|
|
pub mod parse_bpf_loader;
|
|
|
|
pub mod parse_instruction;
|
|
|
|
pub mod parse_stake;
|
|
|
|
pub mod parse_system;
|
|
|
|
pub mod parse_token;
|
|
|
|
pub mod parse_vote;
|
|
|
|
pub mod token_balances;
|
|
|
|
|
2022-03-07 23:20:34 -08:00
|
|
|
pub struct BlockEncodingOptions {
|
|
|
|
pub transaction_details: TransactionDetails,
|
|
|
|
pub show_rewards: bool,
|
|
|
|
pub max_supported_transaction_version: Option<u8>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Error, Debug, PartialEq, Eq, Clone)]
|
|
|
|
pub enum EncodeError {
|
|
|
|
#[error("Encoding does not support transaction version {0}")]
|
|
|
|
UnsupportedTransactionVersion(u8),
|
|
|
|
}
|
|
|
|
|
2022-01-03 07:45:18 -08:00
|
|
|
/// Represents types that can be encoded into one of several encoding formats
|
|
|
|
pub trait Encodable {
|
|
|
|
type Encoded;
|
2022-03-07 23:20:34 -08:00
|
|
|
fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Represents types that can be encoded into one of several encoding formats
|
|
|
|
pub trait EncodableWithMeta {
|
|
|
|
type Encoded;
|
|
|
|
fn encode_with_meta(
|
|
|
|
&self,
|
|
|
|
encoding: UiTransactionEncoding,
|
|
|
|
meta: &TransactionStatusMeta,
|
|
|
|
) -> Self::Encoded;
|
2022-03-16 20:43:04 -07:00
|
|
|
fn json_encode(&self) -> Self::Encoded;
|
2022-01-03 07:45:18 -08:00
|
|
|
}
|
|
|
|
|
2022-09-15 09:00:30 -07:00
|
|
|
trait JsonAccounts {
|
|
|
|
type Encoded;
|
|
|
|
fn build_json_accounts(&self) -> Self::Encoded;
|
|
|
|
}
|
|
|
|
|
2022-03-09 00:09:08 -08:00
|
|
|
#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub enum TransactionBinaryEncoding {
|
|
|
|
Base58,
|
|
|
|
Base64,
|
|
|
|
}
|
|
|
|
|
2022-01-03 07:45:18 -08:00
|
|
|
#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub enum UiTransactionEncoding {
|
|
|
|
Binary, // Legacy. Retained for RPC backwards compatibility
|
|
|
|
Base64,
|
|
|
|
Base58,
|
|
|
|
Json,
|
|
|
|
JsonParsed,
|
|
|
|
}
|
|
|
|
|
2022-03-09 00:09:08 -08:00
|
|
|
impl UiTransactionEncoding {
|
|
|
|
pub fn into_binary_encoding(&self) -> Option<TransactionBinaryEncoding> {
|
|
|
|
match self {
|
|
|
|
Self::Binary | Self::Base58 => Some(TransactionBinaryEncoding::Base58),
|
|
|
|
Self::Base64 => Some(TransactionBinaryEncoding::Base64),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-03 07:45:18 -08:00
|
|
|
impl fmt::Display for UiTransactionEncoding {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
let v = serde_json::to_value(self).map_err(|_| fmt::Error)?;
|
|
|
|
let s = v.as_str().ok_or(fmt::Error)?;
|
2022-12-06 06:30:06 -08:00
|
|
|
write!(f, "{s}")
|
2022-01-03 07:45:18 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub enum TransactionDetails {
|
|
|
|
Full,
|
|
|
|
Signatures,
|
|
|
|
None,
|
2022-09-15 09:00:30 -07:00
|
|
|
Accounts,
|
2022-01-03 07:45:18 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for TransactionDetails {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::Full
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-01 13:06:40 -07:00
|
|
|
/// A duplicate representation of an Instruction for pretty JSON serialization
|
2022-05-22 18:00:42 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
2020-06-19 15:15:13 -07:00
|
|
|
#[serde(rename_all = "camelCase", untagged)]
|
2020-07-01 13:06:40 -07:00
|
|
|
pub enum UiInstruction {
|
|
|
|
Compiled(UiCompiledInstruction),
|
2020-08-04 23:58:58 -07:00
|
|
|
Parsed(UiParsedInstruction),
|
|
|
|
}
|
|
|
|
|
2020-09-24 07:36:22 -07:00
|
|
|
impl UiInstruction {
|
2022-10-25 19:37:44 -07:00
|
|
|
fn parse(
|
|
|
|
instruction: &CompiledInstruction,
|
|
|
|
account_keys: &AccountKeys,
|
|
|
|
stack_height: Option<u32>,
|
|
|
|
) -> Self {
|
2022-02-05 04:00:31 -08:00
|
|
|
let program_id = &account_keys[instruction.program_id_index as usize];
|
2022-10-25 19:37:44 -07:00
|
|
|
if let Ok(parsed_instruction) = parse(program_id, instruction, account_keys, stack_height) {
|
2020-09-24 07:36:22 -07:00
|
|
|
UiInstruction::Parsed(UiParsedInstruction::Parsed(parsed_instruction))
|
|
|
|
} else {
|
|
|
|
UiInstruction::Parsed(UiParsedInstruction::PartiallyDecoded(
|
2022-10-25 19:37:44 -07:00
|
|
|
UiPartiallyDecodedInstruction::from(instruction, account_keys, stack_height),
|
2020-09-24 07:36:22 -07:00
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-22 18:00:42 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
2022-01-03 07:45:18 -08:00
|
|
|
#[serde(rename_all = "camelCase", untagged)]
|
|
|
|
pub enum UiParsedInstruction {
|
|
|
|
Parsed(ParsedInstruction),
|
|
|
|
PartiallyDecoded(UiPartiallyDecodedInstruction),
|
|
|
|
}
|
|
|
|
|
2020-06-30 21:55:11 -07:00
|
|
|
/// A duplicate representation of a CompiledInstruction for pretty JSON serialization
|
2022-05-22 18:00:42 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
2020-03-26 13:29:30 -07:00
|
|
|
#[serde(rename_all = "camelCase")]
|
2020-07-01 13:06:40 -07:00
|
|
|
pub struct UiCompiledInstruction {
|
2020-03-26 13:29:30 -07:00
|
|
|
pub program_id_index: u8,
|
|
|
|
pub accounts: Vec<u8>,
|
|
|
|
pub data: String,
|
2022-10-25 19:37:44 -07:00
|
|
|
pub stack_height: Option<u32>,
|
2020-03-26 13:29:30 -07:00
|
|
|
}
|
|
|
|
|
2022-10-25 19:37:44 -07:00
|
|
|
impl UiCompiledInstruction {
|
|
|
|
fn from(instruction: &CompiledInstruction, stack_height: Option<u32>) -> Self {
|
2020-06-19 15:15:13 -07:00
|
|
|
Self {
|
|
|
|
program_id_index: instruction.program_id_index,
|
|
|
|
accounts: instruction.accounts.clone(),
|
2022-10-25 19:37:44 -07:00
|
|
|
data: bs58::encode(&instruction.data).into_string(),
|
|
|
|
stack_height,
|
2020-06-19 15:15:13 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-03 13:27:15 -07:00
|
|
|
/// A partially decoded CompiledInstruction that includes explicit account addresses
|
2022-05-22 18:00:42 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
2020-08-03 13:27:15 -07:00
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct UiPartiallyDecodedInstruction {
|
|
|
|
pub program_id: String,
|
|
|
|
pub accounts: Vec<String>,
|
|
|
|
pub data: String,
|
2022-10-25 19:37:44 -07:00
|
|
|
pub stack_height: Option<u32>,
|
2020-08-03 13:27:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl UiPartiallyDecodedInstruction {
|
2022-10-25 19:37:44 -07:00
|
|
|
fn from(
|
|
|
|
instruction: &CompiledInstruction,
|
|
|
|
account_keys: &AccountKeys,
|
|
|
|
stack_height: Option<u32>,
|
|
|
|
) -> Self {
|
2020-08-03 13:27:15 -07:00
|
|
|
Self {
|
|
|
|
program_id: account_keys[instruction.program_id_index as usize].to_string(),
|
|
|
|
accounts: instruction
|
|
|
|
.accounts
|
|
|
|
.iter()
|
|
|
|
.map(|&i| account_keys[i as usize].to_string())
|
|
|
|
.collect(),
|
|
|
|
data: bs58::encode(instruction.data.clone()).into_string(),
|
2022-10-25 19:37:44 -07:00
|
|
|
stack_height,
|
2020-08-03 13:27:15 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-22 18:00:42 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
2020-09-24 07:36:22 -07:00
|
|
|
pub struct InnerInstructions {
|
|
|
|
/// Transaction instruction index
|
|
|
|
pub index: u8,
|
|
|
|
/// List of inner instructions
|
2022-10-25 19:37:44 -07:00
|
|
|
pub instructions: Vec<InnerInstruction>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
|
|
pub struct InnerInstruction {
|
|
|
|
/// Compiled instruction
|
|
|
|
pub instruction: CompiledInstruction,
|
|
|
|
/// Invocation stack height of the instruction,
|
|
|
|
pub stack_height: Option<u32>,
|
2020-09-24 07:36:22 -07:00
|
|
|
}
|
|
|
|
|
2022-05-22 18:00:42 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
2020-09-24 07:36:22 -07:00
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct UiInnerInstructions {
|
|
|
|
/// Transaction instruction index
|
|
|
|
pub index: u8,
|
|
|
|
/// List of inner instructions
|
|
|
|
pub instructions: Vec<UiInstruction>,
|
|
|
|
}
|
|
|
|
|
2022-01-03 07:45:18 -08:00
|
|
|
impl UiInnerInstructions {
|
2022-03-07 23:20:34 -08:00
|
|
|
fn parse(inner_instructions: InnerInstructions, account_keys: &AccountKeys) -> Self {
|
2022-01-03 07:45:18 -08:00
|
|
|
Self {
|
|
|
|
index: inner_instructions.index,
|
|
|
|
instructions: inner_instructions
|
|
|
|
.instructions
|
|
|
|
.iter()
|
2022-10-25 19:37:44 -07:00
|
|
|
.map(
|
|
|
|
|InnerInstruction {
|
|
|
|
instruction: ix,
|
|
|
|
stack_height,
|
|
|
|
}| {
|
|
|
|
UiInstruction::parse(ix, account_keys, *stack_height)
|
|
|
|
},
|
|
|
|
)
|
2022-01-03 07:45:18 -08:00
|
|
|
.collect(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<InnerInstructions> for UiInnerInstructions {
|
|
|
|
fn from(inner_instructions: InnerInstructions) -> Self {
|
|
|
|
Self {
|
|
|
|
index: inner_instructions.index,
|
|
|
|
instructions: inner_instructions
|
|
|
|
.instructions
|
|
|
|
.iter()
|
2022-10-25 19:37:44 -07:00
|
|
|
.map(
|
|
|
|
|InnerInstruction {
|
|
|
|
instruction: ix,
|
|
|
|
stack_height,
|
|
|
|
}| {
|
|
|
|
UiInstruction::Compiled(UiCompiledInstruction::from(ix, *stack_height))
|
|
|
|
},
|
|
|
|
)
|
2022-01-03 07:45:18 -08:00
|
|
|
.collect(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-03 00:20:57 -08:00
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
2020-12-10 19:25:07 -08:00
|
|
|
pub struct TransactionTokenBalance {
|
|
|
|
pub account_index: u8,
|
|
|
|
pub mint: String,
|
|
|
|
pub ui_token_amount: UiTokenAmount,
|
2021-10-13 20:46:52 -07:00
|
|
|
pub owner: String,
|
2022-04-19 21:01:22 -07:00
|
|
|
pub program_id: String,
|
2020-12-10 19:25:07 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct UiTransactionTokenBalance {
|
|
|
|
pub account_index: u8,
|
|
|
|
pub mint: String,
|
|
|
|
pub ui_token_amount: UiTokenAmount,
|
2022-09-14 15:58:50 -07:00
|
|
|
#[serde(
|
|
|
|
default = "OptionSerializer::skip",
|
|
|
|
skip_serializing_if = "OptionSerializer::should_skip"
|
|
|
|
)]
|
|
|
|
pub owner: OptionSerializer<String>,
|
|
|
|
#[serde(
|
|
|
|
default = "OptionSerializer::skip",
|
|
|
|
skip_serializing_if = "OptionSerializer::should_skip"
|
|
|
|
)]
|
|
|
|
pub program_id: OptionSerializer<String>,
|
2020-12-10 19:25:07 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl From<TransactionTokenBalance> for UiTransactionTokenBalance {
|
|
|
|
fn from(token_balance: TransactionTokenBalance) -> Self {
|
|
|
|
Self {
|
|
|
|
account_index: token_balance.account_index,
|
|
|
|
mint: token_balance.mint,
|
|
|
|
ui_token_amount: token_balance.ui_token_amount,
|
2021-10-13 20:46:52 -07:00
|
|
|
owner: if !token_balance.owner.is_empty() {
|
2022-09-14 15:58:50 -07:00
|
|
|
OptionSerializer::Some(token_balance.owner)
|
2021-10-13 20:46:52 -07:00
|
|
|
} else {
|
2022-09-14 15:58:50 -07:00
|
|
|
OptionSerializer::Skip
|
2021-10-13 20:46:52 -07:00
|
|
|
},
|
2022-04-19 21:01:22 -07:00
|
|
|
program_id: if !token_balance.program_id.is_empty() {
|
2022-09-14 15:58:50 -07:00
|
|
|
OptionSerializer::Some(token_balance.program_id)
|
2022-04-19 21:01:22 -07:00
|
|
|
} else {
|
2022-09-14 15:58:50 -07:00
|
|
|
OptionSerializer::Skip
|
2022-04-19 21:01:22 -07:00
|
|
|
},
|
2020-12-10 19:25:07 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-03 00:20:57 -08:00
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
2020-03-26 13:29:30 -07:00
|
|
|
pub struct TransactionStatusMeta {
|
2022-03-07 23:20:34 -08:00
|
|
|
pub status: TransactionResult<()>,
|
2020-03-26 13:29:30 -07:00
|
|
|
pub fee: u64,
|
|
|
|
pub pre_balances: Vec<u64>,
|
|
|
|
pub post_balances: Vec<u64>,
|
2020-09-24 07:36:22 -07:00
|
|
|
pub inner_instructions: Option<Vec<InnerInstructions>>,
|
2020-10-08 12:06:15 -07:00
|
|
|
pub log_messages: Option<Vec<String>>,
|
2020-12-10 19:25:07 -08:00
|
|
|
pub pre_token_balances: Option<Vec<TransactionTokenBalance>>,
|
|
|
|
pub post_token_balances: Option<Vec<TransactionTokenBalance>>,
|
2021-05-26 14:43:15 -07:00
|
|
|
pub rewards: Option<Rewards>,
|
2022-01-13 23:24:41 -08:00
|
|
|
pub loaded_addresses: LoadedAddresses,
|
2022-03-22 15:17:05 -07:00
|
|
|
pub return_data: Option<TransactionReturnData>,
|
2022-08-06 10:14:31 -07:00
|
|
|
pub compute_units_consumed: Option<u64>,
|
2020-03-26 13:29:30 -07:00
|
|
|
}
|
|
|
|
|
2020-04-04 20:24:06 -07:00
|
|
|
impl Default for TransactionStatusMeta {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
status: Ok(()),
|
|
|
|
fee: 0,
|
|
|
|
pre_balances: vec![],
|
|
|
|
post_balances: vec![],
|
2020-09-24 07:36:22 -07:00
|
|
|
inner_instructions: None,
|
2020-10-08 12:06:15 -07:00
|
|
|
log_messages: None,
|
2020-12-10 19:25:07 -08:00
|
|
|
pre_token_balances: None,
|
|
|
|
post_token_balances: None,
|
2021-05-26 14:43:15 -07:00
|
|
|
rewards: None,
|
2022-01-13 23:24:41 -08:00
|
|
|
loaded_addresses: LoadedAddresses::default(),
|
2022-03-22 15:17:05 -07:00
|
|
|
return_data: None,
|
2022-08-06 10:14:31 -07:00
|
|
|
compute_units_consumed: None,
|
2020-04-04 20:24:06 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-01 13:06:40 -07:00
|
|
|
/// A duplicate representation of TransactionStatusMeta with `err` field
|
2020-04-04 16:13:26 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
2020-07-01 13:06:40 -07:00
|
|
|
pub struct UiTransactionStatusMeta {
|
2020-04-04 16:13:26 -07:00
|
|
|
pub err: Option<TransactionError>,
|
2022-03-07 23:20:34 -08:00
|
|
|
pub status: TransactionResult<()>, // This field is deprecated. See https://github.com/solana-labs/solana/issues/9302
|
2020-04-04 16:13:26 -07:00
|
|
|
pub fee: u64,
|
|
|
|
pub pre_balances: Vec<u64>,
|
|
|
|
pub post_balances: Vec<u64>,
|
2022-09-14 15:58:50 -07:00
|
|
|
#[serde(
|
|
|
|
default = "OptionSerializer::none",
|
|
|
|
skip_serializing_if = "OptionSerializer::should_skip"
|
|
|
|
)]
|
|
|
|
pub inner_instructions: OptionSerializer<Vec<UiInnerInstructions>>,
|
|
|
|
#[serde(
|
|
|
|
default = "OptionSerializer::none",
|
|
|
|
skip_serializing_if = "OptionSerializer::should_skip"
|
|
|
|
)]
|
|
|
|
pub log_messages: OptionSerializer<Vec<String>>,
|
|
|
|
#[serde(
|
|
|
|
default = "OptionSerializer::none",
|
|
|
|
skip_serializing_if = "OptionSerializer::should_skip"
|
|
|
|
)]
|
|
|
|
pub pre_token_balances: OptionSerializer<Vec<UiTransactionTokenBalance>>,
|
|
|
|
#[serde(
|
|
|
|
default = "OptionSerializer::none",
|
|
|
|
skip_serializing_if = "OptionSerializer::should_skip"
|
|
|
|
)]
|
|
|
|
pub post_token_balances: OptionSerializer<Vec<UiTransactionTokenBalance>>,
|
|
|
|
#[serde(
|
|
|
|
default = "OptionSerializer::none",
|
|
|
|
skip_serializing_if = "OptionSerializer::should_skip"
|
|
|
|
)]
|
|
|
|
pub rewards: OptionSerializer<Rewards>,
|
|
|
|
#[serde(
|
|
|
|
default = "OptionSerializer::skip",
|
|
|
|
skip_serializing_if = "OptionSerializer::should_skip"
|
|
|
|
)]
|
|
|
|
pub loaded_addresses: OptionSerializer<UiLoadedAddresses>,
|
|
|
|
#[serde(
|
|
|
|
default = "OptionSerializer::skip",
|
|
|
|
skip_serializing_if = "OptionSerializer::should_skip"
|
|
|
|
)]
|
|
|
|
pub return_data: OptionSerializer<UiTransactionReturnData>,
|
|
|
|
#[serde(
|
|
|
|
default = "OptionSerializer::skip",
|
|
|
|
skip_serializing_if = "OptionSerializer::should_skip"
|
|
|
|
)]
|
|
|
|
pub compute_units_consumed: OptionSerializer<u64>,
|
2022-03-07 23:20:34 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// A duplicate representation of LoadedAddresses
|
2022-05-22 18:00:42 -07:00
|
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
2022-03-07 23:20:34 -08:00
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct UiLoadedAddresses {
|
|
|
|
pub writable: Vec<String>,
|
|
|
|
pub readonly: Vec<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<&LoadedAddresses> for UiLoadedAddresses {
|
|
|
|
fn from(loaded_addresses: &LoadedAddresses) -> Self {
|
|
|
|
Self {
|
|
|
|
writable: loaded_addresses
|
|
|
|
.writable
|
|
|
|
.iter()
|
|
|
|
.map(ToString::to_string)
|
|
|
|
.collect(),
|
|
|
|
readonly: loaded_addresses
|
|
|
|
.readonly
|
|
|
|
.iter()
|
|
|
|
.map(ToString::to_string)
|
|
|
|
.collect(),
|
|
|
|
}
|
|
|
|
}
|
2020-09-24 07:36:22 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl UiTransactionStatusMeta {
|
2022-09-08 09:17:02 -07:00
|
|
|
fn parse(meta: TransactionStatusMeta, static_keys: &[Pubkey], show_rewards: bool) -> Self {
|
2022-03-07 23:20:34 -08:00
|
|
|
let account_keys = AccountKeys::new(static_keys, Some(&meta.loaded_addresses));
|
2020-09-24 07:36:22 -07:00
|
|
|
Self {
|
|
|
|
err: meta.status.clone().err(),
|
|
|
|
status: meta.status,
|
|
|
|
fee: meta.fee,
|
|
|
|
pre_balances: meta.pre_balances,
|
|
|
|
post_balances: meta.post_balances,
|
2022-09-14 15:58:50 -07:00
|
|
|
inner_instructions: meta
|
|
|
|
.inner_instructions
|
|
|
|
.map(|ixs| {
|
|
|
|
ixs.into_iter()
|
|
|
|
.map(|ix| UiInnerInstructions::parse(ix, &account_keys))
|
|
|
|
.collect()
|
|
|
|
})
|
|
|
|
.into(),
|
|
|
|
log_messages: meta.log_messages.into(),
|
2020-12-10 19:25:07 -08:00
|
|
|
pre_token_balances: meta
|
|
|
|
.pre_token_balances
|
2022-09-14 15:58:50 -07:00
|
|
|
.map(|balance| balance.into_iter().map(Into::into).collect())
|
|
|
|
.into(),
|
2020-12-10 19:25:07 -08:00
|
|
|
post_token_balances: meta
|
|
|
|
.post_token_balances
|
2022-09-14 15:58:50 -07:00
|
|
|
.map(|balance| balance.into_iter().map(Into::into).collect())
|
|
|
|
.into(),
|
|
|
|
rewards: if show_rewards { meta.rewards } else { None }.into(),
|
|
|
|
loaded_addresses: OptionSerializer::Skip,
|
|
|
|
return_data: OptionSerializer::or_skip(
|
|
|
|
meta.return_data.map(|return_data| return_data.into()),
|
|
|
|
),
|
|
|
|
compute_units_consumed: OptionSerializer::or_skip(meta.compute_units_consumed),
|
2020-09-24 07:36:22 -07:00
|
|
|
}
|
|
|
|
}
|
2022-09-15 09:00:30 -07:00
|
|
|
|
|
|
|
fn build_simple(meta: TransactionStatusMeta, show_rewards: bool) -> Self {
|
|
|
|
Self {
|
|
|
|
err: meta.status.clone().err(),
|
|
|
|
status: meta.status,
|
|
|
|
fee: meta.fee,
|
|
|
|
pre_balances: meta.pre_balances,
|
|
|
|
post_balances: meta.post_balances,
|
|
|
|
inner_instructions: OptionSerializer::Skip,
|
|
|
|
log_messages: OptionSerializer::Skip,
|
|
|
|
pre_token_balances: meta
|
|
|
|
.pre_token_balances
|
|
|
|
.map(|balance| balance.into_iter().map(Into::into).collect())
|
|
|
|
.into(),
|
|
|
|
post_token_balances: meta
|
|
|
|
.post_token_balances
|
|
|
|
.map(|balance| balance.into_iter().map(Into::into).collect())
|
|
|
|
.into(),
|
|
|
|
rewards: if show_rewards {
|
|
|
|
meta.rewards.into()
|
|
|
|
} else {
|
|
|
|
OptionSerializer::Skip
|
|
|
|
},
|
|
|
|
loaded_addresses: OptionSerializer::Skip,
|
|
|
|
return_data: OptionSerializer::Skip,
|
|
|
|
compute_units_consumed: OptionSerializer::Skip,
|
|
|
|
}
|
|
|
|
}
|
2020-04-04 16:13:26 -07:00
|
|
|
}
|
|
|
|
|
2020-07-01 13:06:40 -07:00
|
|
|
impl From<TransactionStatusMeta> for UiTransactionStatusMeta {
|
2020-04-04 16:13:26 -07:00
|
|
|
fn from(meta: TransactionStatusMeta) -> Self {
|
|
|
|
Self {
|
|
|
|
err: meta.status.clone().err(),
|
|
|
|
status: meta.status,
|
|
|
|
fee: meta.fee,
|
|
|
|
pre_balances: meta.pre_balances,
|
|
|
|
post_balances: meta.post_balances,
|
2020-09-24 07:36:22 -07:00
|
|
|
inner_instructions: meta
|
|
|
|
.inner_instructions
|
2022-09-14 15:58:50 -07:00
|
|
|
.map(|ixs| ixs.into_iter().map(Into::into).collect())
|
|
|
|
.into(),
|
|
|
|
log_messages: meta.log_messages.into(),
|
2020-12-10 19:25:07 -08:00
|
|
|
pre_token_balances: meta
|
|
|
|
.pre_token_balances
|
2022-09-14 15:58:50 -07:00
|
|
|
.map(|balance| balance.into_iter().map(Into::into).collect())
|
|
|
|
.into(),
|
2020-12-10 19:25:07 -08:00
|
|
|
post_token_balances: meta
|
|
|
|
.post_token_balances
|
2022-09-14 15:58:50 -07:00
|
|
|
.map(|balance| balance.into_iter().map(Into::into).collect())
|
|
|
|
.into(),
|
|
|
|
rewards: meta.rewards.into(),
|
|
|
|
loaded_addresses: Some(UiLoadedAddresses::from(&meta.loaded_addresses)).into(),
|
|
|
|
return_data: OptionSerializer::or_skip(
|
|
|
|
meta.return_data.map(|return_data| return_data.into()),
|
|
|
|
),
|
|
|
|
compute_units_consumed: OptionSerializer::or_skip(meta.compute_units_consumed),
|
2020-04-04 16:13:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-22 18:00:42 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
2021-01-15 08:05:05 -08:00
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub enum TransactionConfirmationStatus {
|
|
|
|
Processed,
|
|
|
|
Confirmed,
|
|
|
|
Finalized,
|
|
|
|
}
|
|
|
|
|
2022-05-22 18:00:42 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
2020-03-26 13:29:30 -07:00
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct TransactionStatus {
|
|
|
|
pub slot: Slot,
|
2022-03-07 23:20:34 -08:00
|
|
|
pub confirmations: Option<usize>, // None = rooted
|
|
|
|
pub status: TransactionResult<()>, // legacy field
|
2020-04-04 16:13:26 -07:00
|
|
|
pub err: Option<TransactionError>,
|
2021-01-15 08:05:05 -08:00
|
|
|
pub confirmation_status: Option<TransactionConfirmationStatus>,
|
2020-03-26 13:29:30 -07:00
|
|
|
}
|
|
|
|
|
2020-04-06 03:04:54 -07:00
|
|
|
impl TransactionStatus {
|
|
|
|
pub fn satisfies_commitment(&self, commitment_config: CommitmentConfig) -> bool {
|
2021-01-26 11:23:07 -08:00
|
|
|
if commitment_config.is_finalized() {
|
|
|
|
self.confirmations.is_none()
|
|
|
|
} else if commitment_config.is_confirmed() {
|
|
|
|
if let Some(status) = &self.confirmation_status {
|
|
|
|
*status != TransactionConfirmationStatus::Processed
|
|
|
|
} else {
|
|
|
|
// These fallback cases handle TransactionStatus RPC responses from older software
|
|
|
|
self.confirmations.is_some() && self.confirmations.unwrap() > 1
|
|
|
|
|| self.confirmations.is_none()
|
2021-01-15 08:05:05 -08:00
|
|
|
}
|
2021-01-26 11:23:07 -08:00
|
|
|
} else {
|
|
|
|
true
|
2021-01-15 08:05:05 -08:00
|
|
|
}
|
2020-04-06 03:04:54 -07:00
|
|
|
}
|
2021-01-20 17:32:48 -08:00
|
|
|
|
|
|
|
// Returns `confirmation_status`, or if is_none, determines the status from confirmations.
|
|
|
|
// Facilitates querying nodes on older software
|
|
|
|
pub fn confirmation_status(&self) -> TransactionConfirmationStatus {
|
|
|
|
match &self.confirmation_status {
|
|
|
|
Some(status) => status.clone(),
|
|
|
|
None => {
|
|
|
|
if self.confirmations.is_none() {
|
|
|
|
TransactionConfirmationStatus::Finalized
|
|
|
|
} else if self.confirmations.unwrap() > 0 {
|
|
|
|
TransactionConfirmationStatus::Confirmed
|
|
|
|
} else {
|
|
|
|
TransactionConfirmationStatus::Processed
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-04-06 03:04:54 -07:00
|
|
|
}
|
|
|
|
|
2022-05-22 18:00:42 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
2020-07-27 11:42:49 -07:00
|
|
|
pub struct ConfirmedTransactionStatusWithSignature {
|
|
|
|
pub signature: Signature,
|
|
|
|
pub slot: Slot,
|
|
|
|
pub err: Option<TransactionError>,
|
|
|
|
pub memo: Option<String>,
|
2021-01-20 22:10:35 -08:00
|
|
|
pub block_time: Option<UnixTimestamp>,
|
2020-07-27 11:42:49 -07:00
|
|
|
}
|
|
|
|
|
2022-05-22 18:00:42 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
2020-09-30 16:57:06 -07:00
|
|
|
#[serde(rename_all = "camelCase")]
|
2020-03-26 13:29:30 -07:00
|
|
|
pub struct Reward {
|
|
|
|
pub pubkey: String,
|
|
|
|
pub lamports: i64,
|
2020-09-30 16:57:06 -07:00
|
|
|
pub post_balance: u64, // Account balance in lamports after `lamports` was applied
|
2020-10-09 12:55:35 -07:00
|
|
|
pub reward_type: Option<RewardType>,
|
2021-07-10 23:18:42 -07:00
|
|
|
pub commission: Option<u8>, // Vote account commission when the reward was credited, only present for voting and staking rewards
|
2020-03-26 13:29:30 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
pub type Rewards = Vec<Reward>;
|
|
|
|
|
2023-01-08 19:23:35 -08:00
|
|
|
#[derive(Debug, Error)]
|
|
|
|
pub enum ConvertBlockError {
|
|
|
|
#[error("transactions missing after converted, before: {0}, after: {1}")]
|
|
|
|
TransactionsMissing(usize, usize),
|
|
|
|
}
|
|
|
|
|
2022-02-09 21:28:18 -08:00
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
2020-03-26 13:29:30 -07:00
|
|
|
pub struct ConfirmedBlock {
|
|
|
|
pub previous_blockhash: String,
|
|
|
|
pub blockhash: String,
|
|
|
|
pub parent_slot: Slot,
|
|
|
|
pub transactions: Vec<TransactionWithStatusMeta>,
|
|
|
|
pub rewards: Rewards,
|
2020-07-09 21:47:29 -07:00
|
|
|
pub block_time: Option<UnixTimestamp>,
|
2021-05-26 21:16:16 -07:00
|
|
|
pub block_height: Option<u64>,
|
2020-03-26 13:29:30 -07:00
|
|
|
}
|
|
|
|
|
2022-02-09 21:28:18 -08:00
|
|
|
// Confirmed block with type guarantees that transaction metadata
|
|
|
|
// is always present. Used for uploading to BigTable.
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
2022-01-13 23:24:41 -08:00
|
|
|
pub struct VersionedConfirmedBlock {
|
|
|
|
pub previous_blockhash: String,
|
|
|
|
pub blockhash: String,
|
|
|
|
pub parent_slot: Slot,
|
|
|
|
pub transactions: Vec<VersionedTransactionWithStatusMeta>,
|
|
|
|
pub rewards: Rewards,
|
|
|
|
pub block_time: Option<UnixTimestamp>,
|
|
|
|
pub block_height: Option<u64>,
|
|
|
|
}
|
|
|
|
|
2022-02-09 21:28:18 -08:00
|
|
|
impl From<VersionedConfirmedBlock> for ConfirmedBlock {
|
|
|
|
fn from(block: VersionedConfirmedBlock) -> Self {
|
|
|
|
Self {
|
2022-01-13 23:24:41 -08:00
|
|
|
previous_blockhash: block.previous_blockhash,
|
|
|
|
blockhash: block.blockhash,
|
|
|
|
parent_slot: block.parent_slot,
|
2022-02-09 21:28:18 -08:00
|
|
|
transactions: block
|
|
|
|
.transactions
|
|
|
|
.into_iter()
|
|
|
|
.map(TransactionWithStatusMeta::Complete)
|
|
|
|
.collect(),
|
2022-01-13 23:24:41 -08:00
|
|
|
rewards: block.rewards,
|
|
|
|
block_time: block.block_time,
|
|
|
|
block_height: block.block_height,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-08 19:23:35 -08:00
|
|
|
impl TryFrom<ConfirmedBlock> for VersionedConfirmedBlock {
|
|
|
|
type Error = ConvertBlockError;
|
|
|
|
|
|
|
|
fn try_from(block: ConfirmedBlock) -> Result<Self, Self::Error> {
|
|
|
|
let expected_transaction_count = block.transactions.len();
|
|
|
|
|
|
|
|
let txs: Vec<_> = block
|
|
|
|
.transactions
|
|
|
|
.into_iter()
|
|
|
|
.filter_map(|tx| match tx {
|
|
|
|
TransactionWithStatusMeta::MissingMetadata(_) => None,
|
|
|
|
TransactionWithStatusMeta::Complete(tx) => Some(tx),
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
if txs.len() != expected_transaction_count {
|
|
|
|
return Err(ConvertBlockError::TransactionsMissing(
|
|
|
|
expected_transaction_count,
|
|
|
|
txs.len(),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Self {
|
|
|
|
previous_blockhash: block.previous_blockhash,
|
|
|
|
blockhash: block.blockhash,
|
|
|
|
parent_slot: block.parent_slot,
|
|
|
|
transactions: txs,
|
|
|
|
rewards: block.rewards,
|
|
|
|
block_time: block.block_time,
|
|
|
|
block_height: block.block_height,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-07 23:20:34 -08:00
|
|
|
impl ConfirmedBlock {
|
|
|
|
pub fn encode_with_options(
|
2021-03-18 10:58:20 -07:00
|
|
|
self,
|
|
|
|
encoding: UiTransactionEncoding,
|
2022-03-07 23:20:34 -08:00
|
|
|
options: BlockEncodingOptions,
|
|
|
|
) -> Result<UiConfirmedBlock, EncodeError> {
|
|
|
|
let (transactions, signatures) = match options.transaction_details {
|
2021-03-18 10:58:20 -07:00
|
|
|
TransactionDetails::Full => (
|
|
|
|
Some(
|
|
|
|
self.transactions
|
|
|
|
.into_iter()
|
2022-03-07 23:20:34 -08:00
|
|
|
.map(|tx_with_meta| {
|
2022-09-08 09:17:02 -07:00
|
|
|
tx_with_meta.encode(
|
|
|
|
encoding,
|
|
|
|
options.max_supported_transaction_version,
|
|
|
|
options.show_rewards,
|
|
|
|
)
|
2022-03-07 23:20:34 -08:00
|
|
|
})
|
|
|
|
.collect::<Result<Vec<_>, _>>()?,
|
2021-03-18 10:58:20 -07:00
|
|
|
),
|
|
|
|
None,
|
|
|
|
),
|
|
|
|
TransactionDetails::Signatures => (
|
|
|
|
None,
|
|
|
|
Some(
|
|
|
|
self.transactions
|
|
|
|
.into_iter()
|
2022-03-07 23:20:34 -08:00
|
|
|
.map(|tx_with_meta| tx_with_meta.transaction_signature().to_string())
|
2021-03-18 10:58:20 -07:00
|
|
|
.collect(),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
TransactionDetails::None => (None, None),
|
2022-09-15 09:00:30 -07:00
|
|
|
TransactionDetails::Accounts => (
|
|
|
|
Some(
|
|
|
|
self.transactions
|
|
|
|
.into_iter()
|
|
|
|
.map(|tx_with_meta| {
|
|
|
|
tx_with_meta.build_json_accounts(
|
|
|
|
options.max_supported_transaction_version,
|
|
|
|
options.show_rewards,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.collect::<Result<Vec<_>, _>>()?,
|
|
|
|
),
|
|
|
|
None,
|
|
|
|
),
|
2021-03-18 10:58:20 -07:00
|
|
|
};
|
2022-03-07 23:20:34 -08:00
|
|
|
Ok(UiConfirmedBlock {
|
2021-03-18 10:58:20 -07:00
|
|
|
previous_blockhash: self.previous_blockhash,
|
|
|
|
blockhash: self.blockhash,
|
|
|
|
parent_slot: self.parent_slot,
|
|
|
|
transactions,
|
|
|
|
signatures,
|
2022-03-07 23:20:34 -08:00
|
|
|
rewards: if options.show_rewards {
|
2021-03-18 10:58:20 -07:00
|
|
|
Some(self.rewards)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
},
|
|
|
|
block_time: self.block_time,
|
2021-05-26 21:16:16 -07:00
|
|
|
block_height: self.block_height,
|
2022-03-07 23:20:34 -08:00
|
|
|
})
|
2021-03-18 10:58:20 -07:00
|
|
|
}
|
2020-09-23 22:10:29 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct EncodedConfirmedBlock {
|
|
|
|
pub previous_blockhash: String,
|
|
|
|
pub blockhash: String,
|
|
|
|
pub parent_slot: Slot,
|
|
|
|
pub transactions: Vec<EncodedTransactionWithStatusMeta>,
|
|
|
|
pub rewards: Rewards,
|
|
|
|
pub block_time: Option<UnixTimestamp>,
|
2021-05-26 21:16:16 -07:00
|
|
|
pub block_height: Option<u64>,
|
2020-09-23 22:10:29 -07:00
|
|
|
}
|
|
|
|
|
2022-01-03 07:45:18 -08:00
|
|
|
impl From<UiConfirmedBlock> for EncodedConfirmedBlock {
|
|
|
|
fn from(block: UiConfirmedBlock) -> Self {
|
|
|
|
Self {
|
|
|
|
previous_blockhash: block.previous_blockhash,
|
|
|
|
blockhash: block.blockhash,
|
|
|
|
parent_slot: block.parent_slot,
|
|
|
|
transactions: block.transactions.unwrap_or_default(),
|
|
|
|
rewards: block.rewards.unwrap_or_default(),
|
|
|
|
block_time: block.block_time,
|
|
|
|
block_height: block.block_height,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-22 17:12:32 -07:00
|
|
|
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
2021-03-18 10:58:20 -07:00
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct UiConfirmedBlock {
|
|
|
|
pub previous_blockhash: String,
|
|
|
|
pub blockhash: String,
|
|
|
|
pub parent_slot: Slot,
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
pub transactions: Option<Vec<EncodedTransactionWithStatusMeta>>,
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
pub signatures: Option<Vec<String>>,
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
pub rewards: Option<Rewards>,
|
|
|
|
pub block_time: Option<UnixTimestamp>,
|
2021-05-26 21:16:16 -07:00
|
|
|
pub block_height: Option<u64>,
|
2021-03-18 10:58:20 -07:00
|
|
|
}
|
|
|
|
|
2022-02-09 21:28:18 -08:00
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
|
|
#[allow(clippy::large_enum_variant)]
|
|
|
|
pub enum TransactionWithStatusMeta {
|
|
|
|
// Very old transactions may be missing metadata
|
|
|
|
MissingMetadata(Transaction),
|
|
|
|
// Versioned stored transaction always have metadata
|
|
|
|
Complete(VersionedTransactionWithStatusMeta),
|
|
|
|
}
|
|
|
|
|
2022-01-13 23:24:41 -08:00
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
|
|
pub struct VersionedTransactionWithStatusMeta {
|
|
|
|
pub transaction: VersionedTransaction,
|
2022-02-09 21:28:18 -08:00
|
|
|
pub meta: TransactionStatusMeta,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TransactionWithStatusMeta {
|
2022-03-16 20:43:04 -07:00
|
|
|
pub fn get_status_meta(&self) -> Option<TransactionStatusMeta> {
|
|
|
|
match self {
|
|
|
|
Self::MissingMetadata(_) => None,
|
|
|
|
Self::Complete(tx_with_meta) => Some(tx_with_meta.meta.clone()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_transaction(&self) -> VersionedTransaction {
|
|
|
|
match self {
|
|
|
|
Self::MissingMetadata(transaction) => VersionedTransaction::from(transaction.clone()),
|
|
|
|
Self::Complete(tx_with_meta) => tx_with_meta.transaction.clone(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-09 21:28:18 -08:00
|
|
|
pub fn transaction_signature(&self) -> &Signature {
|
|
|
|
match self {
|
|
|
|
Self::MissingMetadata(transaction) => &transaction.signatures[0],
|
|
|
|
Self::Complete(VersionedTransactionWithStatusMeta { transaction, .. }) => {
|
|
|
|
&transaction.signatures[0]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-07 23:20:34 -08:00
|
|
|
pub fn encode(
|
|
|
|
self,
|
|
|
|
encoding: UiTransactionEncoding,
|
|
|
|
max_supported_transaction_version: Option<u8>,
|
2022-09-08 09:17:02 -07:00
|
|
|
show_rewards: bool,
|
2022-03-07 23:20:34 -08:00
|
|
|
) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
|
|
|
|
match self {
|
|
|
|
Self::MissingMetadata(ref transaction) => Ok(EncodedTransactionWithStatusMeta {
|
|
|
|
version: None,
|
|
|
|
transaction: transaction.encode(encoding),
|
|
|
|
meta: None,
|
|
|
|
}),
|
|
|
|
Self::Complete(tx_with_meta) => {
|
2022-09-08 09:17:02 -07:00
|
|
|
tx_with_meta.encode(encoding, max_supported_transaction_version, show_rewards)
|
2022-03-07 23:20:34 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn account_keys(&self) -> AccountKeys {
|
|
|
|
match self {
|
|
|
|
Self::MissingMetadata(tx) => AccountKeys::new(&tx.message.account_keys, None),
|
|
|
|
Self::Complete(tx_with_meta) => tx_with_meta.account_keys(),
|
|
|
|
}
|
|
|
|
}
|
2022-02-09 21:28:18 -08:00
|
|
|
|
2022-09-15 09:00:30 -07:00
|
|
|
fn build_json_accounts(
|
2022-03-07 23:20:34 -08:00
|
|
|
self,
|
|
|
|
max_supported_transaction_version: Option<u8>,
|
2022-09-08 09:17:02 -07:00
|
|
|
show_rewards: bool,
|
2022-03-07 23:20:34 -08:00
|
|
|
) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
|
2022-09-15 09:00:30 -07:00
|
|
|
match self {
|
|
|
|
Self::MissingMetadata(ref transaction) => Ok(EncodedTransactionWithStatusMeta {
|
|
|
|
version: None,
|
|
|
|
transaction: transaction.build_json_accounts(),
|
|
|
|
meta: None,
|
|
|
|
}),
|
|
|
|
Self::Complete(tx_with_meta) => {
|
|
|
|
tx_with_meta.build_json_accounts(max_supported_transaction_version, show_rewards)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl VersionedTransactionWithStatusMeta {
|
|
|
|
fn validate_version(
|
|
|
|
&self,
|
|
|
|
max_supported_transaction_version: Option<u8>,
|
|
|
|
) -> Result<Option<TransactionVersion>, EncodeError> {
|
|
|
|
match (
|
2022-03-07 23:20:34 -08:00
|
|
|
max_supported_transaction_version,
|
|
|
|
self.transaction.version(),
|
|
|
|
) {
|
|
|
|
// Set to none because old clients can't handle this field
|
|
|
|
(None, TransactionVersion::LEGACY) => Ok(None),
|
|
|
|
(None, TransactionVersion::Number(version)) => {
|
|
|
|
Err(EncodeError::UnsupportedTransactionVersion(version))
|
|
|
|
}
|
|
|
|
(Some(_), TransactionVersion::LEGACY) => Ok(Some(TransactionVersion::LEGACY)),
|
|
|
|
(Some(max_version), TransactionVersion::Number(version)) => {
|
|
|
|
if version <= max_version {
|
|
|
|
Ok(Some(TransactionVersion::Number(version)))
|
|
|
|
} else {
|
|
|
|
Err(EncodeError::UnsupportedTransactionVersion(version))
|
|
|
|
}
|
|
|
|
}
|
2022-09-15 09:00:30 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn encode(
|
|
|
|
self,
|
|
|
|
encoding: UiTransactionEncoding,
|
|
|
|
max_supported_transaction_version: Option<u8>,
|
|
|
|
show_rewards: bool,
|
|
|
|
) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
|
|
|
|
let version = self.validate_version(max_supported_transaction_version)?;
|
2022-03-07 23:20:34 -08:00
|
|
|
|
|
|
|
Ok(EncodedTransactionWithStatusMeta {
|
|
|
|
transaction: self.transaction.encode_with_meta(encoding, &self.meta),
|
|
|
|
meta: Some(match encoding {
|
|
|
|
UiTransactionEncoding::JsonParsed => UiTransactionStatusMeta::parse(
|
|
|
|
self.meta,
|
|
|
|
self.transaction.message.static_account_keys(),
|
2022-09-08 09:17:02 -07:00
|
|
|
show_rewards,
|
2022-03-07 23:20:34 -08:00
|
|
|
),
|
2022-09-08 09:17:02 -07:00
|
|
|
_ => {
|
|
|
|
let mut meta = UiTransactionStatusMeta::from(self.meta);
|
|
|
|
if !show_rewards {
|
2022-09-14 15:58:50 -07:00
|
|
|
meta.rewards = OptionSerializer::None;
|
2022-09-08 09:17:02 -07:00
|
|
|
}
|
|
|
|
meta
|
|
|
|
}
|
2022-03-07 23:20:34 -08:00
|
|
|
}),
|
|
|
|
version,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-02-05 04:00:31 -08:00
|
|
|
pub fn account_keys(&self) -> AccountKeys {
|
|
|
|
AccountKeys::new(
|
|
|
|
self.transaction.message.static_account_keys(),
|
2022-02-09 21:28:18 -08:00
|
|
|
Some(&self.meta.loaded_addresses),
|
2022-02-05 04:00:31 -08:00
|
|
|
)
|
2022-01-13 23:24:41 -08:00
|
|
|
}
|
2022-09-15 09:00:30 -07:00
|
|
|
|
|
|
|
fn build_json_accounts(
|
|
|
|
self,
|
|
|
|
max_supported_transaction_version: Option<u8>,
|
|
|
|
show_rewards: bool,
|
|
|
|
) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
|
|
|
|
let version = self.validate_version(max_supported_transaction_version)?;
|
|
|
|
|
|
|
|
let account_keys = match &self.transaction.message {
|
|
|
|
VersionedMessage::Legacy(message) => parse_legacy_message_accounts(message),
|
|
|
|
VersionedMessage::V0(message) => {
|
|
|
|
let loaded_message =
|
|
|
|
LoadedMessage::new_borrowed(message, &self.meta.loaded_addresses);
|
|
|
|
parse_v0_message_accounts(&loaded_message)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(EncodedTransactionWithStatusMeta {
|
|
|
|
transaction: EncodedTransaction::Accounts(UiAccountsList {
|
|
|
|
signatures: self
|
|
|
|
.transaction
|
|
|
|
.signatures
|
|
|
|
.iter()
|
|
|
|
.map(ToString::to_string)
|
|
|
|
.collect(),
|
|
|
|
account_keys,
|
|
|
|
}),
|
|
|
|
meta: Some(UiTransactionStatusMeta::build_simple(
|
|
|
|
self.meta,
|
|
|
|
show_rewards,
|
|
|
|
)),
|
|
|
|
version,
|
|
|
|
})
|
|
|
|
}
|
2022-01-13 23:24:41 -08:00
|
|
|
}
|
|
|
|
|
2022-01-03 07:45:18 -08:00
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
2021-03-18 10:58:20 -07:00
|
|
|
#[serde(rename_all = "camelCase")]
|
2022-01-03 07:45:18 -08:00
|
|
|
pub struct EncodedTransactionWithStatusMeta {
|
|
|
|
pub transaction: EncodedTransaction,
|
|
|
|
pub meta: Option<UiTransactionStatusMeta>,
|
2022-03-07 23:20:34 -08:00
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
pub version: Option<TransactionVersion>,
|
2021-03-18 10:58:20 -07:00
|
|
|
}
|
2022-01-13 23:24:41 -08:00
|
|
|
|
2022-01-03 00:20:57 -08:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2022-01-03 07:45:18 -08:00
|
|
|
pub struct ConfirmedTransactionWithStatusMeta {
|
2020-04-08 23:57:30 -07:00
|
|
|
pub slot: Slot,
|
2022-02-09 21:28:18 -08:00
|
|
|
pub tx_with_meta: TransactionWithStatusMeta,
|
2021-01-20 22:10:35 -08:00
|
|
|
pub block_time: Option<UnixTimestamp>,
|
2020-04-08 23:57:30 -07:00
|
|
|
}
|
|
|
|
|
2022-02-09 21:28:18 -08:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
|
|
pub struct VersionedConfirmedTransactionWithStatusMeta {
|
|
|
|
pub slot: Slot,
|
|
|
|
pub tx_with_meta: VersionedTransactionWithStatusMeta,
|
|
|
|
pub block_time: Option<UnixTimestamp>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ConfirmedTransactionWithStatusMeta {
|
2022-03-07 23:20:34 -08:00
|
|
|
pub fn encode(
|
|
|
|
self,
|
|
|
|
encoding: UiTransactionEncoding,
|
|
|
|
max_supported_transaction_version: Option<u8>,
|
|
|
|
) -> Result<EncodedConfirmedTransactionWithStatusMeta, EncodeError> {
|
|
|
|
Ok(EncodedConfirmedTransactionWithStatusMeta {
|
|
|
|
slot: self.slot,
|
2022-09-08 09:17:02 -07:00
|
|
|
transaction: self.tx_with_meta.encode(
|
|
|
|
encoding,
|
|
|
|
max_supported_transaction_version,
|
|
|
|
true,
|
|
|
|
)?,
|
2022-03-07 23:20:34 -08:00
|
|
|
block_time: self.block_time,
|
|
|
|
})
|
|
|
|
}
|
2022-03-16 20:43:04 -07:00
|
|
|
|
|
|
|
pub fn get_transaction(&self) -> VersionedTransaction {
|
|
|
|
self.tx_with_meta.get_transaction()
|
|
|
|
}
|
2022-01-13 23:24:41 -08:00
|
|
|
}
|
|
|
|
|
2020-09-23 22:10:29 -07:00
|
|
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
2022-01-03 07:45:18 -08:00
|
|
|
pub struct EncodedConfirmedTransactionWithStatusMeta {
|
2020-09-23 22:10:29 -07:00
|
|
|
pub slot: Slot,
|
|
|
|
#[serde(flatten)]
|
|
|
|
pub transaction: EncodedTransactionWithStatusMeta,
|
2021-01-20 22:10:35 -08:00
|
|
|
pub block_time: Option<UnixTimestamp>,
|
2020-09-23 22:10:29 -07:00
|
|
|
}
|
|
|
|
|
2022-05-22 19:02:38 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
2020-03-26 13:29:30 -07:00
|
|
|
#[serde(rename_all = "camelCase", untagged)]
|
|
|
|
pub enum EncodedTransaction {
|
2020-08-15 17:56:09 -07:00
|
|
|
LegacyBinary(String), // Old way of expressing base-58, retained for RPC backwards compatibility
|
2022-03-09 00:09:08 -08:00
|
|
|
Binary(String, TransactionBinaryEncoding),
|
2020-07-01 13:06:40 -07:00
|
|
|
Json(UiTransaction),
|
2022-09-15 09:00:30 -07:00
|
|
|
Accounts(UiAccountsList),
|
2020-03-26 13:29:30 -07:00
|
|
|
}
|
|
|
|
|
2022-03-07 23:20:34 -08:00
|
|
|
impl EncodableWithMeta for VersionedTransaction {
|
|
|
|
type Encoded = EncodedTransaction;
|
|
|
|
fn encode_with_meta(
|
|
|
|
&self,
|
|
|
|
encoding: UiTransactionEncoding,
|
|
|
|
meta: &TransactionStatusMeta,
|
|
|
|
) -> Self::Encoded {
|
|
|
|
match encoding {
|
|
|
|
UiTransactionEncoding::Binary => EncodedTransaction::LegacyBinary(
|
|
|
|
bs58::encode(bincode::serialize(self).unwrap()).into_string(),
|
|
|
|
),
|
|
|
|
UiTransactionEncoding::Base58 => EncodedTransaction::Binary(
|
|
|
|
bs58::encode(bincode::serialize(self).unwrap()).into_string(),
|
2022-03-09 00:09:08 -08:00
|
|
|
TransactionBinaryEncoding::Base58,
|
2022-03-07 23:20:34 -08:00
|
|
|
),
|
|
|
|
UiTransactionEncoding::Base64 => EncodedTransaction::Binary(
|
2023-05-11 11:34:58 -07:00
|
|
|
BASE64_STANDARD.encode(bincode::serialize(self).unwrap()),
|
2022-03-09 00:09:08 -08:00
|
|
|
TransactionBinaryEncoding::Base64,
|
2022-03-07 23:20:34 -08:00
|
|
|
),
|
2022-03-16 20:43:04 -07:00
|
|
|
UiTransactionEncoding::Json => self.json_encode(),
|
|
|
|
UiTransactionEncoding::JsonParsed => EncodedTransaction::Json(UiTransaction {
|
|
|
|
signatures: self.signatures.iter().map(ToString::to_string).collect(),
|
|
|
|
message: match &self.message {
|
|
|
|
VersionedMessage::Legacy(message) => {
|
|
|
|
message.encode(UiTransactionEncoding::JsonParsed)
|
|
|
|
}
|
|
|
|
VersionedMessage::V0(message) => {
|
|
|
|
message.encode_with_meta(UiTransactionEncoding::JsonParsed, meta)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}),
|
2022-03-07 23:20:34 -08:00
|
|
|
}
|
|
|
|
}
|
2022-03-16 20:43:04 -07:00
|
|
|
fn json_encode(&self) -> Self::Encoded {
|
|
|
|
EncodedTransaction::Json(UiTransaction {
|
|
|
|
signatures: self.signatures.iter().map(ToString::to_string).collect(),
|
|
|
|
message: match &self.message {
|
|
|
|
VersionedMessage::Legacy(message) => message.encode(UiTransactionEncoding::Json),
|
|
|
|
VersionedMessage::V0(message) => message.json_encode(),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
2022-03-07 23:20:34 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Encodable for Transaction {
|
2022-01-03 07:45:18 -08:00
|
|
|
type Encoded = EncodedTransaction;
|
2022-03-07 23:20:34 -08:00
|
|
|
fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
|
2020-06-19 15:15:13 -07:00
|
|
|
match encoding {
|
2020-08-14 23:53:58 -07:00
|
|
|
UiTransactionEncoding::Binary => EncodedTransaction::LegacyBinary(
|
2022-01-03 07:45:18 -08:00
|
|
|
bs58::encode(bincode::serialize(self).unwrap()).into_string(),
|
2020-06-19 15:15:13 -07:00
|
|
|
),
|
2020-08-15 22:06:39 -07:00
|
|
|
UiTransactionEncoding::Base58 => EncodedTransaction::Binary(
|
2022-01-03 07:45:18 -08:00
|
|
|
bs58::encode(bincode::serialize(self).unwrap()).into_string(),
|
2022-03-09 00:09:08 -08:00
|
|
|
TransactionBinaryEncoding::Base58,
|
2020-08-15 22:06:39 -07:00
|
|
|
),
|
|
|
|
UiTransactionEncoding::Base64 => EncodedTransaction::Binary(
|
2023-05-11 11:34:58 -07:00
|
|
|
BASE64_STANDARD.encode(bincode::serialize(self).unwrap()),
|
2022-03-09 00:09:08 -08:00
|
|
|
TransactionBinaryEncoding::Base64,
|
2020-08-14 23:53:58 -07:00
|
|
|
),
|
|
|
|
UiTransactionEncoding::Json | UiTransactionEncoding::JsonParsed => {
|
2020-07-01 13:06:40 -07:00
|
|
|
EncodedTransaction::Json(UiTransaction {
|
2022-01-03 07:45:18 -08:00
|
|
|
signatures: self.signatures.iter().map(ToString::to_string).collect(),
|
|
|
|
message: self.message.encode(encoding),
|
2020-06-19 15:15:13 -07:00
|
|
|
})
|
|
|
|
}
|
2020-03-27 22:55:55 -07:00
|
|
|
}
|
|
|
|
}
|
2022-01-03 07:45:18 -08:00
|
|
|
}
|
|
|
|
|
2022-09-15 09:00:30 -07:00
|
|
|
impl JsonAccounts for Transaction {
|
|
|
|
type Encoded = EncodedTransaction;
|
|
|
|
fn build_json_accounts(&self) -> Self::Encoded {
|
|
|
|
EncodedTransaction::Accounts(UiAccountsList {
|
|
|
|
signatures: self.signatures.iter().map(ToString::to_string).collect(),
|
|
|
|
account_keys: parse_legacy_message_accounts(&self.message),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-03 07:45:18 -08:00
|
|
|
impl EncodedTransaction {
|
2022-03-16 20:43:04 -07:00
|
|
|
pub fn decode(&self) -> Option<VersionedTransaction> {
|
|
|
|
let (blob, encoding) = match self {
|
2022-09-15 09:00:30 -07:00
|
|
|
Self::Json(_) | Self::Accounts(_) => return None,
|
2022-03-16 20:43:04 -07:00
|
|
|
Self::LegacyBinary(blob) => (blob, TransactionBinaryEncoding::Base58),
|
|
|
|
Self::Binary(blob, encoding) => (blob, *encoding),
|
|
|
|
};
|
|
|
|
|
|
|
|
let transaction: Option<VersionedTransaction> = match encoding {
|
|
|
|
TransactionBinaryEncoding::Base58 => bs58::decode(blob)
|
2020-03-27 22:55:55 -07:00
|
|
|
.into_vec()
|
|
|
|
.ok()
|
|
|
|
.and_then(|bytes| bincode::deserialize(&bytes).ok()),
|
2023-05-11 11:34:58 -07:00
|
|
|
TransactionBinaryEncoding::Base64 => BASE64_STANDARD
|
|
|
|
.decode(blob)
|
2022-03-16 20:43:04 -07:00
|
|
|
.ok()
|
|
|
|
.and_then(|bytes| bincode::deserialize(&bytes).ok()),
|
2021-01-20 17:58:53 -08:00
|
|
|
};
|
2022-03-16 20:43:04 -07:00
|
|
|
|
2023-06-07 08:12:41 -07:00
|
|
|
transaction.filter(|transaction| transaction.sanitize().is_ok())
|
2020-03-26 13:29:30 -07:00
|
|
|
}
|
|
|
|
}
|
2020-04-06 03:04:54 -07:00
|
|
|
|
2022-01-03 07:45:18 -08:00
|
|
|
/// A duplicate representation of a Transaction for pretty JSON serialization
|
2022-05-22 18:00:42 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
2022-01-03 07:45:18 -08:00
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct UiTransaction {
|
|
|
|
pub signatures: Vec<String>,
|
|
|
|
pub message: UiMessage,
|
|
|
|
}
|
|
|
|
|
2022-05-22 18:00:42 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
2022-01-03 07:45:18 -08:00
|
|
|
#[serde(rename_all = "camelCase", untagged)]
|
|
|
|
pub enum UiMessage {
|
|
|
|
Parsed(UiParsedMessage),
|
|
|
|
Raw(UiRawMessage),
|
|
|
|
}
|
|
|
|
|
2022-03-07 23:20:34 -08:00
|
|
|
impl Encodable for Message {
|
2022-01-03 07:45:18 -08:00
|
|
|
type Encoded = UiMessage;
|
2022-03-07 23:20:34 -08:00
|
|
|
fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
|
2022-01-03 07:45:18 -08:00
|
|
|
if encoding == UiTransactionEncoding::JsonParsed {
|
2022-02-05 04:00:31 -08:00
|
|
|
let account_keys = AccountKeys::new(&self.account_keys, None);
|
2022-01-03 07:45:18 -08:00
|
|
|
UiMessage::Parsed(UiParsedMessage {
|
2022-09-06 08:40:39 -07:00
|
|
|
account_keys: parse_legacy_message_accounts(self),
|
2022-01-03 07:45:18 -08:00
|
|
|
recent_blockhash: self.recent_blockhash.to_string(),
|
|
|
|
instructions: self
|
|
|
|
.instructions
|
|
|
|
.iter()
|
2022-10-25 19:37:44 -07:00
|
|
|
.map(|instruction| UiInstruction::parse(instruction, &account_keys, None))
|
2022-01-03 07:45:18 -08:00
|
|
|
.collect(),
|
2022-03-07 23:20:34 -08:00
|
|
|
address_table_lookups: None,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
UiMessage::Raw(UiRawMessage {
|
|
|
|
header: self.header,
|
|
|
|
account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
|
|
|
|
recent_blockhash: self.recent_blockhash.to_string(),
|
2022-10-25 19:37:44 -07:00
|
|
|
instructions: self
|
|
|
|
.instructions
|
|
|
|
.iter()
|
|
|
|
.map(|ix| UiCompiledInstruction::from(ix, None))
|
|
|
|
.collect(),
|
2022-03-07 23:20:34 -08:00
|
|
|
address_table_lookups: None,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl EncodableWithMeta for v0::Message {
|
|
|
|
type Encoded = UiMessage;
|
|
|
|
fn encode_with_meta(
|
|
|
|
&self,
|
|
|
|
encoding: UiTransactionEncoding,
|
|
|
|
meta: &TransactionStatusMeta,
|
|
|
|
) -> Self::Encoded {
|
|
|
|
if encoding == UiTransactionEncoding::JsonParsed {
|
|
|
|
let account_keys = AccountKeys::new(&self.account_keys, Some(&meta.loaded_addresses));
|
|
|
|
let loaded_message = LoadedMessage::new_borrowed(self, &meta.loaded_addresses);
|
|
|
|
UiMessage::Parsed(UiParsedMessage {
|
2022-09-06 08:40:39 -07:00
|
|
|
account_keys: parse_v0_message_accounts(&loaded_message),
|
2022-03-07 23:20:34 -08:00
|
|
|
recent_blockhash: self.recent_blockhash.to_string(),
|
|
|
|
instructions: self
|
|
|
|
.instructions
|
|
|
|
.iter()
|
2022-10-25 19:37:44 -07:00
|
|
|
.map(|instruction| UiInstruction::parse(instruction, &account_keys, None))
|
2022-03-07 23:20:34 -08:00
|
|
|
.collect(),
|
|
|
|
address_table_lookups: Some(
|
|
|
|
self.address_table_lookups.iter().map(Into::into).collect(),
|
|
|
|
),
|
2022-01-03 07:45:18 -08:00
|
|
|
})
|
|
|
|
} else {
|
2022-03-16 20:43:04 -07:00
|
|
|
self.json_encode()
|
2022-01-03 07:45:18 -08:00
|
|
|
}
|
|
|
|
}
|
2022-03-16 20:43:04 -07:00
|
|
|
fn json_encode(&self) -> Self::Encoded {
|
|
|
|
UiMessage::Raw(UiRawMessage {
|
|
|
|
header: self.header,
|
|
|
|
account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
|
|
|
|
recent_blockhash: self.recent_blockhash.to_string(),
|
2022-10-25 19:37:44 -07:00
|
|
|
instructions: self
|
|
|
|
.instructions
|
|
|
|
.iter()
|
|
|
|
.map(|ix| UiCompiledInstruction::from(ix, None))
|
|
|
|
.collect(),
|
2022-03-16 20:43:04 -07:00
|
|
|
address_table_lookups: Some(
|
|
|
|
self.address_table_lookups.iter().map(Into::into).collect(),
|
|
|
|
),
|
|
|
|
})
|
|
|
|
}
|
2022-01-03 07:45:18 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// A duplicate representation of a Message, in raw format, for pretty JSON serialization
|
2022-05-22 18:00:42 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
2022-01-03 07:45:18 -08:00
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct UiRawMessage {
|
|
|
|
pub header: MessageHeader,
|
|
|
|
pub account_keys: Vec<String>,
|
|
|
|
pub recent_blockhash: String,
|
|
|
|
pub instructions: Vec<UiCompiledInstruction>,
|
2022-03-07 23:20:34 -08:00
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
pub address_table_lookups: Option<Vec<UiAddressTableLookup>>,
|
|
|
|
}
|
|
|
|
|
2022-09-15 09:00:30 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct UiAccountsList {
|
|
|
|
pub signatures: Vec<String>,
|
|
|
|
pub account_keys: Vec<ParsedAccount>,
|
|
|
|
}
|
|
|
|
|
2022-03-07 23:20:34 -08:00
|
|
|
/// A duplicate representation of a MessageAddressTableLookup, in raw format, for pretty JSON serialization
|
2022-05-22 18:00:42 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
2022-03-07 23:20:34 -08:00
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct UiAddressTableLookup {
|
|
|
|
pub account_key: String,
|
|
|
|
pub writable_indexes: Vec<u8>,
|
|
|
|
pub readonly_indexes: Vec<u8>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<&MessageAddressTableLookup> for UiAddressTableLookup {
|
|
|
|
fn from(lookup: &MessageAddressTableLookup) -> Self {
|
|
|
|
Self {
|
|
|
|
account_key: lookup.account_key.to_string(),
|
|
|
|
writable_indexes: lookup.writable_indexes.clone(),
|
|
|
|
readonly_indexes: lookup.readonly_indexes.clone(),
|
|
|
|
}
|
|
|
|
}
|
2022-01-03 07:45:18 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// A duplicate representation of a Message, in parsed format, for pretty JSON serialization
|
2022-05-22 18:00:42 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
2022-01-03 07:45:18 -08:00
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct UiParsedMessage {
|
|
|
|
pub account_keys: Vec<ParsedAccount>,
|
|
|
|
pub recent_blockhash: String,
|
|
|
|
pub instructions: Vec<UiInstruction>,
|
2023-05-22 10:29:18 -07:00
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
2022-03-07 23:20:34 -08:00
|
|
|
pub address_table_lookups: Option<Vec<UiAddressTableLookup>>,
|
2022-01-03 07:45:18 -08:00
|
|
|
}
|
|
|
|
|
2021-01-20 22:10:35 -08:00
|
|
|
// A serialized `Vec<TransactionByAddrInfo>` is stored in the `tx-by-addr` table. The row keys are
|
|
|
|
// the one's compliment of the slot so that rows may be listed in reverse order
|
2022-05-22 18:00:42 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
2021-01-20 22:10:35 -08:00
|
|
|
pub struct TransactionByAddrInfo {
|
|
|
|
pub signature: Signature, // The transaction signature
|
|
|
|
pub err: Option<TransactionError>, // None if the transaction executed successfully
|
|
|
|
pub index: u32, // Where the transaction is located in the block
|
|
|
|
pub memo: Option<String>, // Transaction memo
|
|
|
|
pub block_time: Option<UnixTimestamp>,
|
|
|
|
}
|
|
|
|
|
2022-09-08 14:10:57 -07:00
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct UiTransactionReturnData {
|
|
|
|
pub program_id: String,
|
|
|
|
pub data: (String, UiReturnDataEncoding),
|
|
|
|
}
|
|
|
|
|
2022-09-14 15:58:50 -07:00
|
|
|
impl Default for UiTransactionReturnData {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
program_id: String::default(),
|
|
|
|
data: (String::default(), UiReturnDataEncoding::Base64),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-08 14:10:57 -07:00
|
|
|
impl From<TransactionReturnData> for UiTransactionReturnData {
|
|
|
|
fn from(return_data: TransactionReturnData) -> Self {
|
|
|
|
Self {
|
|
|
|
program_id: return_data.program_id.to_string(),
|
|
|
|
data: (
|
2023-05-11 11:34:58 -07:00
|
|
|
BASE64_STANDARD.encode(return_data.data),
|
2022-09-08 14:10:57 -07:00
|
|
|
UiReturnDataEncoding::Base64,
|
|
|
|
),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub enum UiReturnDataEncoding {
|
|
|
|
Base64,
|
|
|
|
}
|
|
|
|
|
2020-04-06 03:04:54 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
2022-09-14 15:58:50 -07:00
|
|
|
use {super::*, serde_json::json};
|
2020-04-06 03:04:54 -07:00
|
|
|
|
2021-01-20 17:58:53 -08:00
|
|
|
#[test]
|
|
|
|
fn test_decode_invalid_transaction() {
|
|
|
|
// This transaction will not pass sanitization
|
|
|
|
let unsanitary_transaction = EncodedTransaction::Binary(
|
|
|
|
"ju9xZWuDBX4pRxX2oZkTjxU5jB4SSTgEGhX8bQ8PURNzyzqKMPPpNvWihx8zUe\
|
|
|
|
FfrbVNoAaEsNKZvGzAnTDy5bhNT9kt6KFCTBixpvrLCzg4M5UdFUQYrn1gdgjX\
|
|
|
|
pLHxcaShD81xBNaFDgnA2nkkdHnKtZt4hVSfKAmw3VRZbjrZ7L2fKZBx21CwsG\
|
|
|
|
hD6onjM2M3qZW5C8J6d1pj41MxKmZgPBSha3MyKkNLkAGFASK"
|
|
|
|
.to_string(),
|
2022-03-09 00:09:08 -08:00
|
|
|
TransactionBinaryEncoding::Base58,
|
2021-01-20 17:58:53 -08:00
|
|
|
);
|
|
|
|
assert!(unsanitary_transaction.decode().is_none());
|
|
|
|
}
|
|
|
|
|
2020-04-06 03:04:54 -07:00
|
|
|
#[test]
|
|
|
|
fn test_satisfies_commitment() {
|
|
|
|
let status = TransactionStatus {
|
|
|
|
slot: 0,
|
|
|
|
confirmations: None,
|
|
|
|
status: Ok(()),
|
|
|
|
err: None,
|
2021-01-15 08:05:05 -08:00
|
|
|
confirmation_status: Some(TransactionConfirmationStatus::Finalized),
|
2020-04-06 03:04:54 -07:00
|
|
|
};
|
|
|
|
|
2021-01-26 11:23:07 -08:00
|
|
|
assert!(status.satisfies_commitment(CommitmentConfig::finalized()));
|
|
|
|
assert!(status.satisfies_commitment(CommitmentConfig::confirmed()));
|
|
|
|
assert!(status.satisfies_commitment(CommitmentConfig::processed()));
|
2020-04-06 03:04:54 -07:00
|
|
|
|
|
|
|
let status = TransactionStatus {
|
|
|
|
slot: 0,
|
|
|
|
confirmations: Some(10),
|
|
|
|
status: Ok(()),
|
|
|
|
err: None,
|
2021-01-15 08:05:05 -08:00
|
|
|
confirmation_status: Some(TransactionConfirmationStatus::Confirmed),
|
2020-04-06 03:04:54 -07:00
|
|
|
};
|
|
|
|
|
2021-01-26 11:23:07 -08:00
|
|
|
assert!(!status.satisfies_commitment(CommitmentConfig::finalized()));
|
|
|
|
assert!(status.satisfies_commitment(CommitmentConfig::confirmed()));
|
|
|
|
assert!(status.satisfies_commitment(CommitmentConfig::processed()));
|
2021-01-15 08:05:05 -08:00
|
|
|
|
|
|
|
let status = TransactionStatus {
|
|
|
|
slot: 0,
|
|
|
|
confirmations: Some(1),
|
|
|
|
status: Ok(()),
|
|
|
|
err: None,
|
|
|
|
confirmation_status: Some(TransactionConfirmationStatus::Processed),
|
|
|
|
};
|
|
|
|
|
2021-01-26 11:23:07 -08:00
|
|
|
assert!(!status.satisfies_commitment(CommitmentConfig::finalized()));
|
|
|
|
assert!(!status.satisfies_commitment(CommitmentConfig::confirmed()));
|
|
|
|
assert!(status.satisfies_commitment(CommitmentConfig::processed()));
|
2021-01-15 08:05:05 -08:00
|
|
|
|
|
|
|
let status = TransactionStatus {
|
|
|
|
slot: 0,
|
|
|
|
confirmations: Some(0),
|
|
|
|
status: Ok(()),
|
|
|
|
err: None,
|
|
|
|
confirmation_status: None,
|
|
|
|
};
|
|
|
|
|
2021-01-26 11:23:07 -08:00
|
|
|
assert!(!status.satisfies_commitment(CommitmentConfig::finalized()));
|
|
|
|
assert!(!status.satisfies_commitment(CommitmentConfig::confirmed()));
|
|
|
|
assert!(status.satisfies_commitment(CommitmentConfig::processed()));
|
2021-01-15 08:05:05 -08:00
|
|
|
|
|
|
|
// Test single_gossip fallback cases
|
|
|
|
let status = TransactionStatus {
|
|
|
|
slot: 0,
|
|
|
|
confirmations: Some(1),
|
|
|
|
status: Ok(()),
|
|
|
|
err: None,
|
|
|
|
confirmation_status: None,
|
|
|
|
};
|
2021-01-26 11:23:07 -08:00
|
|
|
assert!(!status.satisfies_commitment(CommitmentConfig::confirmed()));
|
2021-01-15 08:05:05 -08:00
|
|
|
|
|
|
|
let status = TransactionStatus {
|
|
|
|
slot: 0,
|
|
|
|
confirmations: Some(2),
|
|
|
|
status: Ok(()),
|
|
|
|
err: None,
|
|
|
|
confirmation_status: None,
|
|
|
|
};
|
2021-01-26 11:23:07 -08:00
|
|
|
assert!(status.satisfies_commitment(CommitmentConfig::confirmed()));
|
2021-01-15 08:05:05 -08:00
|
|
|
|
|
|
|
let status = TransactionStatus {
|
|
|
|
slot: 0,
|
|
|
|
confirmations: None,
|
|
|
|
status: Ok(()),
|
|
|
|
err: None,
|
|
|
|
confirmation_status: None,
|
|
|
|
};
|
2021-01-26 11:23:07 -08:00
|
|
|
assert!(status.satisfies_commitment(CommitmentConfig::confirmed()));
|
2020-04-06 03:04:54 -07:00
|
|
|
}
|
2022-09-14 15:58:50 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_serde_empty_fields() {
|
|
|
|
fn test_serde<'de, T: serde::Serialize + serde::Deserialize<'de>>(
|
|
|
|
json_input: &'de str,
|
|
|
|
expected_json_output: &str,
|
|
|
|
) {
|
|
|
|
let typed_meta: T = serde_json::from_str(json_input).unwrap();
|
|
|
|
let reserialized_value = json!(typed_meta);
|
|
|
|
|
|
|
|
let expected_json_output_value: serde_json::Value =
|
|
|
|
serde_json::from_str(expected_json_output).unwrap();
|
|
|
|
assert_eq!(reserialized_value, expected_json_output_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
let json_input = "{\
|
|
|
|
\"err\":null,\
|
|
|
|
\"status\":{\"Ok\":null},\
|
|
|
|
\"fee\":1234,\
|
|
|
|
\"preBalances\":[1,2,3],\
|
|
|
|
\"postBalances\":[4,5,6]\
|
|
|
|
}";
|
|
|
|
let expected_json_output = "{\
|
|
|
|
\"err\":null,\
|
|
|
|
\"status\":{\"Ok\":null},\
|
|
|
|
\"fee\":1234,\
|
|
|
|
\"preBalances\":[1,2,3],\
|
|
|
|
\"postBalances\":[4,5,6],\
|
|
|
|
\"innerInstructions\":null,\
|
|
|
|
\"logMessages\":null,\
|
|
|
|
\"preTokenBalances\":null,\
|
|
|
|
\"postTokenBalances\":null,\
|
|
|
|
\"rewards\":null\
|
|
|
|
}";
|
|
|
|
test_serde::<UiTransactionStatusMeta>(json_input, expected_json_output);
|
|
|
|
|
|
|
|
let json_input = "{\
|
|
|
|
\"accountIndex\":5,\
|
|
|
|
\"mint\":\"DXM2yVSouSg1twmQgHLKoSReqXhtUroehWxrTgPmmfWi\",\
|
|
|
|
\"uiTokenAmount\": {
|
|
|
|
\"amount\": \"1\",\
|
|
|
|
\"decimals\": 0,\
|
|
|
|
\"uiAmount\": 1.0,\
|
|
|
|
\"uiAmountString\": \"1\"\
|
|
|
|
}\
|
|
|
|
}";
|
|
|
|
let expected_json_output = "{\
|
|
|
|
\"accountIndex\":5,\
|
|
|
|
\"mint\":\"DXM2yVSouSg1twmQgHLKoSReqXhtUroehWxrTgPmmfWi\",\
|
|
|
|
\"uiTokenAmount\": {
|
|
|
|
\"amount\": \"1\",\
|
|
|
|
\"decimals\": 0,\
|
|
|
|
\"uiAmount\": 1.0,\
|
|
|
|
\"uiAmountString\": \"1\"\
|
|
|
|
}\
|
|
|
|
}";
|
|
|
|
test_serde::<UiTransactionTokenBalance>(json_input, expected_json_output);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_ui_transaction_status_meta_ctors_serialization() {
|
|
|
|
let meta = TransactionStatusMeta {
|
|
|
|
status: Ok(()),
|
|
|
|
fee: 1234,
|
|
|
|
pre_balances: vec![1, 2, 3],
|
|
|
|
post_balances: vec![4, 5, 6],
|
|
|
|
inner_instructions: None,
|
|
|
|
log_messages: None,
|
|
|
|
pre_token_balances: None,
|
|
|
|
post_token_balances: None,
|
|
|
|
rewards: None,
|
|
|
|
loaded_addresses: LoadedAddresses {
|
|
|
|
writable: vec![],
|
|
|
|
readonly: vec![],
|
|
|
|
},
|
|
|
|
return_data: None,
|
|
|
|
compute_units_consumed: None,
|
|
|
|
};
|
|
|
|
let expected_json_output_value: serde_json::Value = serde_json::from_str(
|
|
|
|
"{\
|
|
|
|
\"err\":null,\
|
|
|
|
\"status\":{\"Ok\":null},\
|
|
|
|
\"fee\":1234,\
|
|
|
|
\"preBalances\":[1,2,3],\
|
|
|
|
\"postBalances\":[4,5,6],\
|
|
|
|
\"innerInstructions\":null,\
|
|
|
|
\"logMessages\":null,\
|
|
|
|
\"preTokenBalances\":null,\
|
|
|
|
\"postTokenBalances\":null,\
|
|
|
|
\"rewards\":null,\
|
|
|
|
\"loadedAddresses\":{\
|
|
|
|
\"readonly\": [],\
|
|
|
|
\"writable\": []\
|
|
|
|
}\
|
|
|
|
}",
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
let ui_meta_from: UiTransactionStatusMeta = meta.clone().into();
|
|
|
|
assert_eq!(
|
2022-11-09 11:39:38 -08:00
|
|
|
serde_json::to_value(ui_meta_from).unwrap(),
|
2022-09-14 15:58:50 -07:00
|
|
|
expected_json_output_value
|
|
|
|
);
|
|
|
|
|
|
|
|
let expected_json_output_value: serde_json::Value = serde_json::from_str(
|
|
|
|
"{\
|
|
|
|
\"err\":null,\
|
|
|
|
\"status\":{\"Ok\":null},\
|
|
|
|
\"fee\":1234,\
|
|
|
|
\"preBalances\":[1,2,3],\
|
|
|
|
\"postBalances\":[4,5,6],\
|
|
|
|
\"innerInstructions\":null,\
|
|
|
|
\"logMessages\":null,\
|
|
|
|
\"preTokenBalances\":null,\
|
|
|
|
\"postTokenBalances\":null,\
|
|
|
|
\"rewards\":null\
|
|
|
|
}",
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
let ui_meta_parse_with_rewards = UiTransactionStatusMeta::parse(meta.clone(), &[], true);
|
|
|
|
assert_eq!(
|
2022-11-09 11:39:38 -08:00
|
|
|
serde_json::to_value(ui_meta_parse_with_rewards).unwrap(),
|
2022-09-14 15:58:50 -07:00
|
|
|
expected_json_output_value
|
|
|
|
);
|
|
|
|
|
|
|
|
let ui_meta_parse_no_rewards = UiTransactionStatusMeta::parse(meta, &[], false);
|
|
|
|
assert_eq!(
|
2022-11-09 11:39:38 -08:00
|
|
|
serde_json::to_value(ui_meta_parse_no_rewards).unwrap(),
|
2022-09-14 15:58:50 -07:00
|
|
|
expected_json_output_value
|
|
|
|
);
|
|
|
|
}
|
2020-04-06 03:04:54 -07:00
|
|
|
}
|