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