sui_rpc_store/reader/
mod.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Adapter that exposes [`RpcStoreSchema`] through the trait stack
5//! `sui-rpc-api` consumes — [`ObjectStore`], [`ReadStore`],
6//! [`ChildObjectResolver`], [`RpcStateReader`], and [`RpcIndexes`].
7//!
8//! The adapter type, [`RpcStoreReader`], is generic over a
9//! [`Reader`] so a single struct serves both tip reads (`R = Db`)
10//! and point-in-time reads bound to a snapshot (`R = Snapshot`).
11//! Callers requesting "give me the latest" hold the tip reader;
12//! callers requesting "show me state at checkpoint X" hold the
13//! snapshot-bound one. The choice of consistency context is made
14//! once, at the point [`RpcStoreReader::at_snapshot`] is called.
15//!
16//! [`ObjectStore`]: sui_types::storage::ObjectStore
17//! [`ReadStore`]: sui_types::storage::ReadStore
18//! [`ChildObjectResolver`]: sui_types::storage::ChildObjectResolver
19//! [`RpcStateReader`]: sui_types::storage::RpcStateReader
20//! [`RpcIndexes`]: sui_types::storage::RpcIndexes
21//! [`Reader`]: sui_consistent_store::reader::Reader
22
23mod child_resolver;
24mod indexes;
25#[cfg(test)]
26mod integration_test;
27mod layout;
28mod object_store;
29mod read_store;
30mod state_reader;
31
32use std::sync::Arc;
33
34use sui_consistent_store::Db;
35use sui_consistent_store::SchemaAtSnapshot;
36use sui_consistent_store::Snapshot;
37use sui_consistent_store::reader::Reader;
38
39use crate::RpcStoreSchema;
40
41/// Adapter exposing [`RpcStoreSchema`] through the
42/// `sui-rpc-api` reader-trait stack.
43///
44/// Construct one of two ways:
45///
46/// - [`RpcStoreReader::new`] binds to tip reads (`R = Db`). Use
47///   this for callers that want the latest committed state.
48/// - [`RpcStoreReader::at_snapshot`] takes a captured
49///   [`Snapshot`] and returns a [`RpcStoreReader<Snapshot>`] whose
50///   every read returns the state at that snapshot. Use this for
51///   "show me state at checkpoint X" requests.
52///
53/// `RpcStoreReader` holds an `Arc<RpcStoreSchema<R>>` so trait
54/// impls can hand a `&self` to any of the inherent read helpers
55/// already defined on the schema. The wrapper itself is `Clone`
56/// (cheap, `Arc`-backed) so it can be handed to
57/// `sui-rpc-api::StateReader::new(Arc::new(reader))`.
58pub struct RpcStoreReader<R: Reader = Db> {
59    /// The `Db` handle. Held separately from `schema` so trait
60    /// impls that need framework-level access (chain id, pipeline
61    /// watermarks) don't have to walk through a typed CF.
62    db: Db,
63
64    /// Typed handles to every CF the read paths exercise.
65    schema: Arc<RpcStoreSchema<R>>,
66}
67
68impl<R: Reader> RpcStoreReader<R> {
69    /// Bind the adapter to an existing [`RpcStoreSchema`].
70    ///
71    /// `db` must be the same [`Db`] the schema was opened against.
72    /// Holding both separately is cheap (each is `Arc`-backed) and
73    /// avoids a `schema.epochs.reader().db()` style detour inside
74    /// hot read paths.
75    pub fn new(db: Db, schema: Arc<RpcStoreSchema<R>>) -> Self {
76        Self { db, schema }
77    }
78
79    /// Borrow the underlying [`Db`] handle. Used by trait impls
80    /// that read directly from the framework CFs (chain id,
81    /// pipeline watermarks) rather than going through the typed
82    /// user schema.
83    pub fn db(&self) -> &Db {
84        &self.db
85    }
86
87    /// Borrow the typed schema this adapter is bound to. Trait
88    /// impls reach through this to call any of the inherent read
89    /// helpers `RpcStoreSchema` exposes per-CF.
90    pub fn schema(&self) -> &RpcStoreSchema<R> {
91        &self.schema
92    }
93}
94
95impl RpcStoreReader<Db> {
96    /// Re-project this reader against a captured [`Snapshot`].
97    ///
98    /// The returned [`RpcStoreReader<Snapshot>`] reads every CF
99    /// through the snapshot's `ReadOptions`, so reads are
100    /// consistent with the point in time at which the snapshot
101    /// was taken (rather than the tip). The original tip reader
102    /// is unaffected.
103    pub fn at_snapshot(&self, snap: &Snapshot) -> RpcStoreReader<Snapshot> {
104        RpcStoreReader {
105            db: self.db.clone(),
106            schema: Arc::new(self.schema.at(snap)),
107        }
108    }
109}
110
111impl<R: Reader> Clone for RpcStoreReader<R> {
112    fn clone(&self) -> Self {
113        Self {
114            db: self.db.clone(),
115            schema: self.schema.clone(),
116        }
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use sui_consistent_store::DbOptions;
123
124    use super::*;
125
126    #[test]
127    fn new_binds_db_and_schema() {
128        let dir = tempfile::tempdir().unwrap();
129        let (db, schema) = Db::open::<RpcStoreSchema>(dir.path(), DbOptions::default()).unwrap();
130        let reader = RpcStoreReader::new(db.clone(), Arc::new(schema));
131        // Smoke check: both handles are reachable and cloneable.
132        let _ = reader.clone();
133        assert!(reader.schema().get_pruning_watermarks().unwrap().is_none());
134    }
135
136    #[test]
137    fn at_snapshot_returns_snapshot_bound_reader() {
138        let dir = tempfile::tempdir().unwrap();
139        let (db, schema) = Db::open::<RpcStoreSchema>(dir.path(), DbOptions::default()).unwrap();
140        let reader = RpcStoreReader::new(db.clone(), Arc::new(schema));
141
142        db.take_snapshot(sui_consistent_store::Watermark::for_checkpoint(0));
143        let snap = db.at_snapshot(0).expect("snapshot retained");
144        let snap_reader = reader.at_snapshot(&snap);
145        // Both readers see the same (empty) state on a fresh DB.
146        assert!(reader.schema().get_pruning_watermarks().unwrap().is_none());
147        assert!(
148            snap_reader
149                .schema()
150                .get_pruning_watermarks()
151                .unwrap()
152                .is_none()
153        );
154    }
155}