iota_adapter_latest/programmable_transactions/
linkage_view.rs

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