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