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