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}