sui_adapter_latest/data_store/
transaction_package_store.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use indexmap::IndexMap;
5use move_core_types::{
6    account_address::AccountAddress,
7    resolver::{ModuleResolver, SerializedPackage},
8};
9use move_vm_runtime::{
10    shared::types::VersionId, validation::verification::ast::Package as VerifiedPackage,
11};
12use std::{cell::RefCell, rc::Rc, sync::Arc};
13use sui_types::{
14    base_types::ObjectID,
15    error::{ExecutionError, SuiError, SuiResult},
16    move_package::MovePackage,
17    storage::BackingPackageStore,
18};
19
20/// A `TransactionPackageStore` is a `ModuleResolver` that fetches packages from a backing store.
21/// It also tracks packages that are being published in the current transaction and allows
22/// "loading" of those packages as well.
23///
24/// It is used to provide package loading (from storage) for the Move VM.
25#[allow(clippy::type_complexity)]
26pub struct TransactionPackageStore<'a> {
27    package_store: &'a dyn BackingPackageStore,
28
29    /// Elements in this are packages that are being published or have been published in the current
30    /// transaction.
31    /// Elements in this are _not_ safe to evict, unless evicted through `pop_package`.
32    new_packages: RefCell<IndexMap<ObjectID, (Rc<MovePackage>, Arc<VerifiedPackage>)>>,
33
34    /// A cache of packages that we've loaded so far. This is used to speed up package loading.
35    /// Elements in this are safe to be evicted based on cache decisions.
36    package_cache: RefCell<IndexMap<ObjectID, Option<Rc<MovePackage>>>>,
37}
38
39impl<'a> TransactionPackageStore<'a> {
40    pub fn new(package_store: &'a dyn BackingPackageStore) -> Self {
41        Self {
42            package_store,
43            new_packages: RefCell::new(IndexMap::new()),
44            package_cache: RefCell::new(IndexMap::new()),
45        }
46    }
47
48    /// Push a new package into the new packages. This is used to track packages that are being
49    /// published or have been published.
50    pub fn push_package(
51        &self,
52        id: ObjectID,
53        package: Rc<MovePackage>,
54        verified_package: VerifiedPackage,
55    ) -> Result<(), ExecutionError> {
56        // Check that the package ID is not already present anywhere.
57        debug_assert!(!self.new_packages.borrow().contains_key(&id));
58
59        // Insert the package into the new packages
60        // If the package already exists, we will overwrite it and signal an error.
61        if self
62            .new_packages
63            .borrow_mut()
64            .insert(id, (package, Arc::new(verified_package)))
65            .is_some()
66        {
67            invariant_violation!(
68                "Package with ID {} already exists in the new packages. This should never happen.",
69                id
70            );
71        }
72
73        Ok(())
74    }
75
76    /// Rollback a package that was pushed into the new packages. We keep the invariant that:
77    /// * You can only pop the most recent package that was pushed.
78    /// * The element being popped _must_ exist in the new packages.
79    ///
80    /// Otherwise this returns an invariant violation.
81    pub fn pop_package(&self, id: ObjectID) -> Result<Arc<VerifiedPackage>, ExecutionError> {
82        if self
83            .new_packages
84            .borrow()
85            .last()
86            .is_none_or(|(pkg_id, _)| *pkg_id != id)
87        {
88            make_invariant_violation!(
89                "Tried to pop package {} from new packages, but new packages was empty or \
90                it is not the most recent package inserted. This should never happen.",
91                id
92            );
93        }
94
95        let Some((pkg_id, (_move_pkg, verified_pkg))) = self.new_packages.borrow_mut().pop() else {
96            unreachable!(
97                "We just checked that new packages is not empty, so this should never happen."
98            );
99        };
100        assert_eq!(
101            pkg_id, id,
102            "Popped package ID {} does not match requested ID {}. This should never happen as was checked above.",
103            pkg_id, id
104        );
105
106        Ok(verified_pkg)
107    }
108
109    /// Fetch a package that is being published in the current transaction, if it exists.
110    /// This does not look in the backing store.
111    pub fn fetch_new_package(
112        &self,
113        id: &ObjectID,
114    ) -> Option<(Rc<MovePackage>, Arc<VerifiedPackage>)> {
115        self.new_packages.borrow().get(id).cloned()
116    }
117
118    /// Return all new packages that have been added to this store in the transaction.
119    pub fn to_new_packages(&self) -> Vec<MovePackage> {
120        self.new_packages
121            .borrow()
122            .iter()
123            .map(|(_, (move_pkg, _))| move_pkg.as_ref().clone())
124            .collect()
125    }
126
127    /// Fetch a package by its version ID. This will first look in the new packages, and then in
128    /// the cache for any packages that have been loaded this transaction, and then in the backing store.
129    /// If found, it will be returned as a [`MovePackage`].
130    pub fn fetch_move_package(
131        &self,
132        package_version_id: VersionId,
133    ) -> SuiResult<Option<Rc<MovePackage>>> {
134        if let Some((move_pkg, _verified_pkg)) = self.fetch_new_package(&package_version_id.into())
135        {
136            return Ok(Some(move_pkg));
137        }
138
139        if let Some(cached_pkg) = self
140            .package_cache
141            .borrow()
142            .get(&ObjectID::from(package_version_id))
143        {
144            return Ok(cached_pkg.clone());
145        }
146
147        let move_package = self
148            .package_store
149            .get_package_object(&package_version_id.into())?
150            .map(|pkg| Rc::new(pkg.move_package().clone()));
151
152        self.package_cache
153            .borrow_mut()
154            .insert(package_version_id.into(), move_package.clone());
155
156        Ok(move_package)
157    }
158
159    /// Fetch a package by its version ID. This will first look in the new packages, and then in
160    /// the cache for any packages that have been loaded this transaction, and then in the backing store.
161    /// If found, it will be returned as a [`SerializedPackage`].
162    fn fetch_package(&self, package_version_id: VersionId) -> SuiResult<Option<SerializedPackage>> {
163        self.fetch_move_package(package_version_id)
164            .and_then(|opt_pkg| {
165                opt_pkg
166                    .as_ref()
167                    .map(|pkg| pkg.as_ref().into_serialized_move_package())
168                    .transpose()
169            })
170    }
171}
172
173// Better days have arrived!
174impl ModuleResolver for TransactionPackageStore<'_> {
175    type Error = SuiError;
176    fn get_packages_static<const N: usize>(
177        &self,
178        ids: [AccountAddress; N],
179    ) -> Result<[Option<SerializedPackage>; N], Self::Error> {
180        // Once https://doc.rust-lang.org/stable/std/primitive.array.html#method.try_map is stable
181        // we can use that here.
182        let mut packages = [const { None }; N];
183        for (i, id) in ids.iter().enumerate() {
184            packages[i] = self.fetch_package(*id)?;
185        }
186
187        Ok(packages)
188    }
189
190    fn get_packages<'a>(
191        &self,
192        ids: impl ExactSizeIterator<Item = &'a AccountAddress>,
193    ) -> Result<Vec<Option<SerializedPackage>>, Self::Error> {
194        ids.map(|id| self.fetch_package(*id)).collect()
195    }
196}