1use crate::schema::epochs;
5use crate::{errors::IndexerError, schema::feature_flags, schema::protocol_configs};
6use diesel::prelude::{AsChangeset, Identifiable};
7use diesel::{Insertable, Queryable, Selectable};
8use sui_json_rpc_types::{EndOfEpochInfo, EpochInfo};
9use sui_types::event::SystemEpochInfoEvent;
10use sui_types::messages_checkpoint::CertifiedCheckpointSummary;
11use sui_types::sui_system_state::sui_system_state_summary::SuiSystemStateSummary;
12
13#[derive(Queryable, Insertable, Debug, Clone, Default)]
14#[diesel(table_name = epochs)]
15pub struct StoredEpochInfo {
16    pub epoch: i64,
17    pub first_checkpoint_id: i64,
18    pub epoch_start_timestamp: i64,
19    pub reference_gas_price: i64,
20    pub protocol_version: i64,
21    pub total_stake: i64,
22    pub storage_fund_balance: i64,
23    pub system_state: Option<Vec<u8>>,
24    pub epoch_total_transactions: Option<i64>,
25    pub last_checkpoint_id: Option<i64>,
26    pub epoch_end_timestamp: Option<i64>,
27    pub storage_fund_reinvestment: Option<i64>,
28    pub storage_charge: Option<i64>,
29    pub storage_rebate: Option<i64>,
30    pub stake_subsidy_amount: Option<i64>,
31    pub total_gas_fees: Option<i64>,
32    pub total_stake_rewards_distributed: Option<i64>,
33    pub leftover_storage_fund_inflow: Option<i64>,
34    pub epoch_commitments: Option<Vec<u8>>,
35    pub system_state_summary_json: Option<serde_json::Value>,
37    pub first_tx_sequence_number: Option<i64>,
39}
40
41#[derive(Insertable, Identifiable, AsChangeset, Clone, Debug)]
42#[diesel(primary_key(epoch))]
43#[diesel(table_name = epochs)]
44pub struct StartOfEpochUpdate {
45    pub epoch: i64,
46    pub first_checkpoint_id: i64,
47    pub first_tx_sequence_number: i64,
48    pub epoch_start_timestamp: i64,
49    pub reference_gas_price: i64,
50    pub protocol_version: i64,
51    pub total_stake: i64,
52    pub storage_fund_balance: i64,
53    pub system_state_summary_json: serde_json::Value,
54}
55
56#[derive(Identifiable, AsChangeset, Clone, Debug)]
57#[diesel(primary_key(epoch))]
58#[diesel(table_name = epochs)]
59pub struct EndOfEpochUpdate {
60    pub epoch: i64,
61    pub epoch_total_transactions: i64,
62    pub last_checkpoint_id: i64,
63    pub epoch_end_timestamp: i64,
64    pub storage_fund_reinvestment: i64,
65    pub storage_charge: i64,
66    pub storage_rebate: i64,
67    pub stake_subsidy_amount: i64,
68    pub total_gas_fees: i64,
69    pub total_stake_rewards_distributed: i64,
70    pub leftover_storage_fund_inflow: i64,
71    pub epoch_commitments: Vec<u8>,
72}
73
74#[derive(Queryable, Insertable, Debug, Clone, Default)]
75#[diesel(table_name = protocol_configs)]
76pub struct StoredProtocolConfig {
77    pub protocol_version: i64,
78    pub config_name: String,
79    pub config_value: Option<String>,
80}
81
82#[derive(Queryable, Insertable, Debug, Clone, Default)]
83#[diesel(table_name = feature_flags)]
84pub struct StoredFeatureFlag {
85    pub protocol_version: i64,
86    pub flag_name: String,
87    pub flag_value: bool,
88}
89
90#[derive(Queryable, Selectable, Clone)]
91#[diesel(table_name = epochs)]
92pub struct QueryableEpochInfo {
93    pub epoch: i64,
94    pub first_checkpoint_id: i64,
95    pub epoch_start_timestamp: i64,
96    pub reference_gas_price: i64,
97    pub protocol_version: i64,
98    pub total_stake: i64,
99    pub storage_fund_balance: i64,
100    pub epoch_total_transactions: Option<i64>,
101    pub first_tx_sequence_number: Option<i64>,
102    pub last_checkpoint_id: Option<i64>,
103    pub epoch_end_timestamp: Option<i64>,
104    pub storage_fund_reinvestment: Option<i64>,
105    pub storage_charge: Option<i64>,
106    pub storage_rebate: Option<i64>,
107    pub stake_subsidy_amount: Option<i64>,
108    pub total_gas_fees: Option<i64>,
109    pub total_stake_rewards_distributed: Option<i64>,
110    pub leftover_storage_fund_inflow: Option<i64>,
111    pub epoch_commitments: Option<Vec<u8>>,
112}
113
114#[derive(Queryable)]
115pub struct QueryableEpochSystemState {
116    pub epoch: i64,
117    pub system_state: Vec<u8>,
118}
119
120#[derive(Default)]
121pub struct EpochStartInfo {
122    pub first_checkpoint_id: u64,
123    pub first_tx_sequence_number: u64,
124    pub total_stake: u64,
125    pub storage_fund_balance: u64,
126}
127
128impl EpochStartInfo {
129    pub fn new(
130        first_checkpoint_id: u64,
131        first_tx_sequence_number: u64,
132        epoch_event_opt: Option<&SystemEpochInfoEvent>,
133    ) -> Self {
134        Self {
135            first_checkpoint_id,
136            first_tx_sequence_number,
137            total_stake: epoch_event_opt.map(|e| e.total_stake).unwrap_or_default(),
138            storage_fund_balance: epoch_event_opt
139                .map(|e| e.storage_fund_balance)
140                .unwrap_or_default(),
141        }
142    }
143}
144
145impl StartOfEpochUpdate {
146    pub fn new(
147        new_system_state_summary: SuiSystemStateSummary,
148        epoch_start_info: EpochStartInfo,
149    ) -> Self {
150        Self {
151            epoch: new_system_state_summary.epoch as i64,
152            system_state_summary_json: serde_json::to_value(new_system_state_summary.clone())
153                .unwrap(),
154            first_checkpoint_id: epoch_start_info.first_checkpoint_id as i64,
155            first_tx_sequence_number: epoch_start_info.first_tx_sequence_number as i64,
156            epoch_start_timestamp: new_system_state_summary.epoch_start_timestamp_ms as i64,
157            reference_gas_price: new_system_state_summary.reference_gas_price as i64,
158            protocol_version: new_system_state_summary.protocol_version as i64,
159            total_stake: epoch_start_info.total_stake as i64,
160            storage_fund_balance: epoch_start_info.storage_fund_balance as i64,
161        }
162    }
163}
164
165#[derive(Default)]
166pub struct EpochEndInfo {
167    pub storage_fund_reinvestment: u64,
168    pub storage_charge: u64,
169    pub storage_rebate: u64,
170    pub leftover_storage_fund_inflow: u64,
171    pub stake_subsidy_amount: u64,
172    pub total_gas_fees: u64,
173    pub total_stake_rewards_distributed: u64,
174}
175
176impl EpochEndInfo {
177    pub fn new(epoch_event_opt: Option<&SystemEpochInfoEvent>) -> Self {
178        epoch_event_opt.map_or_else(Self::default, |epoch_event| Self {
179            storage_fund_reinvestment: epoch_event.storage_fund_reinvestment,
180            storage_charge: epoch_event.storage_charge,
181            storage_rebate: epoch_event.storage_rebate,
182            leftover_storage_fund_inflow: epoch_event.leftover_storage_fund_inflow,
183            stake_subsidy_amount: epoch_event.stake_subsidy_amount,
184            total_gas_fees: epoch_event.total_gas_fees,
185            total_stake_rewards_distributed: epoch_event.total_stake_rewards_distributed,
186        })
187    }
188}
189
190impl EndOfEpochUpdate {
191    pub fn new(
192        last_checkpoint_summary: &CertifiedCheckpointSummary,
193        first_tx_sequence_number: u64,
194        epoch_end_info: EpochEndInfo,
195    ) -> Self {
196        Self {
197            epoch: last_checkpoint_summary.epoch as i64,
198            epoch_total_transactions: (last_checkpoint_summary.network_total_transactions
199                - first_tx_sequence_number) as i64,
200            last_checkpoint_id: *last_checkpoint_summary.sequence_number() as i64,
201            epoch_end_timestamp: last_checkpoint_summary.timestamp_ms as i64,
202            storage_fund_reinvestment: epoch_end_info.storage_fund_reinvestment as i64,
203            storage_charge: epoch_end_info.storage_charge as i64,
204            storage_rebate: epoch_end_info.storage_rebate as i64,
205            leftover_storage_fund_inflow: epoch_end_info.leftover_storage_fund_inflow as i64,
206            stake_subsidy_amount: epoch_end_info.stake_subsidy_amount as i64,
207            total_gas_fees: epoch_end_info.total_gas_fees as i64,
208            total_stake_rewards_distributed: epoch_end_info.total_stake_rewards_distributed as i64,
209            epoch_commitments: bcs::to_bytes(
210                &last_checkpoint_summary
211                    .end_of_epoch_data
212                    .clone()
213                    .unwrap()
214                    .epoch_commitments,
215            )
216            .unwrap(),
217        }
218    }
219}
220
221impl StoredEpochInfo {
222    pub fn get_json_system_state_summary(&self) -> Result<SuiSystemStateSummary, IndexerError> {
223        let Some(system_state_summary_json) = self.system_state_summary_json.clone() else {
224            return Err(IndexerError::PersistentStorageDataCorruptionError(
225                "System state summary is null for the given epoch".into(),
226            ));
227        };
228        let system_state_summary: SuiSystemStateSummary =
229            serde_json::from_value(system_state_summary_json).map_err(|_| {
230                IndexerError::PersistentStorageDataCorruptionError(format!(
231                    "Failed to deserialize `system_state` for epoch {:?}",
232                    self.epoch,
233                ))
234            })?;
235        debug_assert_eq!(system_state_summary.epoch, self.epoch as u64);
236        Ok(system_state_summary)
237    }
238}
239
240impl From<&StoredEpochInfo> for Option<EndOfEpochInfo> {
241    fn from(info: &StoredEpochInfo) -> Option<EndOfEpochInfo> {
242        Some(EndOfEpochInfo {
243            reference_gas_price: (info.reference_gas_price as u64),
244            protocol_version: (info.protocol_version as u64),
245            last_checkpoint_id: info.last_checkpoint_id.map(|v| v as u64)?,
246            total_stake: info.total_stake as u64,
247            storage_fund_balance: info.storage_fund_balance as u64,
248            epoch_end_timestamp: info.epoch_end_timestamp.map(|v| v as u64)?,
249            storage_fund_reinvestment: info.storage_fund_reinvestment.map(|v| v as u64)?,
250            storage_charge: info.storage_charge.map(|v| v as u64)?,
251            storage_rebate: info.storage_rebate.map(|v| v as u64)?,
252            stake_subsidy_amount: info.stake_subsidy_amount.map(|v| v as u64)?,
253            total_gas_fees: info.total_gas_fees.map(|v| v as u64)?,
254            total_stake_rewards_distributed: info
255                .total_stake_rewards_distributed
256                .map(|v| v as u64)?,
257            leftover_storage_fund_inflow: info.leftover_storage_fund_inflow.map(|v| v as u64)?,
258        })
259    }
260}
261
262impl TryFrom<StoredEpochInfo> for EpochInfo {
263    type Error = IndexerError;
264
265    fn try_from(value: StoredEpochInfo) -> Result<Self, Self::Error> {
266        let end_of_epoch_info = (&value).into();
267        let system_state_summary = value.get_json_system_state_summary()?;
268        Ok(EpochInfo {
269            epoch: value.epoch as u64,
270            validators: system_state_summary.active_validators,
271            epoch_total_transactions: value.epoch_total_transactions.unwrap_or(0) as u64,
272            first_checkpoint_id: value.first_checkpoint_id as u64,
273            epoch_start_timestamp: value.epoch_start_timestamp as u64,
274            end_of_epoch_info,
275            reference_gas_price: Some(value.reference_gas_price as u64),
276        })
277    }
278}