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 = EndOfEpochDataSchema::from(source.clone());
171        iota_data.serialize(serializer)
172    }
173}
174
175impl<'de> DeserializeAs<'de, EndOfEpochData> for EndOfEpochDataSchema {
176    fn deserialize_as<D>(deserializer: D) -> Result<EndOfEpochData, D::Error>
177    where
178        D: serde::Deserializer<'de>,
179    {
180        let iota_data = EndOfEpochDataSchema::deserialize(deserializer)?;
181        Ok(iota_data.into())
182    }
183}
184
185impl From<EndOfEpochDataSchema> for EndOfEpochData {
186    fn from(iota_data: EndOfEpochDataSchema) -> Self {
187        let EndOfEpochDataSchema {
188            next_epoch_committee,
189            next_epoch_protocol_version,
190            epoch_commitments,
191            epoch_supply_change,
192        } = iota_data;
193        EndOfEpochData {
194            next_epoch_committee: next_epoch_committee
195                .into_iter()
196                .map(|(public_key, stake)| ValidatorCommitteeMember {
197                    public_key: public_key.into(),
198                    stake,
199                })
200                .collect(),
201            next_epoch_protocol_version: next_epoch_protocol_version.as_u64(),
202            epoch_commitments: epoch_commitments.into_iter().map(Into::into).collect(),
203            epoch_supply_change,
204        }
205    }
206}
207
208impl From<EndOfEpochData> for EndOfEpochDataSchema {
209    fn from(data: EndOfEpochData) -> Self {
210        let EndOfEpochData {
211            next_epoch_committee,
212            next_epoch_protocol_version,
213            epoch_commitments,
214            epoch_supply_change,
215        } = data;
216        EndOfEpochDataSchema {
217            next_epoch_committee: next_epoch_committee
218                .into_iter()
219                .map(|member| (member.public_key.into(), member.stake))
220                .collect(),
221            next_epoch_protocol_version: ProtocolVersion::new(next_epoch_protocol_version),
222            epoch_commitments: epoch_commitments.into_iter().map(Into::into).collect(),
223            epoch_supply_change,
224        }
225    }
226}
227
228#[serde_as]
229#[derive(Serialize, Deserialize, JsonSchema)]
230#[schemars(rename = "CheckpointCommitment")]
231pub enum CheckpointCommitmentSchema {
232    ECMHLiveObjectSetDigest(
233        #[schemars(with = "ECMHLiveObjectSetDigestSchema")]
234        #[serde_as(as = "ECMHLiveObjectSetDigestSchema")]
235        ECMHLiveObjectSetDigest,
236    ),
237}
238
239impl SerializeAs<CheckpointCommitment> for CheckpointCommitmentSchema {
240    fn serialize_as<S>(source: &CheckpointCommitment, serializer: S) -> Result<S::Ok, S::Error>
241    where
242        S: serde::Serializer,
243    {
244        let iota_commitment = CheckpointCommitmentSchema::from(source.clone());
245        iota_commitment.serialize(serializer)
246    }
247}
248
249impl<'de> DeserializeAs<'de, CheckpointCommitment> for CheckpointCommitmentSchema {
250    fn deserialize_as<D>(deserializer: D) -> Result<CheckpointCommitment, D::Error>
251    where
252        D: serde::Deserializer<'de>,
253    {
254        let iota_commitment = CheckpointCommitmentSchema::deserialize(deserializer)?;
255        Ok(iota_commitment.into())
256    }
257}
258
259impl From<CheckpointCommitmentSchema> for CheckpointCommitment {
260    fn from(iota_commitment: CheckpointCommitmentSchema) -> Self {
261        match iota_commitment {
262            CheckpointCommitmentSchema::ECMHLiveObjectSetDigest(digest) => {
263                CheckpointCommitment::EcmhLiveObjectSet {
264                    digest: digest.digest,
265                }
266            }
267        }
268    }
269}
270
271impl From<CheckpointCommitment> for CheckpointCommitmentSchema {
272    fn from(commitment: CheckpointCommitment) -> Self {
273        match commitment {
274            CheckpointCommitment::EcmhLiveObjectSet { digest } => {
275                CheckpointCommitmentSchema::ECMHLiveObjectSetDigest(ECMHLiveObjectSetDigest {
276                    digest,
277                })
278            }
279            _ => unimplemented!("a new CheckpointCommitment variant was added and must be handled"),
280        }
281    }
282}
283
284/// The Sha256 digest of an EllipticCurveMultisetHash committing to the live
285/// object set.
286#[derive(Serialize, Deserialize, JsonSchema)]
287#[serde(rename = "ECMHLiveObjectSetDigest")]
288pub struct ECMHLiveObjectSetDigestSchema {
289    #[schemars(with = "[u8; 32]")]
290    pub digest: Digest,
291}
292
293impl SerializeAs<ECMHLiveObjectSetDigest> for ECMHLiveObjectSetDigestSchema {
294    fn serialize_as<S>(source: &ECMHLiveObjectSetDigest, serializer: S) -> Result<S::Ok, S::Error>
295    where
296        S: serde::Serializer,
297    {
298        let iota_digest = ECMHLiveObjectSetDigestSchema::from(source.clone());
299        iota_digest.serialize(serializer)
300    }
301}
302
303impl<'de> DeserializeAs<'de, ECMHLiveObjectSetDigest> for ECMHLiveObjectSetDigestSchema {
304    fn deserialize_as<D>(deserializer: D) -> Result<ECMHLiveObjectSetDigest, D::Error>
305    where
306        D: serde::Deserializer<'de>,
307    {
308        let iota_digest = ECMHLiveObjectSetDigestSchema::deserialize(deserializer)?;
309        Ok(iota_digest.into())
310    }
311}
312
313impl From<ECMHLiveObjectSetDigestSchema> for ECMHLiveObjectSetDigest {
314    fn from(iota_digest: ECMHLiveObjectSetDigestSchema) -> Self {
315        Self {
316            digest: iota_digest.digest,
317        }
318    }
319}
320
321impl From<ECMHLiveObjectSetDigest> for ECMHLiveObjectSetDigestSchema {
322    fn from(digest: ECMHLiveObjectSetDigest) -> Self {
323        Self {
324            digest: digest.digest,
325        }
326    }
327}
328
329#[serde_as]
330#[derive(Clone, Copy, Debug, JsonSchema, Serialize, Deserialize)]
331#[serde(untagged)]
332pub enum CheckpointId {
333    SequenceNumber(
334        #[schemars(with = "String")]
335        #[serde_as(as = "DisplayFromStr")]
336        CheckpointSequenceNumber,
337    ),
338    Digest(
339        #[serde_as(as = "Base58Schema")]
340        #[schemars(with = "Base58Schema")]
341        CheckpointDigest,
342    ),
343}
344
345impl From<CheckpointSequenceNumber> for CheckpointId {
346    fn from(seq: CheckpointSequenceNumber) -> Self {
347        Self::SequenceNumber(seq)
348    }
349}
350
351impl From<CheckpointDigest> for CheckpointId {
352    fn from(digest: CheckpointDigest) -> Self {
353        Self::Digest(digest)
354    }
355}
356
357impl std::fmt::Display for CheckpointId {
358    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
359        match self {
360            CheckpointId::SequenceNumber(seq) => write!(f, "SequenceNumber({seq})"),
361            CheckpointId::Digest(digest) => write!(f, "Digest({digest})"),
362        }
363    }
364}