sui_adapter_v0/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, SerializedPackage},
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: 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 enum LinkageInfo {
48    /// No linkage information -- requests to relink will fail with an invariant violation.
49    Unset,
50    /// Linkage information cannot be altered, and does not affect type or module identity.
51    Universal,
52    /// Linkage provided by the package found at `storage_id` whose module self-addresses are
53    /// `runtime_id`.
54    Set(PackageLinkage),
55}
56
57#[derive(Debug)]
58pub struct PackageLinkage {
59    storage_id: AccountAddress,
60    runtime_id: AccountAddress,
61    link_table: BTreeMap<ObjectID, UpgradeInfo>,
62}
63
64pub struct SavedLinkage(PackageLinkage);
65
66impl<'state> LinkageView<'state> {
67    pub fn new(resolver: Box<dyn SuiResolver + 'state>, linkage_info: LinkageInfo) -> Self {
68        Self {
69            resolver,
70            linkage_info,
71            type_origin_cache: RefCell::new(HashMap::new()),
72            past_contexts: RefCell::new(HashSet::new()),
73        }
74    }
75
76    pub fn reset_linkage(&mut self) {
77        if let LinkageInfo::Set(_) = &self.linkage_info {
78            // Resetting does not affect "universal" linkage.
79            self.linkage_info = LinkageInfo::Unset;
80        }
81    }
82
83    /// Indicates whether this `LinkageView` has had its context set to match the linkage in
84    /// `context`.
85    pub fn has_linkage(&self, context: ObjectID) -> bool {
86        match &self.linkage_info {
87            LinkageInfo::Unset => false,
88            LinkageInfo::Universal => true,
89            LinkageInfo::Set(linkage) => linkage.storage_id == *context,
90        }
91    }
92
93    /// Reset the linkage, but save the context that existed before, if there was one.
94    pub fn steal_linkage(&mut self) -> Option<SavedLinkage> {
95        if let LinkageInfo::Universal = &self.linkage_info {
96            None
97        } else {
98            match std::mem::replace(&mut self.linkage_info, LinkageInfo::Unset) {
99                LinkageInfo::Set(linkage) => Some(SavedLinkage(linkage)),
100                LinkageInfo::Unset => None,
101                LinkageInfo::Universal => unreachable!(),
102            }
103        }
104    }
105
106    /// Restore a previously saved linkage context.  Fails if there is already a context set.
107    pub fn restore_linkage(&mut self, saved: Option<SavedLinkage>) -> Result<(), ExecutionError> {
108        let Some(SavedLinkage(saved)) = saved else {
109            return Ok(());
110        };
111
112        match &self.linkage_info {
113            LinkageInfo::Unset => (),
114            LinkageInfo::Universal => (),
115            LinkageInfo::Set(existing) => {
116                invariant_violation!(
117                    "Attempt to overwrite linkage by restoring: {saved:#?} \
118                     Existing linkage: {existing:#?}",
119                )
120            }
121        }
122
123        // No need to populate type origin cache, because a saved context must have been set as a
124        // linkage before, and the cache would have been populated at that time.
125        self.linkage_info = LinkageInfo::Set(saved);
126        Ok(())
127    }
128
129    /// Set the linkage context to the information based on the linkage and type origin tables from
130    /// the `context` package.  Returns the original package ID (aka the runtime ID) of the context
131    /// package on success.
132    pub fn set_linkage(&mut self, context: &MovePackage) -> Result<AccountAddress, ExecutionError> {
133        match &self.linkage_info {
134            LinkageInfo::Unset => (),
135            LinkageInfo::Universal => return Ok(*context.id()),
136
137            LinkageInfo::Set(existing) => {
138                invariant_violation!(
139                    "Attempt to overwrite linkage info with context from {}. \
140                     Existing linkage: {existing:#?}",
141                    context.id(),
142                )
143            }
144        }
145
146        let linkage = PackageLinkage::from(context);
147        let storage_id = context.id();
148        let runtime_id = linkage.runtime_id;
149        self.linkage_info = LinkageInfo::Set(linkage);
150
151        if !self.past_contexts.borrow_mut().insert(storage_id) {
152            return Ok(runtime_id);
153        }
154
155        // Pre-populate the type origin cache with entries from the current package -- this is
156        // necessary to serve "defining module" requests for unpublished packages, but will also
157        // speed up other requests.
158        for TypeOrigin {
159            module_name,
160            datatype_name: struct_name,
161            package: defining_id,
162        } in context.type_origin_table()
163        {
164            let Ok(module_name) = Identifier::from_str(module_name) else {
165                invariant_violation!("Module name isn't an identifier: {module_name}");
166            };
167
168            let Ok(struct_name) = Identifier::from_str(struct_name) else {
169                invariant_violation!("Struct name isn't an identifier: {struct_name}");
170            };
171
172            let runtime_id = ModuleId::new(runtime_id, module_name);
173            self.add_type_origin(runtime_id, struct_name, *defining_id)?;
174        }
175
176        Ok(runtime_id)
177    }
178
179    pub fn original_package_id(&self) -> Option<AccountAddress> {
180        if let LinkageInfo::Set(linkage) = &self.linkage_info {
181            Some(linkage.runtime_id)
182        } else {
183            None
184        }
185    }
186
187    fn get_cached_type_origin(
188        &self,
189        runtime_id: &ModuleId,
190        struct_: &IdentStr,
191    ) -> Option<AccountAddress> {
192        self.type_origin_cache
193            .borrow()
194            .get(runtime_id)?
195            .get(struct_)
196            .cloned()
197    }
198
199    fn add_type_origin(
200        &self,
201        runtime_id: ModuleId,
202        struct_: Identifier,
203        defining_id: ObjectID,
204    ) -> Result<(), ExecutionError> {
205        let mut cache = self.type_origin_cache.borrow_mut();
206        let module_cache = cache.entry(runtime_id.clone()).or_default();
207
208        match module_cache.entry(struct_) {
209            Entry::Vacant(entry) => {
210                entry.insert(*defining_id);
211            }
212
213            Entry::Occupied(entry) => {
214                if entry.get() != &*defining_id {
215                    invariant_violation!(
216                        "Conflicting defining ID for {}::{}: {} and {}",
217                        runtime_id,
218                        entry.key(),
219                        defining_id,
220                        entry.get(),
221                    );
222                }
223            }
224        }
225
226        Ok(())
227    }
228}
229
230impl From<&MovePackage> for PackageLinkage {
231    fn from(package: &MovePackage) -> Self {
232        Self {
233            storage_id: package.id().into(),
234            runtime_id: package.original_package_id().into(),
235            link_table: package.linkage_table().clone(),
236        }
237    }
238}
239
240impl LinkageResolver for LinkageView<'_> {
241    type Error = SuiError;
242
243    fn link_context(&self) -> AccountAddress {
244        if let LinkageInfo::Set(linkage) = &self.linkage_info {
245            linkage.storage_id
246        } else {
247            AccountAddress::ZERO
248        }
249    }
250
251    fn relocate(&self, module_id: &ModuleId) -> Result<ModuleId, Self::Error> {
252        let linkage = match &self.linkage_info {
253            LinkageInfo::Set(linkage) => linkage,
254            LinkageInfo::Universal => return Ok(module_id.clone()),
255
256            LinkageInfo::Unset => {
257                invariant_violation!("No linkage context set while relocating {module_id}.")
258            }
259        };
260
261        // The request is to relocate a module in the package that the link context is from.  This
262        // entry will not be stored in the linkage table, so must be handled specially.
263        if module_id.address() == &linkage.runtime_id {
264            return Ok(ModuleId::new(
265                linkage.storage_id,
266                module_id.name().to_owned(),
267            ));
268        }
269
270        let runtime_id = ObjectID::from_address(*module_id.address());
271        let Some(upgrade) = linkage.link_table.get(&runtime_id) else {
272            invariant_violation!(
273                "Missing linkage for {runtime_id} in context {}, runtime_id is {}",
274                linkage.storage_id,
275                linkage.runtime_id
276            );
277        };
278
279        Ok(ModuleId::new(
280            upgrade.upgraded_id.into(),
281            module_id.name().to_owned(),
282        ))
283    }
284
285    fn defining_module(
286        &self,
287        runtime_id: &ModuleId,
288        struct_: &IdentStr,
289    ) -> Result<ModuleId, Self::Error> {
290        match &self.linkage_info {
291            LinkageInfo::Set(_) => (),
292            LinkageInfo::Universal => return Ok(runtime_id.clone()),
293
294            LinkageInfo::Unset => {
295                invariant_violation!(
296                    "No linkage context set for defining module query on {runtime_id}::{struct_}."
297                )
298            }
299        };
300
301        if let Some(cached) = self.get_cached_type_origin(runtime_id, struct_) {
302            return Ok(ModuleId::new(cached, runtime_id.name().to_owned()));
303        }
304
305        let storage_id = ObjectID::from(*self.relocate(runtime_id)?.address());
306        let Some(package) = self.resolver.get_package_object(&storage_id)? else {
307            invariant_violation!("Missing dependent package in store: {storage_id}",)
308        };
309
310        for TypeOrigin {
311            module_name,
312            datatype_name: struct_name,
313            package,
314        } in package.move_package().type_origin_table()
315        {
316            if module_name == runtime_id.name().as_str() && struct_name == struct_.as_str() {
317                self.add_type_origin(runtime_id.clone(), struct_.to_owned(), *package)?;
318                return Ok(ModuleId::new(**package, runtime_id.name().to_owned()));
319            }
320        }
321
322        invariant_violation!(
323            "{runtime_id}::{struct_} not found in type origin table in {storage_id} (v{})",
324            package.move_package().version(),
325        )
326    }
327}
328
329// Remaining implementations delegated to state_view
330
331impl ModuleResolver for LinkageView<'_> {
332    type Error = SuiError;
333
334    fn get_module(&self, id: &ModuleId) -> Result<Option<Vec<u8>>, Self::Error> {
335        get_module(self, id)
336    }
337
338    fn get_packages_static<const N: usize>(
339        &self,
340        _ids: [AccountAddress; N],
341    ) -> Result<[Option<SerializedPackage>; N], Self::Error> {
342        unreachable!("v0 get_packages_static should not be called on LinkageView")
343    }
344
345    fn get_packages<'a>(
346        &self,
347        _ids: impl ExactSizeIterator<Item = &'a AccountAddress>,
348    ) -> Result<Vec<Option<SerializedPackage>>, Self::Error> {
349        unreachable!("v0 get_packages should not be called on LinkageView")
350    }
351}
352
353impl BackingPackageStore for LinkageView<'_> {
354    fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
355        self.resolver.get_package_object(package_id)
356    }
357}