data_anchor_client/batch_client/
transaction.rs

1use solana_client::client_error::ClientError as Error;
2use solana_sdk::{
3    clock::Slot, commitment_config::CommitmentConfig, signature::Signature,
4    transaction::TransactionError,
5};
6use solana_transaction_status::TransactionStatus as SolanaTransactionStatus;
7
8/// The final outcome of a transaction after the [`BatchClient`] is done, either successfully
9/// or due to reaching the timeout.
10#[derive(Debug)]
11pub enum TransactionOutcome<T> {
12    /// The transaction was successfully confirmed by the network at the desired commitment level.
13    Success(SuccessfulTransaction<T>),
14    /// Either the transaction was not submitted to the network, or it was submitted but not confirmed.
15    Unknown(UnknownTransaction<T>),
16    /// The transaction latest status contained an error.
17    Failure(FailedTransaction<T>),
18}
19
20/// A transaction that was successfully confirmed by the network at the desired commitment level.
21#[derive(Debug)]
22pub struct SuccessfulTransaction<T> {
23    pub data: T,
24    pub slot: Slot,
25    pub signature: Signature,
26}
27
28/// A transaction that either was not submitted to the network, or it was submitted but not confirmed.
29#[derive(Debug)]
30pub struct UnknownTransaction<T> {
31    pub data: T,
32}
33
34/// A transaction that resulted in an error.
35#[derive(Debug)]
36pub struct FailedTransaction<T> {
37    pub data: T,
38    pub error: Error,
39    pub logs: Vec<String>,
40}
41
42impl<T> TransactionOutcome<T> {
43    /// Returns `true` if the outcome was successful.
44    pub fn successful(&self) -> bool {
45        match self {
46            TransactionOutcome::Success(_) => true,
47            TransactionOutcome::Unknown(_) | TransactionOutcome::Failure(_) => false,
48        }
49    }
50
51    /// Returns [`Option::Some`] if the outcome was successful, or [`Option::None`] otherwise.
52    pub fn into_successful(self) -> Option<SuccessfulTransaction<T>> {
53        match self {
54            TransactionOutcome::Success(s) => Some(s),
55            TransactionOutcome::Unknown(_) | TransactionOutcome::Failure(_) => None,
56        }
57    }
58
59    /// Returns a reference to the inner [`FailedTransaction`] if the outcome was a failure, or [`None`] otherwise.
60    pub fn error(&self) -> Option<&FailedTransaction<T>> {
61        match self {
62            TransactionOutcome::Success(_) => None,
63            TransactionOutcome::Unknown(_) => None,
64            TransactionOutcome::Failure(f) => Some(f),
65        }
66    }
67}
68
69/// Tracks the progress of a transaction, and holds on to its associated data.
70pub struct TransactionProgress<T> {
71    pub data: T,
72    pub landed_as: Option<(Slot, Signature)>,
73    pub status: TransactionStatus,
74}
75
76impl<T> TransactionProgress<T> {
77    pub fn new(data: T) -> Self {
78        Self {
79            data,
80            landed_as: None,
81            status: TransactionStatus::Pending,
82        }
83    }
84}
85
86/// Describes the current status of a transaction, whether it has been submitted or not.
87#[derive(Clone, Debug, PartialEq, Eq)]
88pub enum TransactionStatus {
89    Pending,
90    Processing,
91    Committed,
92    Failed(TransactionError, Vec<String>),
93}
94
95impl TransactionStatus {
96    /// Translates from a [`SolanaTransactionStatus`] and a [commitment level](`CommitmentConfig`)
97    /// to a [`TransactionStatus`].
98    pub fn from_solana_status(
99        status: SolanaTransactionStatus,
100        logs: Vec<String>,
101        commitment: CommitmentConfig,
102    ) -> Self {
103        if let Some(TransactionError::AlreadyProcessed) = status.err {
104            TransactionStatus::Committed
105        } else if let Some(err) = status.err {
106            TransactionStatus::Failed(err, logs)
107        } else if status.satisfies_commitment(commitment) {
108            TransactionStatus::Committed
109        } else {
110            TransactionStatus::Processing
111        }
112    }
113
114    /// Checks whether a transaction should be re-confirmed based on its status.
115    ///
116    /// These should be re-confirmed:
117    /// - [`TransactionStatus::Pending`]
118    /// - [`TransactionStatus::Processing`]
119    ///
120    /// These should *not* be re-confirmed:
121    /// - [`TransactionStatus::Committed`]
122    /// - [`TransactionStatus::Failed`]
123    pub fn should_be_reconfirmed(&self) -> bool {
124        match self {
125            TransactionStatus::Pending => true,
126            TransactionStatus::Processing => true,
127            TransactionStatus::Committed => false,
128            TransactionStatus::Failed(..) => false,
129        }
130    }
131}
132
133impl<T> From<TransactionProgress<T>> for TransactionOutcome<T> {
134    fn from(progress: TransactionProgress<T>) -> Self {
135        match progress.status {
136            TransactionStatus::Pending | TransactionStatus::Processing => {
137                TransactionOutcome::Unknown(UnknownTransaction {
138                    data: progress.data,
139                })
140            }
141            TransactionStatus::Failed(err, logs) => {
142                TransactionOutcome::Failure(FailedTransaction {
143                    data: progress.data,
144                    error: err.into(),
145                    logs,
146                })
147            }
148            TransactionStatus::Committed => TransactionOutcome::Success(SuccessfulTransaction {
149                data: progress.data,
150                slot: progress.landed_as.unwrap().0,
151                signature: progress.landed_as.unwrap().1,
152            }),
153        }
154    }
155}