Skip to main content

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