iota_indexer/models/
epoch.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use diesel::{
6    Insertable, Queryable, Selectable,
7    prelude::{AsChangeset, Identifiable},
8};
9use iota_json_rpc_types::{EndOfEpochInfo, EpochInfo};
10use iota_types::{
11    iota_system_state::iota_system_state_summary::IotaSystemStateSummary,
12    messages_checkpoint::CertifiedCheckpointSummary,
13};
14
15use crate::{
16    errors::IndexerError,
17    models::system_state::{StoredSystemState, StoredSystemStateV1},
18    schema::{epochs, feature_flags, protocol_configs},
19    types::IndexedEpochInfoEvent,
20};
21
22#[derive(Queryable, Insertable, Debug, Clone, Default)]
23#[diesel(table_name = epochs)]
24#[diesel(check_for_backend(diesel::pg::Pg))]
25pub struct StoredEpochInfo {
26    pub epoch: i64,
27    pub first_checkpoint_id: i64,
28    pub epoch_start_timestamp: i64,
29    pub reference_gas_price: i64,
30    pub protocol_version: i64,
31    pub total_stake: i64,
32    pub storage_fund_balance: i64,
33    pub system_state: Vec<u8>,
34    /// Total number of network transactions at the end of the epoch.
35    pub network_total_transactions: Option<i64>,
36    pub last_checkpoint_id: Option<i64>,
37    pub epoch_end_timestamp: Option<i64>,
38    pub storage_charge: Option<i64>,
39    pub storage_rebate: Option<i64>,
40    pub total_gas_fees: Option<i64>,
41    pub total_stake_rewards_distributed: Option<i64>,
42    pub epoch_commitments: Option<Vec<u8>>,
43    pub burnt_tokens_amount: Option<i64>,
44    pub minted_tokens_amount: Option<i64>,
45    /// First transaction sequence number of this epoch.
46    pub first_tx_sequence_number: i64,
47}
48
49impl StoredEpochInfo {
50    pub fn epoch_total_transactions(&self) -> Option<i64> {
51        self.network_total_transactions
52            .map(|total_tx| total_tx - self.first_tx_sequence_number)
53    }
54}
55
56#[derive(Queryable, Insertable, Debug, Clone, Default)]
57#[diesel(table_name = protocol_configs)]
58pub struct StoredProtocolConfig {
59    pub protocol_version: i64,
60    pub config_name: String,
61    pub config_value: Option<String>,
62}
63
64#[derive(Queryable, Insertable, Debug, Clone, Default)]
65#[diesel(table_name = feature_flags)]
66pub struct StoredFeatureFlag {
67    pub protocol_version: i64,
68    pub flag_name: String,
69    pub flag_value: bool,
70}
71
72#[derive(Queryable, Selectable, Clone)]
73#[diesel(table_name = epochs)]
74#[diesel(check_for_backend(diesel::pg::Pg))]
75pub struct QueryableEpochInfo {
76    pub epoch: i64,
77    pub first_checkpoint_id: i64,
78    pub epoch_start_timestamp: i64,
79    pub reference_gas_price: i64,
80    pub protocol_version: i64,
81    pub total_stake: i64,
82    pub storage_fund_balance: i64,
83    pub network_total_transactions: Option<i64>,
84    pub last_checkpoint_id: Option<i64>,
85    pub epoch_end_timestamp: Option<i64>,
86    pub storage_charge: Option<i64>,
87    pub storage_rebate: Option<i64>,
88    pub total_gas_fees: Option<i64>,
89    pub total_stake_rewards_distributed: Option<i64>,
90    pub epoch_commitments: Option<Vec<u8>>,
91    pub burnt_tokens_amount: Option<i64>,
92    pub minted_tokens_amount: Option<i64>,
93    pub first_tx_sequence_number: i64,
94}
95
96impl QueryableEpochInfo {
97    pub fn epoch_total_transactions(&self) -> Option<i64> {
98        self.network_total_transactions
99            .map(|total_tx| total_tx - self.first_tx_sequence_number)
100    }
101}
102
103#[derive(Queryable)]
104pub struct QueryableEpochSystemState {
105    pub epoch: i64,
106    pub system_state: Vec<u8>,
107}
108
109#[derive(Insertable, Identifiable, AsChangeset, Clone, Debug)]
110#[diesel(primary_key(epoch))]
111#[diesel(table_name = epochs)]
112pub(crate) struct StartOfEpochUpdate {
113    pub epoch: i64,
114    pub first_checkpoint_id: i64,
115    pub first_tx_sequence_number: i64,
116    pub epoch_start_timestamp: i64,
117    pub reference_gas_price: i64,
118    pub protocol_version: i64,
119    pub total_stake: i64,
120    pub storage_fund_balance: i64,
121    pub system_state: Vec<u8>,
122}
123
124#[derive(Identifiable, AsChangeset, Clone, Debug)]
125#[diesel(primary_key(epoch))]
126#[diesel(table_name = epochs)]
127pub(crate) struct EndOfEpochUpdate {
128    pub epoch: i64,
129    pub network_total_transactions: i64,
130    pub last_checkpoint_id: i64,
131    pub epoch_end_timestamp: i64,
132    pub storage_charge: i64,
133    pub storage_rebate: i64,
134    pub total_gas_fees: i64,
135    pub total_stake_rewards_distributed: i64,
136    pub epoch_commitments: Vec<u8>,
137    pub burnt_tokens_amount: i64,
138    pub minted_tokens_amount: i64,
139}
140
141impl StartOfEpochUpdate {
142    pub fn new(
143        new_system_state_summary: &IotaSystemStateSummary,
144        first_checkpoint_id: u64,
145        first_tx_sequence_number: u64,
146        event: Option<&IndexedEpochInfoEvent>,
147    ) -> Self {
148        // NOTE: total_stake and storage_fund_balance are about new epoch,
149        // although the event is generated at the end of the previous epoch,
150        // the event is optional b/c no such event for the first epoch.
151        let (total_stake, storage_fund_balance) = match event {
152            Some(event) => (event.total_stake, event.storage_fund_balance),
153            None => (0, 0),
154        };
155        let stored_system_state = StoredSystemState::from(new_system_state_summary.clone());
156        Self {
157            epoch: new_system_state_summary.epoch() as i64,
158            first_checkpoint_id: first_checkpoint_id as i64,
159            first_tx_sequence_number: first_tx_sequence_number as i64,
160            epoch_start_timestamp: new_system_state_summary.epoch_start_timestamp_ms() as i64,
161            reference_gas_price: new_system_state_summary.reference_gas_price() as i64,
162            protocol_version: new_system_state_summary.protocol_version().as_u64() as i64,
163            total_stake: total_stake as i64,
164            storage_fund_balance: storage_fund_balance as i64,
165            system_state: bcs::to_bytes(&stored_system_state).unwrap(),
166        }
167    }
168}
169
170impl EndOfEpochUpdate {
171    pub fn new(
172        last_checkpoint_summary: &CertifiedCheckpointSummary,
173        event: &IndexedEpochInfoEvent,
174    ) -> Self {
175        Self {
176            epoch: last_checkpoint_summary.epoch as i64,
177            network_total_transactions: last_checkpoint_summary.network_total_transactions as i64,
178            last_checkpoint_id: *last_checkpoint_summary.sequence_number() as i64,
179            epoch_end_timestamp: last_checkpoint_summary.timestamp_ms as i64,
180            storage_charge: event.storage_charge as i64,
181            storage_rebate: event.storage_rebate as i64,
182            total_gas_fees: event.total_gas_fees as i64,
183            total_stake_rewards_distributed: event.total_stake_rewards_distributed as i64,
184            epoch_commitments: bcs::to_bytes(
185                &last_checkpoint_summary
186                    .end_of_epoch_data
187                    .clone()
188                    .unwrap()
189                    .epoch_commitments,
190            )
191            .unwrap(),
192            burnt_tokens_amount: event.burnt_tokens_amount as i64,
193            minted_tokens_amount: event.minted_tokens_amount as i64,
194        }
195    }
196}
197
198impl From<&StoredEpochInfo> for Option<EndOfEpochInfo> {
199    fn from(info: &StoredEpochInfo) -> Option<EndOfEpochInfo> {
200        Some(EndOfEpochInfo {
201            reference_gas_price: (info.reference_gas_price as u64),
202            protocol_version: (info.protocol_version as u64),
203            last_checkpoint_id: info.last_checkpoint_id.map(|v| v as u64)?,
204            total_stake: info.total_stake as u64,
205            storage_fund_balance: info.storage_fund_balance as u64,
206            epoch_end_timestamp: info.epoch_end_timestamp.map(|v| v as u64)?,
207            storage_charge: info.storage_charge.map(|v| v as u64)?,
208            storage_rebate: info.storage_rebate.map(|v| v as u64)?,
209            total_gas_fees: info.total_gas_fees.map(|v| v as u64)?,
210            total_stake_rewards_distributed: info
211                .total_stake_rewards_distributed
212                .map(|v| v as u64)?,
213            burnt_tokens_amount: info.burnt_tokens_amount.map(|v| v as u64)?,
214            minted_tokens_amount: info.minted_tokens_amount.map(|v| v as u64)?,
215        })
216    }
217}
218
219impl TryFrom<&StoredEpochInfo> for StoredSystemState {
220    type Error = IndexerError;
221
222    fn try_from(value: &StoredEpochInfo) -> Result<Self, Self::Error> {
223        StoredSystemStateV1::try_from(value)
224            .map(Into::into)
225            .or_else(|_| {
226                bcs::from_bytes(&value.system_state).map_err(|_| {
227                    IndexerError::PersistentStorageDataCorruption(
228                        "failed to deserialize `system_state`".into(),
229                    )
230                })
231            })
232    }
233}
234
235impl TryFrom<&StoredEpochInfo> for StoredSystemStateV1 {
236    type Error = IndexerError;
237
238    fn try_from(value: &StoredEpochInfo) -> Result<Self, Self::Error> {
239        bcs::from_bytes(&value.system_state).map_err(|_| {
240            IndexerError::PersistentStorageDataCorruption(
241                "failed to deserialize `system_state`".into(),
242            )
243        })
244    }
245}
246
247impl TryFrom<StoredEpochInfo> for EpochInfo {
248    type Error = IndexerError;
249
250    fn try_from(value: StoredEpochInfo) -> Result<Self, Self::Error> {
251        let epoch = value.epoch as u64;
252        let end_of_epoch_info = (&value).into();
253        let stored_system_state = StoredSystemState::try_from(&value).map_err(|_| {
254            IndexerError::PersistentStorageDataCorruption(format!(
255                "failed to deserialize `system_state` for epoch {epoch}",
256            ))
257        })?;
258        let system_state = IotaSystemStateSummary::from(stored_system_state);
259        Ok(EpochInfo {
260            epoch: value.epoch as u64,
261            validators: system_state.active_validators().to_vec(),
262            epoch_total_transactions: value.epoch_total_transactions().unwrap_or(0) as u64,
263            first_checkpoint_id: value.first_checkpoint_id as u64,
264            epoch_start_timestamp: value.epoch_start_timestamp as u64,
265            end_of_epoch_info,
266            reference_gas_price: Some(value.reference_gas_price as u64),
267            committee_members: system_state.committee_members(),
268        })
269    }
270}