Skip to main content

solana_account_decoder/
parse_sysvar.rs

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