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