1use 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 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 pub first_tx_sequence_number: i64,
49 pub first_optimistic_sequence_number: i64,
53}
54
55pub(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 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}