Skip to main content

iota_json_rpc_types/
iota_checkpoint.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use iota_protocol_config::ProtocolVersion;
6use iota_sdk_types::{
7    CheckpointCommitment, CheckpointContents, CheckpointDigest, CheckpointSummary, Digest,
8    EndOfEpochData, GasCostSummary, TransactionDigest, ValidatorCommitteeMember,
9};
10use iota_types::{
11    base_types::AuthorityName,
12    committee::{EpochId, StakeUnit},
13    crypto::AggregateAuthoritySignature,
14    iota_serde::BigInt,
15    messages_checkpoint::{
16        CheckpointContentsExt, CheckpointSequenceNumber, CheckpointTimestamp,
17        ECMHLiveObjectSetDigest,
18    },
19};
20use schemars::JsonSchema;
21use serde::{Deserialize, Serialize};
22use serde_with::{DeserializeAs, DisplayFromStr, SerializeAs, serde_as};
23
24use crate::{
25    IotaAuthorityPublicKeyBytes, Page,
26    iota_gas_cost_summary::IotaGasCostSummary,
27    iota_primitives::{
28        Base58 as Base58Schema, Base64 as Base64Schema, ProtocolVersion as ProtocolVersionSchema,
29    },
30};
31pub type CheckpointPage = Page<Checkpoint, BigInt<u64>>;
32
33#[serde_as]
34#[derive(Clone, Debug, JsonSchema, Serialize, Deserialize, PartialEq, Eq)]
35#[serde(rename_all = "camelCase")]
36pub struct Checkpoint {
37    /// Checkpoint's epoch ID
38    #[schemars(with = "String")]
39    #[serde_as(as = "DisplayFromStr")]
40    pub epoch: EpochId,
41    /// Checkpoint sequence number
42    #[schemars(with = "String")]
43    #[serde_as(as = "DisplayFromStr")]
44    pub sequence_number: CheckpointSequenceNumber,
45    /// Checkpoint digest
46    #[serde_as(as = "Base58Schema")]
47    #[schemars(with = "Base58Schema")]
48    pub digest: CheckpointDigest,
49    /// Total number of transactions committed since genesis, including those in
50    /// this checkpoint.
51    #[schemars(with = "String")]
52    #[serde_as(as = "DisplayFromStr")]
53    pub network_total_transactions: u64,
54    /// Digest of the previous checkpoint
55    #[serde(skip_serializing_if = "Option::is_none")]
56    #[serde_as(as = "Option<Base58Schema>")]
57    #[schemars(with = "Option<Base58Schema>")]
58    pub previous_digest: Option<CheckpointDigest>,
59    /// The running total gas costs of all transactions included in the current
60    /// epoch so far until this checkpoint.
61    #[schemars(with = "IotaGasCostSummary")]
62    #[serde_as(as = "IotaGasCostSummary")]
63    pub epoch_rolling_gas_cost_summary: GasCostSummary,
64    /// Timestamp of the checkpoint - number of milliseconds from the Unix epoch
65    /// Checkpoint timestamps are monotonic, but not strongly monotonic -
66    /// subsequent checkpoints can have same timestamp if they originate
67    /// from the same underlining consensus commit
68    #[schemars(with = "String")]
69    #[serde_as(as = "DisplayFromStr")]
70    pub timestamp_ms: CheckpointTimestamp,
71    /// Present only on the final checkpoint of the epoch.
72    #[serde(skip_serializing_if = "Option::is_none")]
73    #[schemars(with = "Option<EndOfEpochDataSchema>")]
74    #[serde_as(as = "Option<EndOfEpochDataSchema>")]
75    pub end_of_epoch_data: Option<EndOfEpochData>,
76    /// Transaction digests
77    #[serde_as(as = "Vec<Base58Schema>")]
78    #[schemars(with = "Vec<Base58Schema>")]
79    pub transactions: Vec<TransactionDigest>,
80
81    /// Commitments to checkpoint state
82    #[schemars(with = "Vec<CheckpointCommitmentSchema>")]
83    #[serde_as(as = "Vec<CheckpointCommitmentSchema>")]
84    pub checkpoint_commitments: Vec<CheckpointCommitment>,
85    /// Validator Signature
86    #[schemars(with = "Base64Schema")]
87    pub validator_signature: AggregateAuthoritySignature,
88}
89
90impl
91    From<(
92        CheckpointSummary,
93        CheckpointContents,
94        AggregateAuthoritySignature,
95    )> for Checkpoint
96{
97    fn from(
98        (summary, contents, signature): (
99            CheckpointSummary,
100            CheckpointContents,
101            AggregateAuthoritySignature,
102        ),
103    ) -> Self {
104        let digest = summary.digest();
105        let CheckpointSummary {
106            epoch,
107            sequence_number,
108            network_total_transactions,
109            previous_digest,
110            epoch_rolling_gas_cost_summary,
111            timestamp_ms,
112            end_of_epoch_data,
113            ..
114        } = summary;
115
116        Checkpoint {
117            epoch,
118            sequence_number,
119            digest,
120            network_total_transactions,
121            previous_digest,
122            epoch_rolling_gas_cost_summary,
123            timestamp_ms,
124            end_of_epoch_data,
125            transactions: contents.iter().map(|digest| digest.transaction).collect(),
126            // TODO: populate commitment for rpc clients. Most likely, rpc clients don't need this
127            // info (if they need it, they need to get signed BCS data anyway in order to trust
128            // it).
129            checkpoint_commitments: Default::default(),
130            validator_signature: signature,
131        }
132    }
133}
134
135#[serde_as]
136#[derive(Serialize, Deserialize, JsonSchema)]
137#[serde(rename_all = "camelCase", rename = "EndOfEpochData")]
138pub struct EndOfEpochDataSchema {
139    /// next_epoch_committee is `Some` if and only if the current checkpoint is
140    /// the last checkpoint of an epoch.
141    /// Therefore next_epoch_committee can be used to pick the last checkpoint
142    /// of an epoch, which is often useful to get epoch level summary stats
143    /// like total gas cost of an epoch, or the total number of transactions
144    /// from genesis to the end of an epoch. The committee is stored as a
145    /// vector of validator pub key and stake pairs. The vector
146    /// should be sorted based on the Committee data structure.
147    #[schemars(with = "Vec<(IotaAuthorityPublicKeyBytes, String)>")]
148    #[serde_as(as = "Vec<(_, DisplayFromStr)>")]
149    pub next_epoch_committee: Vec<(AuthorityName, StakeUnit)>,
150
151    /// The protocol version that is in effect during the epoch that starts
152    /// immediately after this checkpoint.
153    #[schemars(with = "ProtocolVersionSchema")]
154    #[serde_as(as = "ProtocolVersionSchema")]
155    pub next_epoch_protocol_version: ProtocolVersion,
156
157    /// Commitments to epoch specific state (e.g. live object set)
158    pub epoch_commitments: Vec<CheckpointCommitmentSchema>,
159
160    /// The number of tokens that were minted (if positive) or burnt (if
161    /// negative) in this epoch.
162    pub epoch_supply_change: i64,
163}
164
165impl SerializeAs<EndOfEpochData> for EndOfEpochDataSchema {
166    fn serialize_as<S>(source: &EndOfEpochData, serializer: S) -> Result<S::Ok, S::Error>
167    where
168        S: serde::Serializer,
169    {
170        let iota_data =
171            EndOfEpochDataSchema::try_from(source.clone()).map_err(serde::ser::Error::custom)?;
172        iota_data.serialize(serializer)
173    }
174}
175
176impl<'de> DeserializeAs<'de, EndOfEpochData> for EndOfEpochDataSchema {
177    fn deserialize_as<D>(deserializer: D) -> Result<EndOfEpochData, D::Error>
178    where
179        D: serde::Deserializer<'de>,
180    {
181        let iota_data = EndOfEpochDataSchema::deserialize(deserializer)?;
182        Ok(iota_data.into())
183    }
184}
185
186impl From<EndOfEpochDataSchema> for EndOfEpochData {
187    fn from(iota_data: EndOfEpochDataSchema) -> Self {
188        let EndOfEpochDataSchema {
189            next_epoch_committee,
190            next_epoch_protocol_version,
191            epoch_commitments,
192            epoch_supply_change,
193        } = iota_data;
194        EndOfEpochData {
195            next_epoch_committee: next_epoch_committee
196                .into_iter()
197                .map(|(public_key, stake)| ValidatorCommitteeMember {
198                    public_key: public_key.into(),
199                    stake,
200                })
201                .collect(),
202            next_epoch_protocol_version: next_epoch_protocol_version.as_u64(),
203            epoch_commitments: epoch_commitments.into_iter().map(Into::into).collect(),
204            epoch_supply_change,
205        }
206    }
207}
208
209impl TryFrom<EndOfEpochData> for EndOfEpochDataSchema {
210    type Error = anyhow::Error;
211
212    fn try_from(data: EndOfEpochData) -> Result<Self, Self::Error> {
213        let EndOfEpochData {
214            next_epoch_committee,
215            next_epoch_protocol_version,
216            epoch_commitments,
217            epoch_supply_change,
218        } = data;
219        Ok(EndOfEpochDataSchema {
220            next_epoch_committee: next_epoch_committee
221                .into_iter()
222                .map(|member| (member.public_key.into(), member.stake))
223                .collect(),
224            next_epoch_protocol_version: ProtocolVersion::new(next_epoch_protocol_version),
225            epoch_commitments: epoch_commitments
226                .into_iter()
227                .map(CheckpointCommitmentSchema::try_from)
228                .collect::<Result<_, _>>()?,
229            epoch_supply_change,
230        })
231    }
232}
233
234#[serde_as]
235#[derive(Serialize, Deserialize, JsonSchema)]
236#[schemars(rename = "CheckpointCommitment")]
237pub enum CheckpointCommitmentSchema {
238    ECMHLiveObjectSetDigest(
239        #[schemars(with = "ECMHLiveObjectSetDigestSchema")]
240        #[serde_as(as = "ECMHLiveObjectSetDigestSchema")]
241        ECMHLiveObjectSetDigest,
242    ),
243}
244
245impl SerializeAs<CheckpointCommitment> for CheckpointCommitmentSchema {
246    fn serialize_as<S>(source: &CheckpointCommitment, serializer: S) -> Result<S::Ok, S::Error>
247    where
248        S: serde::Serializer,
249    {
250        let iota_commitment = CheckpointCommitmentSchema::try_from(source.clone())
251            .map_err(serde::ser::Error::custom)?;
252        iota_commitment.serialize(serializer)
253    }
254}
255
256impl<'de> DeserializeAs<'de, CheckpointCommitment> for CheckpointCommitmentSchema {
257    fn deserialize_as<D>(deserializer: D) -> Result<CheckpointCommitment, D::Error>
258    where
259        D: serde::Deserializer<'de>,
260    {
261        let iota_commitment = CheckpointCommitmentSchema::deserialize(deserializer)?;
262        Ok(iota_commitment.into())
263    }
264}
265
266impl From<CheckpointCommitmentSchema> for CheckpointCommitment {
267    fn from(iota_commitment: CheckpointCommitmentSchema) -> Self {
268        match iota_commitment {
269            CheckpointCommitmentSchema::ECMHLiveObjectSetDigest(digest) => {
270                CheckpointCommitment::EcmhLiveObjectSet {
271                    digest: digest.digest,
272                }
273            }
274        }
275    }
276}
277
278impl TryFrom<CheckpointCommitment> for CheckpointCommitmentSchema {
279    type Error = anyhow::Error;
280
281    fn try_from(commitment: CheckpointCommitment) -> Result<Self, Self::Error> {
282        Ok(match commitment {
283            CheckpointCommitment::EcmhLiveObjectSet { digest } => {
284                CheckpointCommitmentSchema::ECMHLiveObjectSetDigest(ECMHLiveObjectSetDigest {
285                    digest,
286                })
287            }
288            _ => anyhow::bail!("unknown CheckpointCommitment variant"),
289        })
290    }
291}
292
293/// The Sha256 digest of an EllipticCurveMultisetHash committing to the live
294/// object set.
295#[derive(Serialize, Deserialize, JsonSchema)]
296#[serde(rename = "ECMHLiveObjectSetDigest")]
297pub struct ECMHLiveObjectSetDigestSchema {
298    #[schemars(with = "[u8; 32]")]
299    pub digest: Digest,
300}
301
302impl SerializeAs<ECMHLiveObjectSetDigest> for ECMHLiveObjectSetDigestSchema {
303    fn serialize_as<S>(source: &ECMHLiveObjectSetDigest, serializer: S) -> Result<S::Ok, S::Error>
304    where
305        S: serde::Serializer,
306    {
307        let iota_digest = ECMHLiveObjectSetDigestSchema::from(source.clone());
308        iota_digest.serialize(serializer)
309    }
310}
311
312impl<'de> DeserializeAs<'de, ECMHLiveObjectSetDigest> for ECMHLiveObjectSetDigestSchema {
313    fn deserialize_as<D>(deserializer: D) -> Result<ECMHLiveObjectSetDigest, D::Error>
314    where
315        D: serde::Deserializer<'de>,
316    {
317        let iota_digest = ECMHLiveObjectSetDigestSchema::deserialize(deserializer)?;
318        Ok(iota_digest.into())
319    }
320}
321
322impl From<ECMHLiveObjectSetDigestSchema> for ECMHLiveObjectSetDigest {
323    fn from(iota_digest: ECMHLiveObjectSetDigestSchema) -> Self {
324        Self {
325            digest: iota_digest.digest,
326        }
327    }
328}
329
330impl From<ECMHLiveObjectSetDigest> for ECMHLiveObjectSetDigestSchema {
331    fn from(digest: ECMHLiveObjectSetDigest) -> Self {
332        Self {
333            digest: digest.digest,
334        }
335    }
336}
337
338#[serde_as]
339#[derive(Clone, Copy, Debug, JsonSchema, Serialize, Deserialize)]
340#[serde(untagged)]
341pub enum CheckpointId {
342    SequenceNumber(
343        #[schemars(with = "String")]
344        #[serde_as(as = "DisplayFromStr")]
345        CheckpointSequenceNumber,
346    ),
347    Digest(
348        #[serde_as(as = "Base58Schema")]
349        #[schemars(with = "Base58Schema")]
350        CheckpointDigest,
351    ),
352}
353
354impl From<CheckpointSequenceNumber> for CheckpointId {
355    fn from(seq: CheckpointSequenceNumber) -> Self {
356        Self::SequenceNumber(seq)
357    }
358}
359
360impl From<CheckpointDigest> for CheckpointId {
361    fn from(digest: CheckpointDigest) -> Self {
362        Self::Digest(digest)
363    }
364}
365
366impl std::fmt::Display for CheckpointId {
367    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
368        match self {
369            CheckpointId::SequenceNumber(seq) => write!(f, "SequenceNumber({seq})"),
370            CheckpointId::Digest(digest) => write!(f, "Digest({digest})"),
371        }
372    }
373}