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