sui_rpc_store/schema/
checkpoint_summary.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! `checkpoint_seq` → `StoredCheckpointSummary`.
5//!
6//! Holds the lightweight, signed checkpoint header. Contents — the
7//! list of executed tx digests — live in
8//! [`super::checkpoint_contents`] so
9//! summary-only lookups skip the larger payload.
10
11use sui_consistent_store::Protobuf;
12use sui_consistent_store::error::DecodeError;
13use sui_consistent_store::error::Error;
14use sui_consistent_store::reader::Reader;
15use sui_types::crypto::AuthorityStrongQuorumSignInfo;
16use sui_types::messages_checkpoint::CertifiedCheckpointSummary;
17use sui_types::messages_checkpoint::CheckpointSequenceNumber;
18use sui_types::messages_checkpoint::CheckpointSummary;
19use sui_types::messages_checkpoint::VerifiedCheckpoint;
20
21use crate::proto::StoredCheckpointSummary;
22use crate::schema::primitives::U64Be;
23
24pub const NAME: &str = "checkpoint_summary";
25
26pub type Key = U64Be;
27pub type Value = Protobuf<StoredCheckpointSummary>;
28
29pub fn options(resolver: &sui_consistent_store::CfOptionsResolver) -> rocksdb::Options {
30    resolver.options(NAME)
31}
32
33/// Build a `StoredCheckpointSummary` row from a checkpoint
34/// summary and its quorum signature.
35///
36/// BCS-encode failures here would indicate either OOM or a bug in
37/// the types' `Serialize` impls; we panic rather than thread a
38/// `Result` through every call site.
39pub fn store(summary: &CheckpointSummary, signature: &AuthorityStrongQuorumSignInfo) -> Value {
40    let summary_bcs = bcs::to_bytes(summary).expect("bcs encode CheckpointSummary");
41    let signature_bcs = bcs::to_bytes(signature).expect("bcs encode AuthorityStrongQuorumSignInfo");
42    Protobuf(StoredCheckpointSummary {
43        summary_bcs: summary_bcs.into(),
44        signature_bcs: signature_bcs.into(),
45    })
46}
47
48impl<R: Reader> super::RpcStoreSchema<R> {
49    /// Look up the signed summary of a checkpoint by sequence
50    /// number.
51    ///
52    /// Decodes the stored BCS payloads and rewraps them as a
53    /// [`VerifiedCheckpoint`]. The "verified" tag is asserted via
54    /// `new_unchecked`: we trust what we put in our own store.
55    pub fn get_checkpoint_summary(
56        &self,
57        seq: CheckpointSequenceNumber,
58    ) -> Result<Option<VerifiedCheckpoint>, Error> {
59        let Some(stored) = self.checkpoint_summary.get(&U64Be(seq))? else {
60            return Ok(None);
61        };
62        let stored = stored.into_inner();
63        let summary: CheckpointSummary = bcs::from_bytes(&stored.summary_bcs)
64            .map_err(|e| DecodeError::with_source("bcs decode CheckpointSummary", e))?;
65        let signature: AuthorityStrongQuorumSignInfo = bcs::from_bytes(&stored.signature_bcs)
66            .map_err(|e| DecodeError::with_source("bcs decode AuthorityStrongQuorumSignInfo", e))?;
67        let certified = CertifiedCheckpointSummary::new_from_data_and_sig(summary, signature);
68        Ok(Some(VerifiedCheckpoint::new_unchecked(certified)))
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use sui_consistent_store::Db;
75    use sui_consistent_store::DbOptions;
76    use sui_types::crypto::AggregateAuthoritySignature;
77    use sui_types::digests::CheckpointContentsDigest;
78    use sui_types::gas::GasCostSummary;
79
80    use super::*;
81    use crate::RpcStoreSchema;
82
83    fn fresh_db() -> (tempfile::TempDir, sui_consistent_store::Db, RpcStoreSchema) {
84        let dir = tempfile::tempdir().unwrap();
85        let (db, schema) = Db::open::<RpcStoreSchema>(dir.path(), DbOptions::default()).unwrap();
86        (dir, db, schema)
87    }
88
89    fn dummy_summary(seq: CheckpointSequenceNumber) -> CheckpointSummary {
90        CheckpointSummary {
91            epoch: 0,
92            sequence_number: seq,
93            network_total_transactions: 0,
94            content_digest: CheckpointContentsDigest::random(),
95            previous_digest: None,
96            epoch_rolling_gas_cost_summary: GasCostSummary::default(),
97            timestamp_ms: 0,
98            checkpoint_commitments: Vec::new(),
99            end_of_epoch_data: None,
100            version_specific_data: Vec::new(),
101        }
102    }
103
104    fn dummy_sig() -> AuthorityStrongQuorumSignInfo {
105        // Synthetic placeholder values — we're verifying the
106        // storage round-trip, not the signature's validity.
107        AuthorityStrongQuorumSignInfo {
108            epoch: 0,
109            signature: AggregateAuthoritySignature::default(),
110            signers_map: Default::default(),
111        }
112    }
113
114    #[test]
115    fn get_returns_none_for_unknown_seq() {
116        let (_dir, _db, schema) = fresh_db();
117        assert!(schema.get_checkpoint_summary(7).unwrap().is_none());
118    }
119
120    #[test]
121    fn store_then_get_round_trips() {
122        let (_dir, db, schema) = fresh_db();
123        let summary = dummy_summary(42);
124        let sig = dummy_sig();
125
126        let mut batch = db.batch();
127        batch
128            .put(
129                &schema.checkpoint_summary,
130                &U64Be(42),
131                &store(&summary, &sig),
132            )
133            .unwrap();
134        batch.commit().unwrap();
135
136        let read = schema
137            .get_checkpoint_summary(42)
138            .unwrap()
139            .expect("checkpoint present");
140        assert_eq!(read.data(), &summary);
141        assert_eq!(read.auth_sig().epoch, sig.epoch);
142        // RoaringBitmap doesn't impl PartialEq, but bcs bytes do —
143        // round-trip both signatures through bcs and compare.
144        let read_sig_bcs = bcs::to_bytes(read.auth_sig()).unwrap();
145        let expected_sig_bcs = bcs::to_bytes(&sig).unwrap();
146        assert_eq!(read_sig_bcs, expected_sig_bcs);
147    }
148
149    #[test]
150    fn overwrite_replaces_previous() {
151        let (_dir, db, schema) = fresh_db();
152        let first = dummy_summary(42);
153        let later = dummy_summary(42);
154        let later_digest = later.content_digest;
155        let sig = dummy_sig();
156
157        let mut batch = db.batch();
158        batch
159            .put(&schema.checkpoint_summary, &U64Be(42), &store(&first, &sig))
160            .unwrap();
161        batch
162            .put(&schema.checkpoint_summary, &U64Be(42), &store(&later, &sig))
163            .unwrap();
164        batch.commit().unwrap();
165
166        let read = schema
167            .get_checkpoint_summary(42)
168            .unwrap()
169            .expect("checkpoint present");
170        assert_eq!(read.data().content_digest, later_digest);
171    }
172}