2020-03-26 13:29:30 -07:00
|
|
|
#[macro_use]
|
2020-06-19 15:15:13 -07:00
|
|
|
extern crate lazy_static;
|
|
|
|
#[macro_use]
|
2020-03-26 13:29:30 -07:00
|
|
|
extern crate serde_derive;
|
|
|
|
|
2020-06-19 15:15:13 -07:00
|
|
|
pub mod parse_accounts;
|
2020-10-19 22:13:02 -07:00
|
|
|
pub mod parse_bpf_loader;
|
2020-06-19 15:15:13 -07:00
|
|
|
pub mod parse_instruction;
|
2020-10-20 20:02:17 -07:00
|
|
|
pub mod parse_stake;
|
|
|
|
pub mod parse_system;
|
2020-07-31 12:26:09 -07:00
|
|
|
pub mod parse_token;
|
2020-10-27 16:53:17 -07:00
|
|
|
pub mod parse_vote;
|
2020-12-10 19:25:07 -08:00
|
|
|
pub mod token_balances;
|
2020-06-19 15:15:13 -07:00
|
|
|
|
2020-08-04 23:58:58 -07:00
|
|
|
use crate::{
|
|
|
|
parse_accounts::{parse_accounts, ParsedAccount},
|
|
|
|
parse_instruction::{parse, ParsedInstruction},
|
|
|
|
};
|
2020-12-10 19:25:07 -08:00
|
|
|
use solana_account_decoder::parse_token::UiTokenAmount;
|
2020-10-09 12:55:35 -07:00
|
|
|
pub use solana_runtime::bank::RewardType;
|
2020-03-26 13:29:30 -07:00
|
|
|
use solana_sdk::{
|
2020-07-09 21:47:29 -07:00
|
|
|
clock::{Slot, UnixTimestamp},
|
2021-01-26 11:23:07 -08:00
|
|
|
commitment_config::CommitmentConfig,
|
2020-09-30 10:49:35 -07:00
|
|
|
deserialize_utils::default_on_eof,
|
2020-06-19 15:15:13 -07:00
|
|
|
instruction::CompiledInstruction,
|
2020-09-24 07:36:22 -07:00
|
|
|
message::{Message, MessageHeader},
|
2020-08-03 13:27:15 -07:00
|
|
|
pubkey::Pubkey,
|
2021-01-20 17:58:53 -08:00
|
|
|
sanitize::Sanitize,
|
2020-07-27 11:42:49 -07:00
|
|
|
signature::Signature,
|
2020-04-04 16:13:26 -07:00
|
|
|
transaction::{Result, Transaction, TransactionError},
|
2020-03-26 13:29:30 -07:00
|
|
|
};
|
2020-10-05 21:47:47 -07:00
|
|
|
use std::fmt;
|
2020-07-01 13:06:40 -07:00
|
|
|
/// A duplicate representation of an Instruction for pretty JSON serialization
|
2020-06-19 15:15:13 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
#[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),
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase", untagged)]
|
|
|
|
pub enum UiParsedInstruction {
|
|
|
|
Parsed(ParsedInstruction),
|
|
|
|
PartiallyDecoded(UiPartiallyDecodedInstruction),
|
2020-06-19 15:15:13 -07:00
|
|
|
}
|
|
|
|
|
2020-09-24 07:36:22 -07:00
|
|
|
impl UiInstruction {
|
|
|
|
fn parse(instruction: &CompiledInstruction, message: &Message) -> Self {
|
|
|
|
let program_id = instruction.program_id(&message.account_keys);
|
|
|
|
if let Ok(parsed_instruction) = parse(program_id, instruction, &message.account_keys) {
|
|
|
|
UiInstruction::Parsed(UiParsedInstruction::Parsed(parsed_instruction))
|
|
|
|
} else {
|
|
|
|
UiInstruction::Parsed(UiParsedInstruction::PartiallyDecoded(
|
|
|
|
UiPartiallyDecodedInstruction::from(instruction, &message.account_keys),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-30 21:55:11 -07:00
|
|
|
/// A duplicate representation of a CompiledInstruction for pretty JSON serialization
|
2020-03-26 13:29:30 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
#[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,
|
|
|
|
}
|
|
|
|
|
2020-07-01 13:06:40 -07:00
|
|
|
impl From<&CompiledInstruction> for UiCompiledInstruction {
|
2020-06-19 15:15:13 -07:00
|
|
|
fn from(instruction: &CompiledInstruction) -> Self {
|
|
|
|
Self {
|
|
|
|
program_id_index: instruction.program_id_index,
|
|
|
|
accounts: instruction.accounts.clone(),
|
|
|
|
data: bs58::encode(instruction.data.clone()).into_string(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-03 13:27:15 -07:00
|
|
|
/// A partially decoded CompiledInstruction that includes explicit account addresses
|
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct UiPartiallyDecodedInstruction {
|
|
|
|
pub program_id: String,
|
|
|
|
pub accounts: Vec<String>,
|
|
|
|
pub data: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl UiPartiallyDecodedInstruction {
|
|
|
|
fn from(instruction: &CompiledInstruction, account_keys: &[Pubkey]) -> Self {
|
|
|
|
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(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-24 07:36:22 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
pub struct InnerInstructions {
|
|
|
|
/// Transaction instruction index
|
|
|
|
pub index: u8,
|
|
|
|
/// List of inner instructions
|
|
|
|
pub instructions: Vec<CompiledInstruction>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct UiInnerInstructions {
|
|
|
|
/// Transaction instruction index
|
|
|
|
pub index: u8,
|
|
|
|
/// List of inner instructions
|
|
|
|
pub instructions: Vec<UiInstruction>,
|
|
|
|
}
|
|
|
|
|
2020-12-10 19:25:07 -08:00
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
pub struct TransactionTokenBalance {
|
|
|
|
pub account_index: u8,
|
|
|
|
pub mint: String,
|
|
|
|
pub ui_token_amount: UiTokenAmount,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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,
|
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-24 07:36:22 -07:00
|
|
|
impl UiInnerInstructions {
|
|
|
|
fn parse(inner_instructions: InnerInstructions, message: &Message) -> Self {
|
|
|
|
Self {
|
|
|
|
index: inner_instructions.index,
|
|
|
|
instructions: inner_instructions
|
|
|
|
.instructions
|
|
|
|
.iter()
|
|
|
|
.map(|ix| UiInstruction::parse(ix, message))
|
|
|
|
.collect(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<InnerInstructions> for UiInnerInstructions {
|
|
|
|
fn from(inner_instructions: InnerInstructions) -> Self {
|
|
|
|
Self {
|
|
|
|
index: inner_instructions.index,
|
|
|
|
instructions: inner_instructions
|
|
|
|
.instructions
|
|
|
|
.iter()
|
|
|
|
.map(|ix| UiInstruction::Compiled(ix.into()))
|
|
|
|
.collect(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-26 13:29:30 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct TransactionStatusMeta {
|
|
|
|
pub status: Result<()>,
|
|
|
|
pub fee: u64,
|
|
|
|
pub pre_balances: Vec<u64>,
|
|
|
|
pub post_balances: Vec<u64>,
|
2020-09-30 10:49:35 -07:00
|
|
|
#[serde(deserialize_with = "default_on_eof")]
|
2020-09-24 07:36:22 -07:00
|
|
|
pub inner_instructions: Option<Vec<InnerInstructions>>,
|
2020-10-08 12:06:15 -07:00
|
|
|
#[serde(deserialize_with = "default_on_eof")]
|
|
|
|
pub log_messages: Option<Vec<String>>,
|
2020-12-10 19:25:07 -08:00
|
|
|
#[serde(deserialize_with = "default_on_eof")]
|
|
|
|
pub pre_token_balances: Option<Vec<TransactionTokenBalance>>,
|
|
|
|
#[serde(deserialize_with = "default_on_eof")]
|
|
|
|
pub post_token_balances: Option<Vec<TransactionTokenBalance>>,
|
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,
|
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>,
|
|
|
|
pub status: Result<()>, // This field is deprecated. See https://github.com/solana-labs/solana/issues/9302
|
|
|
|
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<UiInnerInstructions>>,
|
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<UiTransactionTokenBalance>>,
|
|
|
|
pub post_token_balances: Option<Vec<UiTransactionTokenBalance>>,
|
2020-09-24 07:36:22 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl UiTransactionStatusMeta {
|
|
|
|
fn parse(meta: TransactionStatusMeta, message: &Message) -> 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: meta.inner_instructions.map(|ixs| {
|
|
|
|
ixs.into_iter()
|
|
|
|
.map(|ix| UiInnerInstructions::parse(ix, message))
|
|
|
|
.collect()
|
|
|
|
}),
|
2020-10-08 12:06:15 -07:00
|
|
|
log_messages: meta.log_messages,
|
2020-12-10 19:25:07 -08:00
|
|
|
pre_token_balances: meta
|
|
|
|
.pre_token_balances
|
|
|
|
.map(|balance| balance.into_iter().map(|balance| balance.into()).collect()),
|
|
|
|
post_token_balances: meta
|
|
|
|
.post_token_balances
|
|
|
|
.map(|balance| balance.into_iter().map(|balance| balance.into()).collect()),
|
2020-09-24 07:36:22 -07:00
|
|
|
}
|
|
|
|
}
|
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
|
|
|
|
.map(|ixs| ixs.into_iter().map(|ix| ix.into()).collect()),
|
2020-10-08 12:06:15 -07:00
|
|
|
log_messages: meta.log_messages,
|
2020-12-10 19:25:07 -08:00
|
|
|
pre_token_balances: meta
|
|
|
|
.pre_token_balances
|
|
|
|
.map(|balance| balance.into_iter().map(|balance| balance.into()).collect()),
|
|
|
|
post_token_balances: meta
|
|
|
|
.post_token_balances
|
|
|
|
.map(|balance| balance.into_iter().map(|balance| balance.into()).collect()),
|
2020-04-04 16:13:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-15 08:05:05 -08:00
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub enum TransactionConfirmationStatus {
|
|
|
|
Processed,
|
|
|
|
Confirmed,
|
|
|
|
Finalized,
|
|
|
|
}
|
|
|
|
|
2020-03-26 13:29:30 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct TransactionStatus {
|
|
|
|
pub slot: Slot,
|
2020-04-15 20:51:05 -07:00
|
|
|
pub confirmations: Option<usize>, // None = rooted
|
2020-07-27 11:42:49 -07:00
|
|
|
pub status: Result<()>, // 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
|
|
|
}
|
|
|
|
|
2020-07-27 11:42:49 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2020-09-30 10:55:22 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, 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>,
|
2020-03-26 13:29:30 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
pub type Rewards = Vec<Reward>;
|
|
|
|
|
2020-09-30 10:55:22 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
2020-03-26 13:29:30 -07:00
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
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>,
|
2020-03-26 13:29:30 -07:00
|
|
|
}
|
|
|
|
|
2020-09-23 22:10:29 -07:00
|
|
|
impl ConfirmedBlock {
|
|
|
|
pub fn encode(self, encoding: UiTransactionEncoding) -> EncodedConfirmedBlock {
|
|
|
|
EncodedConfirmedBlock {
|
|
|
|
previous_blockhash: self.previous_blockhash,
|
|
|
|
blockhash: self.blockhash,
|
|
|
|
parent_slot: self.parent_slot,
|
|
|
|
transactions: self
|
|
|
|
.transactions
|
|
|
|
.into_iter()
|
|
|
|
.map(|tx| tx.encode(encoding))
|
|
|
|
.collect(),
|
|
|
|
rewards: self.rewards,
|
|
|
|
block_time: self.block_time,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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-01-19 16:24:44 -08:00
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
2020-04-08 23:57:30 -07:00
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct ConfirmedTransaction {
|
|
|
|
pub slot: Slot,
|
|
|
|
#[serde(flatten)]
|
|
|
|
pub transaction: TransactionWithStatusMeta,
|
2021-01-20 22:10:35 -08:00
|
|
|
pub block_time: Option<UnixTimestamp>,
|
2020-04-08 23:57:30 -07:00
|
|
|
}
|
|
|
|
|
2020-09-23 22:10:29 -07:00
|
|
|
impl ConfirmedTransaction {
|
|
|
|
pub fn encode(self, encoding: UiTransactionEncoding) -> EncodedConfirmedTransaction {
|
|
|
|
EncodedConfirmedTransaction {
|
|
|
|
slot: self.slot,
|
|
|
|
transaction: self.transaction.encode(encoding),
|
2021-01-20 22:10:35 -08:00
|
|
|
block_time: self.block_time,
|
2020-09-23 22:10:29 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct EncodedConfirmedTransaction {
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2020-03-26 13:29:30 -07:00
|
|
|
/// A duplicate representation of a Transaction for pretty JSON serialization
|
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
2020-07-01 13:06:40 -07:00
|
|
|
pub struct UiTransaction {
|
2020-03-26 13:29:30 -07:00
|
|
|
pub signatures: Vec<String>,
|
2020-07-01 13:06:40 -07:00
|
|
|
pub message: UiMessage,
|
2020-03-26 13:29:30 -07:00
|
|
|
}
|
|
|
|
|
2020-06-19 15:15:13 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase", untagged)]
|
2020-07-01 13:06:40 -07:00
|
|
|
pub enum UiMessage {
|
|
|
|
Parsed(UiParsedMessage),
|
|
|
|
Raw(UiRawMessage),
|
2020-06-19 15:15:13 -07:00
|
|
|
}
|
|
|
|
|
2020-07-01 13:06:40 -07:00
|
|
|
/// A duplicate representation of a Message, in raw format, for pretty JSON serialization
|
2020-03-26 13:29:30 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
2020-07-01 13:06:40 -07:00
|
|
|
pub struct UiRawMessage {
|
2020-03-26 13:29:30 -07:00
|
|
|
pub header: MessageHeader,
|
|
|
|
pub account_keys: Vec<String>,
|
|
|
|
pub recent_blockhash: String,
|
2020-07-01 13:06:40 -07:00
|
|
|
pub instructions: Vec<UiCompiledInstruction>,
|
2020-03-26 13:29:30 -07:00
|
|
|
}
|
|
|
|
|
2020-07-01 13:06:40 -07:00
|
|
|
/// A duplicate representation of a Message, in parsed format, for pretty JSON serialization
|
2020-06-19 15:15:13 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
2020-07-01 13:06:40 -07:00
|
|
|
pub struct UiParsedMessage {
|
2020-08-04 23:58:58 -07:00
|
|
|
pub account_keys: Vec<ParsedAccount>,
|
2020-06-19 15:15:13 -07:00
|
|
|
pub recent_blockhash: String,
|
2020-07-01 13:06:40 -07:00
|
|
|
pub instructions: Vec<UiInstruction>,
|
2020-06-19 15:15:13 -07:00
|
|
|
}
|
|
|
|
|
2020-09-23 22:10:29 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
2020-03-26 13:29:30 -07:00
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct TransactionWithStatusMeta {
|
2020-09-23 22:10:29 -07:00
|
|
|
pub transaction: Transaction,
|
|
|
|
pub meta: Option<TransactionStatusMeta>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TransactionWithStatusMeta {
|
|
|
|
fn encode(self, encoding: UiTransactionEncoding) -> EncodedTransactionWithStatusMeta {
|
2020-09-24 07:36:22 -07:00
|
|
|
let message = self.transaction.message();
|
|
|
|
let meta = self.meta.map(|meta| meta.encode(encoding, message));
|
2020-09-23 22:10:29 -07:00
|
|
|
EncodedTransactionWithStatusMeta {
|
|
|
|
transaction: EncodedTransaction::encode(self.transaction, encoding),
|
2020-09-24 07:36:22 -07:00
|
|
|
meta,
|
2020-09-23 22:10:29 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct EncodedTransactionWithStatusMeta {
|
2020-03-26 13:29:30 -07:00
|
|
|
pub transaction: EncodedTransaction,
|
2020-07-01 13:06:40 -07:00
|
|
|
pub meta: Option<UiTransactionStatusMeta>,
|
2020-03-26 13:29:30 -07:00
|
|
|
}
|
|
|
|
|
2020-09-24 07:36:22 -07:00
|
|
|
impl TransactionStatusMeta {
|
|
|
|
fn encode(self, encoding: UiTransactionEncoding, message: &Message) -> UiTransactionStatusMeta {
|
|
|
|
match encoding {
|
|
|
|
UiTransactionEncoding::JsonParsed => UiTransactionStatusMeta::parse(self, message),
|
|
|
|
_ => self.into(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-27 08:23:17 -07:00
|
|
|
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
|
2020-03-26 13:29:30 -07:00
|
|
|
#[serde(rename_all = "camelCase")]
|
2020-07-01 13:06:40 -07:00
|
|
|
pub enum UiTransactionEncoding {
|
2020-08-15 22:06:39 -07:00
|
|
|
Binary, // Legacy. Retained for RPC backwards compatibility
|
|
|
|
Base64,
|
|
|
|
Base58,
|
2020-03-26 13:29:30 -07:00
|
|
|
Json,
|
2020-06-19 15:15:13 -07:00
|
|
|
JsonParsed,
|
2020-03-26 13:29:30 -07:00
|
|
|
}
|
|
|
|
|
2020-10-05 21:47:47 -07: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)?;
|
|
|
|
write!(f, "{}", s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-26 13:29:30 -07:00
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
#[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
|
2020-08-14 23:53:58 -07:00
|
|
|
Binary(String, UiTransactionEncoding),
|
2020-07-01 13:06:40 -07:00
|
|
|
Json(UiTransaction),
|
2020-03-26 13:29:30 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl EncodedTransaction {
|
2020-07-01 13:06:40 -07:00
|
|
|
pub fn encode(transaction: Transaction, encoding: UiTransactionEncoding) -> Self {
|
2020-06-19 15:15:13 -07:00
|
|
|
match encoding {
|
2020-08-14 23:53:58 -07:00
|
|
|
UiTransactionEncoding::Binary => EncodedTransaction::LegacyBinary(
|
2020-06-19 15:15:13 -07:00
|
|
|
bs58::encode(bincode::serialize(&transaction).unwrap()).into_string(),
|
|
|
|
),
|
2020-08-15 22:06:39 -07:00
|
|
|
UiTransactionEncoding::Base58 => EncodedTransaction::Binary(
|
|
|
|
bs58::encode(bincode::serialize(&transaction).unwrap()).into_string(),
|
|
|
|
encoding,
|
|
|
|
),
|
|
|
|
UiTransactionEncoding::Base64 => EncodedTransaction::Binary(
|
2020-08-14 23:53:58 -07:00
|
|
|
base64::encode(bincode::serialize(&transaction).unwrap()),
|
|
|
|
encoding,
|
|
|
|
),
|
|
|
|
UiTransactionEncoding::Json | UiTransactionEncoding::JsonParsed => {
|
2020-07-01 13:06:40 -07:00
|
|
|
let message = if encoding == UiTransactionEncoding::Json {
|
|
|
|
UiMessage::Raw(UiRawMessage {
|
2020-06-19 15:15:13 -07:00
|
|
|
header: transaction.message.header,
|
|
|
|
account_keys: transaction
|
|
|
|
.message
|
|
|
|
.account_keys
|
|
|
|
.iter()
|
|
|
|
.map(|pubkey| pubkey.to_string())
|
|
|
|
.collect(),
|
|
|
|
recent_blockhash: transaction.message.recent_blockhash.to_string(),
|
|
|
|
instructions: transaction
|
|
|
|
.message
|
|
|
|
.instructions
|
|
|
|
.iter()
|
|
|
|
.map(|instruction| instruction.into())
|
|
|
|
.collect(),
|
|
|
|
})
|
|
|
|
} else {
|
2020-07-01 13:06:40 -07:00
|
|
|
UiMessage::Parsed(UiParsedMessage {
|
2020-06-19 15:15:13 -07:00
|
|
|
account_keys: parse_accounts(&transaction.message),
|
|
|
|
recent_blockhash: transaction.message.recent_blockhash.to_string(),
|
|
|
|
instructions: transaction
|
|
|
|
.message
|
|
|
|
.instructions
|
|
|
|
.iter()
|
|
|
|
.map(|instruction| {
|
2020-09-24 07:36:22 -07:00
|
|
|
UiInstruction::parse(instruction, &transaction.message)
|
2020-06-19 15:15:13 -07:00
|
|
|
})
|
|
|
|
.collect(),
|
|
|
|
})
|
|
|
|
};
|
2020-07-01 13:06:40 -07:00
|
|
|
EncodedTransaction::Json(UiTransaction {
|
2020-06-19 15:15:13 -07:00
|
|
|
signatures: transaction
|
|
|
|
.signatures
|
2020-03-26 13:29:30 -07:00
|
|
|
.iter()
|
2020-06-19 15:15:13 -07:00
|
|
|
.map(|sig| sig.to_string())
|
2020-03-26 13:29:30 -07:00
|
|
|
.collect(),
|
2020-06-19 15:15:13 -07:00
|
|
|
message,
|
|
|
|
})
|
|
|
|
}
|
2020-03-27 22:55:55 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
pub fn decode(&self) -> Option<Transaction> {
|
2021-01-20 17:58:53 -08:00
|
|
|
let transaction: Option<Transaction> = match self {
|
2020-03-27 22:55:55 -07:00
|
|
|
EncodedTransaction::Json(_) => None,
|
2020-08-14 23:53:58 -07:00
|
|
|
EncodedTransaction::LegacyBinary(blob) => bs58::decode(blob)
|
2020-03-27 22:55:55 -07:00
|
|
|
.into_vec()
|
|
|
|
.ok()
|
|
|
|
.and_then(|bytes| bincode::deserialize(&bytes).ok()),
|
2020-08-15 17:26:00 -07:00
|
|
|
EncodedTransaction::Binary(blob, encoding) => match *encoding {
|
2020-08-15 22:06:39 -07:00
|
|
|
UiTransactionEncoding::Base58 => bs58::decode(blob)
|
|
|
|
.into_vec()
|
2020-08-15 17:26:00 -07:00
|
|
|
.ok()
|
|
|
|
.and_then(|bytes| bincode::deserialize(&bytes).ok()),
|
2020-08-15 22:06:39 -07:00
|
|
|
UiTransactionEncoding::Base64 => base64::decode(blob)
|
2020-08-15 17:26:00 -07:00
|
|
|
.ok()
|
|
|
|
.and_then(|bytes| bincode::deserialize(&bytes).ok()),
|
2020-08-15 22:06:39 -07:00
|
|
|
UiTransactionEncoding::Binary
|
|
|
|
| UiTransactionEncoding::Json
|
|
|
|
| UiTransactionEncoding::JsonParsed => None,
|
2020-08-15 17:26:00 -07:00
|
|
|
},
|
2021-01-20 17:58:53 -08:00
|
|
|
};
|
|
|
|
transaction.filter(|transaction| transaction.sanitize().is_ok())
|
2020-03-26 13:29:30 -07:00
|
|
|
}
|
|
|
|
}
|
2020-04-06 03:04:54 -07: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
|
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
|
|
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>,
|
|
|
|
}
|
|
|
|
|
2020-04-06 03:04:54 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
|
|
|
|
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(),
|
|
|
|
UiTransactionEncoding::Base58,
|
|
|
|
);
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|