sui_adapter_v2/programmable_transactions/
linkage_view.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::{
5    cell::RefCell,
6    collections::{hash_map::Entry, BTreeMap, HashMap, HashSet},
7    str::FromStr,
8};
9
10use crate::execution_value::SuiResolver;
11use move_core_types::{
12    account_address::AccountAddress,
13    identifier::{IdentStr, Identifier},
14    language_storage::ModuleId,
15    resolver::{LinkageResolver, ModuleResolver},
16};
17use sui_types::storage::{get_module, PackageObject};
18use sui_types::{
19    base_types::ObjectID,
20    error::{ExecutionError, SuiError, SuiResult},
21    move_package::{MovePackage, TypeOrigin, UpgradeInfo},
22    storage::BackingPackageStore,
23};
24
25/// Exposes module and linkage resolution to the Move runtime.  The first by delegating to
26/// `resolver` and the second via linkage information that is loaded from a move package.
27pub struct LinkageView<'state> {
28    /// Interface to resolve packages, modules and resources directly from the store.
29    resolver: Box<dyn SuiResolver + 'state>,
30    /// Information used to change module and type identities during linkage.
31    linkage_info: Option<LinkageInfo>,
32    /// Cache containing the type origin information from every package that has been set as the
33    /// link context, and every other type that has been requested by the loader in this session.
34    /// It's okay to retain entries in this cache between different link contexts because a type's
35    /// Runtime ID and Defining ID are invariant between across link contexts.
36    ///
37    /// Cache is keyed first by the Runtime ID of the type's module, and then the type's identifier.
38    /// The value is the ObjectID/Address of the package that introduced the type.
39    type_origin_cache: RefCell<HashMap<ModuleId, HashMap<Identifier, AccountAddress>>>,
40    /// Cache of past package addresses that have been the link context -- if a package is in this
41    /// set, then we will not try to load its type origin table when setting it as a context (again).
42    past_contexts: RefCell<HashSet<ObjectID>>,
43}
44
45#[derive(Debug)]
46pub struct LinkageInfo {
47    storage_id: AccountAddress,
48    runtime_id: AccountAddress,
49    link_table: BTreeMap<ObjectID, UpgradeInfo>,
50}
51
52pub struct SavedLinkage(LinkageInfo);
53
54impl<'state> LinkageView<'state> {
55    pub fn new(resolver: Box<dyn SuiResolver + 'state>) -> Self {
56        Self {
57            resolver,
58            linkage_info: None,
59            type_origin_cache: RefCell::new(HashMap::new()),
60            past_contexts: RefCell::new(HashSet::new()),
61        }
62    }
63
64    pub fn reset_linkage(&mut self) {
65        self.linkage_info = None;
66    }
67
68    /// Indicates whether this `LinkageView` has had its context set to match the linkage in
69    /// `context`.
70    pub fn has_linkage(&self, context: ObjectID) -> bool {
71        self.linkage_info
72            .as_ref()
73            .is_some_and(|l| l.storage_id == *context)
74    }
75
76    /// Reset the linkage, but save the context that existed before, if there was one.
77    pub fn steal_linkage(&mut self) -> Option<SavedLinkage> {
78        Some(SavedLinkage(self.linkage_info.take()?))
79    }
80
81    /// Restore a previously saved linkage context.  Fails if there is already a context set.
82    pub fn restore_linkage(&mut self, saved: Option<SavedLinkage>) -> Result<(), ExecutionError> {
83        let Some(SavedLinkage(saved)) = saved else {
84            return Ok(());
85        };
86
87        if let Some(existing) = &self.linkage_info {
88            invariant_violation!(
89                "Attempt to overwrite linkage by restoring: {saved:#?} \
90                 Existing linkage: {existing:#?}",
91            )
92        }
93
94        // No need to populate type origin cache, because a saved context must have been set as a
95        // linkage before, and the cache would have been populated at that time.
96        self.linkage_info = Some(saved);
97        Ok(())
98    }
99
100    /// Set the linkage context to the information based on the linkage and type origin tables from
101    /// the `context` package.  Returns the original package ID (aka the runtime ID) of the context
102    /// package on success.
103    pub fn set_linkage(&mut self, context: &MovePackage) -> Result<AccountAddress, ExecutionError> {
104        if let Some(existing) = &self.linkage_info {
105            invariant_violation!(
106                "Attempt to overwrite linkage info with context from {}. \
107                    Existing linkage: {existing:#?}",
108                context.id(),
109            )
110        }
111
112        let linkage = LinkageInfo::from(context);
113        let storage_id = context.id();
114        let runtime_id = linkage.runtime_id;
115        self.linkage_info = Some(linkage);
116
117        if !self.past_contexts.borrow_mut().insert(storage_id) {
118            return Ok(runtime_id);
119        }
120
121        // Pre-populate the type origin cache with entries from the current package -- this is
122        // necessary to serve "defining module" requests for unpublished packages, but will also
123        // speed up other requests.
124        for TypeOrigin {
125            module_name,
126            datatype_name,
127            package: defining_id,
128        } in context.type_origin_table()
129        {
130            let Ok(module_name) = Identifier::from_str(module_name) else {
131                invariant_violation!("Module name isn't an identifier: {module_name}");
132            };
133
134            let Ok(struct_name) = Identifier::from_str(datatype_name) else {
135                invariant_violation!("Struct name isn't an identifier: {datatype_name}");
136            };
137
138            let runtime_id = ModuleId::new(runtime_id, module_name);
139            self.add_type_origin(runtime_id, struct_name, *defining_id)?;
140        }
141
142        Ok(runtime_id)
143    }
144
145    pub fn original_package_id(&self) -> Option<AccountAddress> {
146        Some(self.linkage_info.as_ref()?.runtime_id)
147    }
148
149    fn get_cached_type_origin(
150        &self,
151        runtime_id: &ModuleId,
152        struct_: &IdentStr,
153    ) -> Option<AccountAddress> {
154        self.type_origin_cache
155            .borrow()
156            .get(runtime_id)?
157            .get(struct_)
158            .cloned()
159    }
160
161    fn add_type_origin(
162        &self,
163        runtime_id: ModuleId,
164        struct_: Identifier,
165        defining_id: ObjectID,
166    ) -> Result<(), ExecutionError> {
167        let mut cache = self.type_origin_cache.borrow_mut();
168        let module_cache = cache.entry(runtime_id.clone()).or_default();
169
170        match module_cache.entry(struct_) {
171            Entry::Vacant(entry) => {
172                entry.insert(*defining_id);
173            }
174
175            Entry::Occupied(entry) => {
176                if entry.get() != &*defining_id {
177                    invariant_violation!(
178                        "Conflicting defining ID for {}::{}: {} and {}",
179                        runtime_id,
180                        entry.key(),
181                        defining_id,
182                        entry.get(),
183                    );
184                }
185            }
186        }
187
188        Ok(())
189    }
190
191    pub(crate) fn link_context(&self) -> AccountAddress {
192        self.linkage_info
193            .as_ref()
194            .map_or(AccountAddress::ZERO, |l| l.storage_id)
195    }
196
197    pub(crate) fn relocate(&self, module_id: &ModuleId) -> Result<ModuleId, SuiError> {
198        let Some(linkage) = &self.linkage_info else {
199            invariant_violation!("No linkage context set while relocating {module_id}.")
200        };
201
202        // The request is to relocate a module in the package that the link context is from.  This
203        // entry will not be stored in the linkage table, so must be handled specially.
204        if module_id.address() == &linkage.runtime_id {
205            return Ok(ModuleId::new(
206                linkage.storage_id,
207                module_id.name().to_owned(),
208            ));
209        }
210
211        let runtime_id = ObjectID::from_address(*module_id.address());
212        let Some(upgrade) = linkage.link_table.get(&runtime_id) else {
213            invariant_violation!(
214                "Missing linkage for {runtime_id} in context {}, runtime_id is {}",
215                linkage.storage_id,
216                linkage.runtime_id
217            );
218        };
219
220        Ok(ModuleId::new(
221            upgrade.upgraded_id.into(),
222            module_id.name().to_owned(),
223        ))
224    }
225
226    pub(crate) fn defining_module(
227        &self,
228        runtime_id: &ModuleId,
229        struct_: &IdentStr,
230    ) -> Result<ModuleId, SuiError> {
231        if self.linkage_info.is_none() {
232            invariant_violation!(
233                "No linkage context set for defining module query on {runtime_id}::{struct_}."
234            )
235        }
236
237        if let Some(cached) = self.get_cached_type_origin(runtime_id, struct_) {
238            return Ok(ModuleId::new(cached, runtime_id.name().to_owned()));
239        }
240
241        let storage_id = ObjectID::from(*self.relocate(runtime_id)?.address());
242        let Some(package) = self.resolver.get_package_object(&storage_id)? else {
243            invariant_violation!("Missing dependent package in store: {storage_id}",)
244        };
245
246        for TypeOrigin {
247            module_name,
248            datatype_name,
249            package,
250        } in package.move_package().type_origin_table()
251        {
252            if module_name == runtime_id.name().as_str() && datatype_name == struct_.as_str() {
253                self.add_type_origin(runtime_id.clone(), struct_.to_owned(), *package)?;
254                return Ok(ModuleId::new(**package, runtime_id.name().to_owned()));
255            }
256        }
257
258        invariant_violation!(
259            "{runtime_id}::{struct_} not found in type origin table in {storage_id} (v{})",
260            package.move_package().version(),
261        )
262    }
263}
264
265impl From<&MovePackage> for LinkageInfo {
266    fn from(package: &MovePackage) -> Self {
267        Self {
268            storage_id: package.id().into(),
269            runtime_id: package.original_package_id().into(),
270            link_table: package.linkage_table().clone(),
271        }
272    }
273}
274
275impl LinkageResolver for LinkageView<'_> {
276    type Error = SuiError;
277
278    fn link_context(&self) -> AccountAddress {
279        LinkageView::link_context(self)
280    }
281
282    fn relocate(&self, module_id: &ModuleId) -> Result<ModuleId, Self::Error> {
283        LinkageView::relocate(self, module_id)
284    }
285
286    fn defining_module(
287        &self,
288        runtime_id: &ModuleId,
289        struct_: &IdentStr,
290    ) -> Result<ModuleId, Self::Error> {
291        LinkageView::defining_module(self, runtime_id, struct_)
292    }
293}
294
295// Remaining implementations delegated to state_view
296
297impl ModuleResolver for LinkageView<'_> {
298    type Error = SuiError;
299
300    fn get_module(&self, id: &ModuleId) -> Result<Option<Vec<u8>>, Self::Error> {
301        get_module(self, id)
302    }
303}
304
305impl BackingPackageStore for LinkageView<'_> {
306    fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
307        self.resolver.get_package_object(package_id)
308    }
309}