sui_rpc_store/reader/
layout.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Move type-layout resolver.
5//!
6//! [`RpcStateReader::get_struct_layout_with_overlay`] resolves the
7//! [`MoveTypeLayout`] of a [`StructTag`] using a Move type
8//! resolver. The validator's perpetual store pulls every piece
9//! from its [`AuthorityState`]; this adapter assembles them from
10//! the typed [`RpcStoreSchema`] CFs instead:
11//!
12//! 1. A [`BackingPackageStore`] (here:
13//!    [`PackageStoreOverObjects`]) that returns package objects
14//!    by id, looking them up through a reverse scan of `objects`.
15//!    Packages are immutable, so a single lookup is enough.
16//! 2. An overlay wrapping the caller-supplied [`ObjectSet`] on top
17//!    of the backing store.
18//! 3. The live [`ProtocolConfig`] — sourced from the latest
19//!    epoch's [`SuiSystemState`] (protocol version) plus the
20//!    chain id recorded by any pipeline's first checkpoint.
21//! 4. An [`Executor`] for that protocol config; its
22//!    `type_layout_resolver` is what actually performs the
23//!    layout build.
24//!
25//! [`AuthorityState`]: https://docs.rs/sui-core/latest/sui_core/authority/struct.AuthorityState.html
26//! [`Executor`]: sui_execution::Executor
27//! [`ProtocolConfig`]: sui_protocol_config::ProtocolConfig
28//! [`BackingPackageStore`]: sui_types::storage::BackingPackageStore
29//! [`RpcStateReader`]: sui_types::storage::RpcStateReader
30//! [`SuiSystemState`]: sui_types::sui_system_state::SuiSystemState
31
32use move_core_types::annotated_value::MoveTypeLayout;
33use move_core_types::language_storage::StructTag;
34use sui_consistent_store::FrameworkSchema;
35use sui_consistent_store::reader::Reader;
36use sui_protocol_config::ProtocolConfig;
37use sui_protocol_config::ProtocolVersion;
38use sui_types::base_types::ObjectID;
39use sui_types::digests::ChainIdentifier;
40use sui_types::digests::CheckpointDigest;
41use sui_types::error::SuiResult;
42use sui_types::full_checkpoint_content::ObjectSet;
43use sui_types::storage::BackingPackageStore;
44use sui_types::storage::ObjectStore;
45use sui_types::storage::OverlayBackingPackageStore;
46use sui_types::storage::PackageObject;
47use sui_types::storage::error::Error as StorageError;
48use sui_types::storage::error::Result as StorageResult;
49use sui_types::sui_system_state::SuiSystemStateTrait;
50
51use crate::RpcStoreSchema;
52use crate::reader::RpcStoreReader;
53
54impl<R: Reader + Send + Sync> RpcStoreReader<R> {
55    /// Resolve the [`MoveTypeLayout`] for a given [`StructTag`],
56    /// optionally seeded with extra objects in `overlay`.
57    ///
58    /// Returns `Ok(None)` when the store doesn't have enough
59    /// context to build an executor (no checkpoints observed yet,
60    /// no chain id recorded). Surfaces resolver failures as
61    /// [`StorageError::custom`].
62    pub fn resolve_struct_layout(
63        &self,
64        struct_tag: &StructTag,
65        overlay: &ObjectSet,
66    ) -> StorageResult<Option<MoveTypeLayout>> {
67        let Some(protocol_config) = self.live_protocol_config()? else {
68            return Ok(None);
69        };
70
71        let executor = sui_execution::executor(&protocol_config, /* silent */ true)
72            .map_err(StorageError::custom)?;
73
74        let backing = PackageStoreOverObjects {
75            schema: self.schema(),
76        };
77        let overlay_store = OverlayBackingPackageStore::new(overlay, &backing);
78
79        let layout = executor
80            .type_layout_resolver(&protocol_config, Box::new(overlay_store))
81            .get_annotated_layout(struct_tag)
82            .map_err(StorageError::custom)?;
83        Ok(Some(layout.into_layout()))
84    }
85
86    /// Resolve the live [`ProtocolConfig`], combining the protocol
87    /// version recorded in the latest epoch's [`SuiSystemState`]
88    /// with the chain id recorded in the framework `__chain_id`
89    /// CF. Returns `Ok(None)` when either side is missing —
90    /// callers treat the absence as "no layout available" rather
91    /// than as an error.
92    fn live_protocol_config(&self) -> StorageResult<Option<ProtocolConfig>> {
93        let Some(chain) = self.read_chain_identifier()? else {
94            return Ok(None);
95        };
96        let Some(protocol_version) = self.latest_protocol_version()? else {
97            return Ok(None);
98        };
99        Ok(ProtocolConfig::get_for_version_if_supported(
100            protocol_version,
101            chain.chain(),
102        ))
103    }
104
105    /// Read the chain identifier from any framework `__chain_id`
106    /// row. Every pipeline records the same value on first
107    /// contact, so any entry is authoritative.
108    fn read_chain_identifier(&self) -> StorageResult<Option<ChainIdentifier>> {
109        let framework = FrameworkSchema::new(self.db().clone());
110        let first = framework
111            .chain_ids
112            .iter(..)
113            .map_err(StorageError::custom)?
114            .next();
115        let Some(entry) = first else {
116            return Ok(None);
117        };
118        let (_, chain_id) = entry.map_err(StorageError::custom)?;
119        Ok(Some(ChainIdentifier::from(CheckpointDigest::new(
120            chain_id.0,
121        ))))
122    }
123
124    /// Read the protocol version from the latest epoch's
125    /// [`SuiSystemState`].
126    fn latest_protocol_version(&self) -> StorageResult<Option<ProtocolVersion>> {
127        let epochs = &self.schema().epochs;
128        let latest = epochs.iter_rev(..).map_err(StorageError::custom)?.next();
129        let Some(entry) = latest else {
130            return Ok(None);
131        };
132        let (epoch_id, _) = entry.map_err(StorageError::custom)?;
133        let Some(info) = self
134            .schema()
135            .get_epoch(epoch_id.0)
136            .map_err(StorageError::custom)?
137        else {
138            return Ok(None);
139        };
140        let Some(state) = info.system_state else {
141            return Ok(None);
142        };
143        Ok(Some(ProtocolVersion::new(state.protocol_version())))
144    }
145}
146
147/// [`BackingPackageStore`] backed by the typed
148/// [`RpcStoreSchema`] CFs. Looks each package up by its storage
149/// id through `get_object` (a reverse scan of `objects`); packages
150/// are immutable, so the latest live row IS the entirety of the
151/// package's on-chain state.
152struct PackageStoreOverObjects<'a, R: Reader> {
153    schema: &'a RpcStoreSchema<R>,
154}
155
156impl<R: Reader> BackingPackageStore for PackageStoreOverObjects<'_, R> {
157    fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
158        let Some(object) = ObjectStore::get_object(&self.schema_object_store(), package_id) else {
159            return Ok(None);
160        };
161        if !object.is_package() {
162            return Ok(None);
163        }
164        Ok(Some(PackageObject::new(object)))
165    }
166}
167
168impl<R: Reader> PackageStoreOverObjects<'_, R> {
169    /// Adapt the schema's inherent `get_object` helper into a
170    /// small struct that implements [`ObjectStore`], so we can
171    /// route `BackingPackageStore` lookups through the existing
172    /// trait surface without re-implementing the `objects` reverse
173    /// scan.
174    fn schema_object_store(&self) -> SchemaObjectStore<'_, R> {
175        SchemaObjectStore {
176            schema: self.schema,
177        }
178    }
179}
180
181struct SchemaObjectStore<'a, R: Reader> {
182    schema: &'a RpcStoreSchema<R>,
183}
184
185impl<R: Reader> ObjectStore for SchemaObjectStore<'_, R> {
186    fn get_object(&self, object_id: &ObjectID) -> Option<sui_types::object::Object> {
187        self.schema.get_object(*object_id).ok().flatten()
188    }
189
190    fn get_object_by_key(
191        &self,
192        object_id: &ObjectID,
193        version: sui_types::base_types::VersionNumber,
194    ) -> Option<sui_types::object::Object> {
195        self.schema
196            .get_object_by_key(*object_id, version)
197            .ok()
198            .flatten()
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use std::sync::Arc;
205
206    use sui_consistent_store::Db;
207    use sui_consistent_store::DbOptions;
208    use sui_types::full_checkpoint_content::ObjectSet;
209
210    use super::*;
211    use crate::reader::RpcStoreReader;
212
213    /// On a fresh store there's no chain id and no epoch row, so
214    /// `resolve_struct_layout` falls through to `Ok(None)` rather
215    /// than failing or constructing an executor against bogus
216    /// state.
217    #[test]
218    fn resolve_returns_none_when_no_chain_id_or_epoch() {
219        let dir = tempfile::tempdir().unwrap();
220        let (db, schema) = Db::open::<RpcStoreSchema>(dir.path(), DbOptions::default()).unwrap();
221        let reader = RpcStoreReader::new(db, Arc::new(schema));
222
223        let tag = StructTag {
224            address: move_core_types::account_address::AccountAddress::new([1u8; 32]),
225            module: move_core_types::identifier::Identifier::new("foo").unwrap(),
226            name: move_core_types::identifier::Identifier::new("Bar").unwrap(),
227            type_params: vec![],
228        };
229        let overlay = ObjectSet::default();
230        assert!(
231            reader
232                .resolve_struct_layout(&tag, &overlay)
233                .unwrap()
234                .is_none()
235        );
236    }
237}