solana_storage_proto/
lib.rs

1#![cfg_attr(
2    not(feature = "agave-unstable-api"),
3    deprecated(
4        since = "3.1.0",
5        note = "This crate has been marked for formal inclusion in the Agave Unstable API. From \
6                v4.0.0 onward, the `agave-unstable-api` crate feature must be specified to \
7                acknowledge use of an interface that may break without warning."
8    )
9)]
10use {
11    serde::{Deserialize, Serialize},
12    solana_account_decoder::{
13        parse_token::{real_number_string_trimmed, UiTokenAmount},
14        StringAmount,
15    },
16    solana_message::v0::LoadedAddresses,
17    solana_serde::default_on_eof,
18    solana_transaction_context::TransactionReturnData,
19    solana_transaction_error::{TransactionError, TransactionResult as Result},
20    solana_transaction_status::{
21        InnerInstructions, Reward, RewardType, TransactionStatusMeta, TransactionTokenBalance,
22    },
23    std::str::FromStr,
24};
25
26pub mod convert;
27
28pub type StoredExtendedRewards = Vec<StoredExtendedReward>;
29
30#[derive(Serialize, Deserialize)]
31pub struct StoredExtendedReward {
32    pubkey: String,
33    lamports: i64,
34    #[serde(deserialize_with = "default_on_eof")]
35    post_balance: u64,
36    #[serde(deserialize_with = "default_on_eof")]
37    reward_type: Option<RewardType>,
38    #[serde(deserialize_with = "default_on_eof")]
39    commission: Option<u8>,
40}
41
42impl From<StoredExtendedReward> for Reward {
43    fn from(value: StoredExtendedReward) -> Self {
44        let StoredExtendedReward {
45            pubkey,
46            lamports,
47            post_balance,
48            reward_type,
49            commission,
50        } = value;
51        Self {
52            pubkey,
53            lamports,
54            post_balance,
55            reward_type,
56            commission,
57        }
58    }
59}
60
61impl From<Reward> for StoredExtendedReward {
62    fn from(value: Reward) -> Self {
63        let Reward {
64            pubkey,
65            lamports,
66            post_balance,
67            reward_type,
68            commission,
69        } = value;
70        Self {
71            pubkey,
72            lamports,
73            post_balance,
74            reward_type,
75            commission,
76        }
77    }
78}
79
80#[derive(Serialize, Deserialize)]
81pub struct StoredTokenAmount {
82    pub ui_amount: f64,
83    pub decimals: u8,
84    pub amount: StringAmount,
85}
86
87impl From<StoredTokenAmount> for UiTokenAmount {
88    fn from(value: StoredTokenAmount) -> Self {
89        let StoredTokenAmount {
90            ui_amount,
91            decimals,
92            amount,
93        } = value;
94        let ui_amount_string =
95            real_number_string_trimmed(u64::from_str(&amount).unwrap_or(0), decimals);
96        Self {
97            ui_amount: Some(ui_amount),
98            decimals,
99            amount,
100            ui_amount_string,
101        }
102    }
103}
104
105impl From<UiTokenAmount> for StoredTokenAmount {
106    fn from(value: UiTokenAmount) -> Self {
107        let UiTokenAmount {
108            ui_amount,
109            decimals,
110            amount,
111            ..
112        } = value;
113        Self {
114            ui_amount: ui_amount.unwrap_or(0.0),
115            decimals,
116            amount,
117        }
118    }
119}
120
121struct StoredTransactionError(Vec<u8>);
122
123impl From<StoredTransactionError> for TransactionError {
124    fn from(value: StoredTransactionError) -> Self {
125        let bytes = value.0;
126        bincode::deserialize(&bytes).expect("transaction error to deserialize from bytes")
127    }
128}
129
130impl From<TransactionError> for StoredTransactionError {
131    fn from(value: TransactionError) -> Self {
132        let bytes = bincode::serialize(&value).expect("transaction error to serialize to bytes");
133        StoredTransactionError(bytes)
134    }
135}
136
137#[derive(Serialize, Deserialize)]
138pub struct StoredTransactionTokenBalance {
139    pub account_index: u8,
140    pub mint: String,
141    pub ui_token_amount: StoredTokenAmount,
142    #[serde(deserialize_with = "default_on_eof")]
143    pub owner: String,
144    #[serde(deserialize_with = "default_on_eof")]
145    pub program_id: String,
146}
147
148impl From<StoredTransactionTokenBalance> for TransactionTokenBalance {
149    fn from(value: StoredTransactionTokenBalance) -> Self {
150        let StoredTransactionTokenBalance {
151            account_index,
152            mint,
153            ui_token_amount,
154            owner,
155            program_id,
156        } = value;
157        Self {
158            account_index,
159            mint,
160            ui_token_amount: ui_token_amount.into(),
161            owner,
162            program_id,
163        }
164    }
165}
166
167impl From<TransactionTokenBalance> for StoredTransactionTokenBalance {
168    fn from(value: TransactionTokenBalance) -> Self {
169        let TransactionTokenBalance {
170            account_index,
171            mint,
172            ui_token_amount,
173            owner,
174            program_id,
175        } = value;
176        Self {
177            account_index,
178            mint,
179            ui_token_amount: ui_token_amount.into(),
180            owner,
181            program_id,
182        }
183    }
184}
185
186#[derive(Serialize, Deserialize)]
187pub struct StoredTransactionStatusMeta {
188    pub status: Result<()>,
189    pub fee: u64,
190    pub pre_balances: Vec<u64>,
191    pub post_balances: Vec<u64>,
192    #[serde(deserialize_with = "default_on_eof")]
193    pub inner_instructions: Option<Vec<InnerInstructions>>,
194    #[serde(deserialize_with = "default_on_eof")]
195    pub log_messages: Option<Vec<String>>,
196    #[serde(deserialize_with = "default_on_eof")]
197    pub pre_token_balances: Option<Vec<StoredTransactionTokenBalance>>,
198    #[serde(deserialize_with = "default_on_eof")]
199    pub post_token_balances: Option<Vec<StoredTransactionTokenBalance>>,
200    #[serde(deserialize_with = "default_on_eof")]
201    pub rewards: Option<Vec<StoredExtendedReward>>,
202    #[serde(deserialize_with = "default_on_eof")]
203    pub return_data: Option<TransactionReturnData>,
204    #[serde(deserialize_with = "default_on_eof")]
205    pub compute_units_consumed: Option<u64>,
206    #[serde(deserialize_with = "default_on_eof")]
207    pub cost_units: Option<u64>,
208}
209
210impl From<StoredTransactionStatusMeta> for TransactionStatusMeta {
211    fn from(value: StoredTransactionStatusMeta) -> Self {
212        let StoredTransactionStatusMeta {
213            status,
214            fee,
215            pre_balances,
216            post_balances,
217            inner_instructions,
218            log_messages,
219            pre_token_balances,
220            post_token_balances,
221            rewards,
222            return_data,
223            compute_units_consumed,
224            cost_units,
225        } = value;
226        Self {
227            status,
228            fee,
229            pre_balances,
230            post_balances,
231            inner_instructions,
232            log_messages,
233            pre_token_balances: pre_token_balances
234                .map(|balances| balances.into_iter().map(|balance| balance.into()).collect()),
235            post_token_balances: post_token_balances
236                .map(|balances| balances.into_iter().map(|balance| balance.into()).collect()),
237            rewards: rewards
238                .map(|rewards| rewards.into_iter().map(|reward| reward.into()).collect()),
239            loaded_addresses: LoadedAddresses::default(),
240            return_data,
241            compute_units_consumed,
242            cost_units,
243        }
244    }
245}
246
247impl TryFrom<TransactionStatusMeta> for StoredTransactionStatusMeta {
248    type Error = bincode::Error;
249    fn try_from(value: TransactionStatusMeta) -> std::result::Result<Self, Self::Error> {
250        let TransactionStatusMeta {
251            status,
252            fee,
253            pre_balances,
254            post_balances,
255            inner_instructions,
256            log_messages,
257            pre_token_balances,
258            post_token_balances,
259            rewards,
260            loaded_addresses,
261            return_data,
262            compute_units_consumed,
263            cost_units,
264        } = value;
265
266        if !loaded_addresses.is_empty() {
267            // Deprecated bincode serialized status metadata doesn't support
268            // loaded addresses.
269            return Err(
270                bincode::ErrorKind::Custom("Bincode serialization is deprecated".into()).into(),
271            );
272        }
273
274        Ok(Self {
275            status,
276            fee,
277            pre_balances,
278            post_balances,
279            inner_instructions,
280            log_messages,
281            pre_token_balances: pre_token_balances
282                .map(|balances| balances.into_iter().map(|balance| balance.into()).collect()),
283            post_token_balances: post_token_balances
284                .map(|balances| balances.into_iter().map(|balance| balance.into()).collect()),
285            rewards: rewards
286                .map(|rewards| rewards.into_iter().map(|reward| reward.into()).collect()),
287            return_data,
288            compute_units_consumed,
289            cost_units,
290        })
291    }
292}
293
294#[cfg(test)]
295mod tests {
296    use {
297        crate::StoredTransactionError, solana_instruction::error::InstructionError,
298        solana_transaction_error::TransactionError, test_case::test_case,
299    };
300
301    #[test_case(TransactionError::InsufficientFundsForFee; "Named variant error")]
302    #[test_case(TransactionError::InsufficientFundsForRent { account_index: 42 }; "Struct variant error")]
303    #[test_case(TransactionError::DuplicateInstruction(42); "Single-value tuple variant error")]
304    #[test_case(TransactionError::InstructionError(42, InstructionError::Custom(0xdeadbeef)); "`InstructionError`")]
305    fn test_serialize_transaction_error_to_stored_transaction_error_round_trip(
306        err: TransactionError,
307    ) {
308        let serialized: StoredTransactionError = err.clone().into();
309        let deserialized: TransactionError = serialized.into();
310        assert_eq!(deserialized, err);
311    }
312
313    #[test_case(
314        vec![4, 0, 0, 0,  /* Fourth enum variant - `InsufficientFundsForFee` */],
315        TransactionError::InsufficientFundsForFee;
316        "Named variant error"
317    )]
318    #[test_case(
319        vec![
320            31, 0, 0, 0,  /* Thirty-first enum variant - `InsufficientFundsForRent` */
321            42, /* Account index */
322        ],
323        TransactionError::InsufficientFundsForRent { account_index: 42 };
324        "Struct variant error"
325    )]
326    #[test_case(
327        vec![
328            30, 0, 0, 0,  /* Thirtieth enum variant - `DuplicateInstruction` */
329            42, /* Instruction index */
330        ],
331        TransactionError::DuplicateInstruction(42);
332        "Single-value tuple variant error"
333    )]
334    #[test_case(
335        vec![
336            8, 0, 0, 0,  /* Eighth enum variant - `InstructionError` */
337            42, /* Outer instruction index */
338            25, 0, 0, 0, /* InstructionError::Custom */
339            /* 0xdeadbeef */
340            239, 190, 173, 222,
341        ],
342        TransactionError::InstructionError(42, InstructionError::Custom(0xdeadbeef));
343        "`InstructionError`"
344    )]
345    fn test_deserialize_stored_transaction_error(
346        stored_bytes: Vec<u8>,
347        expected_transaction_error: TransactionError,
348    ) {
349        let stored_transaction = StoredTransactionError(stored_bytes);
350        let deserialized: TransactionError = stored_transaction.into();
351        assert_eq!(deserialized, expected_transaction_error);
352    }
353
354    #[test_case(
355        vec![4, 0, 0, 0,  /* Fourth enum variant - `InsufficientFundsForFee` */],
356        TransactionError::InsufficientFundsForFee;
357        "Named variant error"
358    )]
359    #[test_case(
360        vec![
361            31, 0, 0, 0,  /* Thirty-first enum variant - `InsufficientFundsForRent` */
362            42, /* Account index */
363        ],
364        TransactionError::InsufficientFundsForRent { account_index: 42 };
365        "Struct variant error"
366    )]
367    #[test_case(
368        vec![
369            30, 0, 0, 0,  /* Thirtieth enum variant - `DuplicateInstruction` */
370            42, /* Instruction index */
371        ],
372        TransactionError::DuplicateInstruction(42);
373        "Single-value tuple variant error"
374    )]
375    #[test_case(
376        vec![
377            8, 0, 0, 0,  /* Eighth enum variant - `InstructionError` */
378            42, /* Outer instruction index */
379            25, 0, 0, 0, /* InstructionError::Custom */
380            /* 0xdeadbeef */
381            239, 190, 173, 222,
382        ],
383        TransactionError::InstructionError(42, InstructionError::Custom(0xdeadbeef));
384        "`InstructionError`"
385    )]
386    fn test_seserialize_stored_transaction_error(
387        expected_serialized_bytes: Vec<u8>,
388        transaction_error: TransactionError,
389    ) {
390        let StoredTransactionError(serialized_bytes) = transaction_error.into();
391        assert_eq!(serialized_bytes, expected_serialized_bytes);
392    }
393}