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