solana_account_decoder/
parse_sysvar.rs

1#[allow(deprecated)]
2use solana_sysvar::{fees::Fees, recent_blockhashes::RecentBlockhashes};
3use {
4    crate::{
5        parse_account_data::{ParsableAccount, ParseAccountError},
6        StringAmount, UiFeeCalculator,
7    },
8    bincode::deserialize,
9    bv::BitVec,
10    solana_clock::{Clock, Epoch, Slot, UnixTimestamp},
11    solana_epoch_schedule::EpochSchedule,
12    solana_pubkey::Pubkey,
13    solana_rent::Rent,
14    solana_sdk_ids::sysvar,
15    solana_slot_hashes::SlotHashes,
16    solana_slot_history::{self as slot_history, SlotHistory},
17    solana_stake_interface::stake_history::{StakeHistory, StakeHistoryEntry},
18    solana_sysvar::{
19        epoch_rewards::EpochRewards, last_restart_slot::LastRestartSlot, rewards::Rewards,
20    },
21};
22
23pub fn parse_sysvar(data: &[u8], pubkey: &Pubkey) -> Result<SysvarAccountType, ParseAccountError> {
24    #[allow(deprecated)]
25    let parsed_account = {
26        if pubkey == &sysvar::clock::id() {
27            deserialize::<Clock>(data)
28                .ok()
29                .map(|clock| SysvarAccountType::Clock(clock.into()))
30        } else if pubkey == &sysvar::epoch_schedule::id() {
31            deserialize(data).ok().map(SysvarAccountType::EpochSchedule)
32        } else if pubkey == &sysvar::fees::id() {
33            deserialize::<Fees>(data)
34                .ok()
35                .map(|fees| SysvarAccountType::Fees(fees.into()))
36        } else if pubkey == &sysvar::recent_blockhashes::id() {
37            deserialize::<RecentBlockhashes>(data)
38                .ok()
39                .map(|recent_blockhashes| {
40                    let recent_blockhashes = recent_blockhashes
41                        .iter()
42                        .map(|entry| UiRecentBlockhashesEntry {
43                            blockhash: entry.blockhash.to_string(),
44                            fee_calculator: entry.fee_calculator.into(),
45                        })
46                        .collect();
47                    SysvarAccountType::RecentBlockhashes(recent_blockhashes)
48                })
49        } else if pubkey == &sysvar::rent::id() {
50            deserialize::<Rent>(data)
51                .ok()
52                .map(|rent| SysvarAccountType::Rent(rent.into()))
53        } else if pubkey == &sysvar::rewards::id() {
54            deserialize::<Rewards>(data)
55                .ok()
56                .map(|rewards| SysvarAccountType::Rewards(rewards.into()))
57        } else if pubkey == &sysvar::slot_hashes::id() {
58            deserialize::<SlotHashes>(data).ok().map(|slot_hashes| {
59                let slot_hashes = slot_hashes
60                    .iter()
61                    .map(|slot_hash| UiSlotHashEntry {
62                        slot: slot_hash.0,
63                        hash: slot_hash.1.to_string(),
64                    })
65                    .collect();
66                SysvarAccountType::SlotHashes(slot_hashes)
67            })
68        } else if pubkey == &sysvar::slot_history::id() {
69            deserialize::<SlotHistory>(data).ok().map(|slot_history| {
70                SysvarAccountType::SlotHistory(UiSlotHistory {
71                    next_slot: slot_history.next_slot,
72                    bits: format!("{:?}", SlotHistoryBits(slot_history.bits)),
73                })
74            })
75        } else if pubkey == &sysvar::stake_history::id() {
76            deserialize::<StakeHistory>(data).ok().map(|stake_history| {
77                let stake_history = stake_history
78                    .iter()
79                    .map(|entry| UiStakeHistoryEntry {
80                        epoch: entry.0,
81                        stake_history: entry.1.clone(),
82                    })
83                    .collect();
84                SysvarAccountType::StakeHistory(stake_history)
85            })
86        } else if pubkey == &sysvar::last_restart_slot::id() {
87            deserialize::<LastRestartSlot>(data)
88                .ok()
89                .map(|last_restart_slot| {
90                    let last_restart_slot = last_restart_slot.last_restart_slot;
91                    SysvarAccountType::LastRestartSlot(UiLastRestartSlot { last_restart_slot })
92                })
93        } else if pubkey == &sysvar::epoch_rewards::id() {
94            deserialize::<EpochRewards>(data)
95                .ok()
96                .map(|epoch_rewards| SysvarAccountType::EpochRewards(epoch_rewards.into()))
97        } else {
98            None
99        }
100    };
101    parsed_account.ok_or(ParseAccountError::AccountNotParsable(
102        ParsableAccount::Sysvar,
103    ))
104}
105
106#[derive(Debug, Serialize, Deserialize, PartialEq)]
107#[serde(rename_all = "camelCase", tag = "type", content = "info")]
108pub enum SysvarAccountType {
109    Clock(UiClock),
110    EpochSchedule(EpochSchedule),
111    #[allow(deprecated)]
112    Fees(UiFees),
113    #[allow(deprecated)]
114    RecentBlockhashes(Vec<UiRecentBlockhashesEntry>),
115    Rent(UiRent),
116    Rewards(UiRewards),
117    SlotHashes(Vec<UiSlotHashEntry>),
118    SlotHistory(UiSlotHistory),
119    StakeHistory(Vec<UiStakeHistoryEntry>),
120    LastRestartSlot(UiLastRestartSlot),
121    EpochRewards(UiEpochRewards),
122}
123
124#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
125#[serde(rename_all = "camelCase")]
126pub struct UiClock {
127    pub slot: Slot,
128    pub epoch: Epoch,
129    pub epoch_start_timestamp: UnixTimestamp,
130    pub leader_schedule_epoch: Epoch,
131    pub unix_timestamp: UnixTimestamp,
132}
133
134impl From<Clock> for UiClock {
135    fn from(clock: Clock) -> Self {
136        Self {
137            slot: clock.slot,
138            epoch: clock.epoch,
139            epoch_start_timestamp: clock.epoch_start_timestamp,
140            leader_schedule_epoch: clock.leader_schedule_epoch,
141            unix_timestamp: clock.unix_timestamp,
142        }
143    }
144}
145
146#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
147#[serde(rename_all = "camelCase")]
148pub struct UiFees {
149    pub fee_calculator: UiFeeCalculator,
150}
151#[allow(deprecated)]
152impl From<Fees> for UiFees {
153    fn from(fees: Fees) -> Self {
154        Self {
155            fee_calculator: fees.fee_calculator.into(),
156        }
157    }
158}
159
160#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
161#[serde(rename_all = "camelCase")]
162pub struct UiRent {
163    pub lamports_per_byte_year: StringAmount,
164    pub exemption_threshold: f64,
165    pub burn_percent: u8,
166}
167
168impl From<Rent> for UiRent {
169    fn from(rent: Rent) -> Self {
170        Self {
171            lamports_per_byte_year: rent.lamports_per_byte_year.to_string(),
172            exemption_threshold: rent.exemption_threshold,
173            burn_percent: rent.burn_percent,
174        }
175    }
176}
177
178#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
179#[serde(rename_all = "camelCase")]
180pub struct UiRewards {
181    pub validator_point_value: f64,
182}
183
184impl From<Rewards> for UiRewards {
185    fn from(rewards: Rewards) -> Self {
186        Self {
187            validator_point_value: rewards.validator_point_value,
188        }
189    }
190}
191
192#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
193#[serde(rename_all = "camelCase")]
194pub struct UiRecentBlockhashesEntry {
195    pub blockhash: String,
196    pub fee_calculator: UiFeeCalculator,
197}
198
199#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
200#[serde(rename_all = "camelCase")]
201pub struct UiSlotHashEntry {
202    pub slot: Slot,
203    pub hash: String,
204}
205
206#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
207#[serde(rename_all = "camelCase")]
208pub struct UiSlotHistory {
209    pub next_slot: Slot,
210    pub bits: String,
211}
212
213struct SlotHistoryBits(BitVec<u64>);
214
215impl std::fmt::Debug for SlotHistoryBits {
216    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217        for i in 0..slot_history::MAX_ENTRIES {
218            if self.0.get(i) {
219                write!(f, "1")?;
220            } else {
221                write!(f, "0")?;
222            }
223        }
224        Ok(())
225    }
226}
227
228#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
229#[serde(rename_all = "camelCase")]
230pub struct UiStakeHistoryEntry {
231    pub epoch: Epoch,
232    pub stake_history: StakeHistoryEntry,
233}
234
235#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
236#[serde(rename_all = "camelCase")]
237pub struct UiLastRestartSlot {
238    pub last_restart_slot: Slot,
239}
240
241#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
242#[serde(rename_all = "camelCase")]
243pub struct UiEpochRewards {
244    pub distribution_starting_block_height: u64,
245    pub num_partitions: u64,
246    pub parent_blockhash: String,
247    pub total_points: String,
248    pub total_rewards: String,
249    pub distributed_rewards: String,
250    pub active: bool,
251}
252
253impl From<EpochRewards> for UiEpochRewards {
254    fn from(epoch_rewards: EpochRewards) -> Self {
255        Self {
256            distribution_starting_block_height: epoch_rewards.distribution_starting_block_height,
257            num_partitions: epoch_rewards.num_partitions,
258            parent_blockhash: epoch_rewards.parent_blockhash.to_string(),
259            total_points: epoch_rewards.total_points.to_string(),
260            total_rewards: epoch_rewards.total_rewards.to_string(),
261            distributed_rewards: epoch_rewards.distributed_rewards.to_string(),
262            active: epoch_rewards.active,
263        }
264    }
265}
266
267#[cfg(test)]
268mod test {
269    #[allow(deprecated)]
270    use solana_sysvar::recent_blockhashes::IterItem;
271    use {
272        super::*, solana_account::create_account_for_test, solana_fee_calculator::FeeCalculator,
273        solana_hash::Hash,
274    };
275
276    #[test]
277    fn test_parse_sysvars() {
278        let hash = Hash::new_from_array([1; 32]);
279
280        let clock_sysvar = create_account_for_test(&Clock::default());
281        assert_eq!(
282            parse_sysvar(&clock_sysvar.data, &sysvar::clock::id()).unwrap(),
283            SysvarAccountType::Clock(UiClock::default()),
284        );
285
286        let epoch_schedule = EpochSchedule {
287            slots_per_epoch: 12,
288            leader_schedule_slot_offset: 0,
289            warmup: false,
290            first_normal_epoch: 1,
291            first_normal_slot: 12,
292        };
293        let epoch_schedule_sysvar = create_account_for_test(&epoch_schedule);
294        assert_eq!(
295            parse_sysvar(&epoch_schedule_sysvar.data, &sysvar::epoch_schedule::id()).unwrap(),
296            SysvarAccountType::EpochSchedule(epoch_schedule),
297        );
298
299        #[allow(deprecated)]
300        {
301            let fees_sysvar = create_account_for_test(&Fees::default());
302            assert_eq!(
303                parse_sysvar(&fees_sysvar.data, &sysvar::fees::id()).unwrap(),
304                SysvarAccountType::Fees(UiFees::default()),
305            );
306
307            let recent_blockhashes: RecentBlockhashes =
308                vec![IterItem(0, &hash, 10)].into_iter().collect();
309            let recent_blockhashes_sysvar = create_account_for_test(&recent_blockhashes);
310            assert_eq!(
311                parse_sysvar(
312                    &recent_blockhashes_sysvar.data,
313                    &sysvar::recent_blockhashes::id()
314                )
315                .unwrap(),
316                SysvarAccountType::RecentBlockhashes(vec![UiRecentBlockhashesEntry {
317                    blockhash: hash.to_string(),
318                    fee_calculator: FeeCalculator::new(10).into(),
319                }]),
320            );
321        }
322
323        let rent = Rent {
324            lamports_per_byte_year: 10,
325            exemption_threshold: 2.0,
326            burn_percent: 5,
327        };
328        let rent_sysvar = create_account_for_test(&rent);
329        assert_eq!(
330            parse_sysvar(&rent_sysvar.data, &sysvar::rent::id()).unwrap(),
331            SysvarAccountType::Rent(rent.into()),
332        );
333
334        let rewards_sysvar = create_account_for_test(&Rewards::default());
335        assert_eq!(
336            parse_sysvar(&rewards_sysvar.data, &sysvar::rewards::id()).unwrap(),
337            SysvarAccountType::Rewards(UiRewards::default()),
338        );
339
340        let mut slot_hashes = SlotHashes::default();
341        slot_hashes.add(1, hash);
342        let slot_hashes_sysvar = create_account_for_test(&slot_hashes);
343        assert_eq!(
344            parse_sysvar(&slot_hashes_sysvar.data, &sysvar::slot_hashes::id()).unwrap(),
345            SysvarAccountType::SlotHashes(vec![UiSlotHashEntry {
346                slot: 1,
347                hash: hash.to_string(),
348            }]),
349        );
350
351        let mut slot_history = SlotHistory::default();
352        slot_history.add(42);
353        let slot_history_sysvar = create_account_for_test(&slot_history);
354        assert_eq!(
355            parse_sysvar(&slot_history_sysvar.data, &sysvar::slot_history::id()).unwrap(),
356            SysvarAccountType::SlotHistory(UiSlotHistory {
357                next_slot: slot_history.next_slot,
358                bits: format!("{:?}", SlotHistoryBits(slot_history.bits)),
359            }),
360        );
361
362        let mut stake_history = StakeHistory::default();
363        let stake_history_entry = StakeHistoryEntry {
364            effective: 10,
365            activating: 2,
366            deactivating: 3,
367        };
368        stake_history.add(1, stake_history_entry.clone());
369        let stake_history_sysvar = create_account_for_test(&stake_history);
370        assert_eq!(
371            parse_sysvar(&stake_history_sysvar.data, &sysvar::stake_history::id()).unwrap(),
372            SysvarAccountType::StakeHistory(vec![UiStakeHistoryEntry {
373                epoch: 1,
374                stake_history: stake_history_entry,
375            }]),
376        );
377
378        let bad_pubkey = solana_pubkey::new_rand();
379        assert!(parse_sysvar(&stake_history_sysvar.data, &bad_pubkey).is_err());
380
381        let bad_data = vec![0; 4];
382        assert!(parse_sysvar(&bad_data, &sysvar::stake_history::id()).is_err());
383
384        let last_restart_slot = LastRestartSlot {
385            last_restart_slot: 1282,
386        };
387        let last_restart_slot_account = create_account_for_test(&last_restart_slot);
388        assert_eq!(
389            parse_sysvar(
390                &last_restart_slot_account.data,
391                &sysvar::last_restart_slot::id()
392            )
393            .unwrap(),
394            SysvarAccountType::LastRestartSlot(UiLastRestartSlot {
395                last_restart_slot: 1282
396            })
397        );
398
399        let epoch_rewards = EpochRewards {
400            distribution_starting_block_height: 42,
401            total_rewards: 100,
402            distributed_rewards: 20,
403            active: true,
404            ..EpochRewards::default()
405        };
406        let epoch_rewards_sysvar = create_account_for_test(&epoch_rewards);
407        assert_eq!(
408            parse_sysvar(&epoch_rewards_sysvar.data, &sysvar::epoch_rewards::id()).unwrap(),
409            SysvarAccountType::EpochRewards(epoch_rewards.into()),
410        );
411    }
412}