sui_adapter_latest/static_programmable_transactions/linkage/
resolution.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::data_store::PackageStore;
5use std::{
6    collections::{BTreeMap, btree_map::Entry},
7    rc::Rc,
8};
9use sui_types::{
10    base_types::{ObjectID, SequenceNumber},
11    error::{ExecutionError, ExecutionErrorKind},
12    move_package::MovePackage,
13};
14
15/// Unifiers. These are used to determine how to unify two packages.
16#[derive(Debug, Clone)]
17pub enum VersionConstraint {
18    /// An exact constraint unifies as follows:
19    /// 1. Exact(a) ~ Exact(b) ==> Exact(a), iff a == b
20    /// 2. Exact(a) ~ AtLeast(b) ==> Exact(a), iff a >= b
21    Exact(SequenceNumber, ObjectID),
22    /// An at least constraint unifies as follows:
23    /// * AtLeast(a, a_version) ~ AtLeast(b, b_version) ==> AtLeast(x, max(a_version, b_version)),
24    ///   where x is the package id of either a or b (the one with the greatest version).
25    AtLeast(SequenceNumber, ObjectID),
26}
27
28#[derive(Debug, Clone)]
29pub(crate) struct ResolutionTable {
30    pub(crate) resolution_table: BTreeMap<ObjectID, VersionConstraint>,
31    /// For every version of every package that we have seen, a mapping of the ObjectID for that
32    /// package to its runtime ID.
33    pub(crate) all_versions_resolution_table: BTreeMap<ObjectID, ObjectID>,
34}
35
36impl ResolutionTable {
37    pub fn empty() -> Self {
38        Self {
39            resolution_table: BTreeMap::new(),
40            all_versions_resolution_table: BTreeMap::new(),
41        }
42    }
43}
44
45impl VersionConstraint {
46    pub fn exact(pkg: &MovePackage) -> Option<VersionConstraint> {
47        Some(VersionConstraint::Exact(pkg.version(), pkg.id()))
48    }
49
50    pub fn at_least(pkg: &MovePackage) -> Option<VersionConstraint> {
51        Some(VersionConstraint::AtLeast(pkg.version(), pkg.id()))
52    }
53
54    pub fn unify(&self, other: &VersionConstraint) -> Result<VersionConstraint, ExecutionError> {
55        match (&self, other) {
56            // If we have two exact resolutions, they must be the same.
57            (VersionConstraint::Exact(sv, self_id), VersionConstraint::Exact(ov, other_id)) => {
58                if self_id != other_id || sv != ov {
59                    Err(ExecutionError::new_with_source(
60                        ExecutionErrorKind::InvalidLinkage,
61                        format!(
62                            "exact/exact conflicting resolutions for package: linkage requires the same package \
63                                 at different versions. Linkage requires exactly {self_id} (version {sv}) and \
64                                 {other_id} (version {ov}) to be used in the same transaction"
65                        ),
66                    ))
67                } else {
68                    Ok(VersionConstraint::Exact(*sv, *self_id))
69                }
70            }
71            // Take the max if you have two at least resolutions.
72            (
73                VersionConstraint::AtLeast(self_version, sid),
74                VersionConstraint::AtLeast(other_version, oid),
75            ) => {
76                let id = if self_version > other_version {
77                    *sid
78                } else {
79                    *oid
80                };
81
82                Ok(VersionConstraint::AtLeast(
83                    *self_version.max(other_version),
84                    id,
85                ))
86            }
87            // If you unify an exact and an at least, the exact must be greater than or equal to
88            // the at least. It unifies to an exact.
89            (
90                VersionConstraint::Exact(exact_version, exact_id),
91                VersionConstraint::AtLeast(at_least_version, at_least_id),
92            )
93            | (
94                VersionConstraint::AtLeast(at_least_version, at_least_id),
95                VersionConstraint::Exact(exact_version, exact_id),
96            ) => {
97                if exact_version < at_least_version {
98                    return Err(ExecutionError::new_with_source(
99                        ExecutionErrorKind::InvalidLinkage,
100                        format!(
101                            "Exact/AtLeast conflicting resolutions for package: linkage requires exactly this \
102                                 package {exact_id} (version {exact_version}) and also at least the following \
103                                 version of the package {at_least_id} at version {at_least_version}. However \
104                                 {exact_id} is at version {exact_version} which is less than {at_least_version}."
105                        ),
106                    ));
107                }
108
109                Ok(VersionConstraint::Exact(*exact_version, *exact_id))
110            }
111        }
112    }
113}
114
115/// Load a package from the store, and update the type origin map with the types in that
116/// package.
117pub(crate) fn get_package(
118    object_id: &ObjectID,
119    store: &dyn PackageStore,
120) -> Result<Rc<MovePackage>, ExecutionError> {
121    store
122        .get_package(object_id)
123        .map_err(|e| {
124            ExecutionError::new_with_source(ExecutionErrorKind::PublishUpgradeMissingDependency, e)
125        })?
126        .ok_or_else(|| ExecutionError::from_kind(ExecutionErrorKind::InvalidLinkage))
127}
128
129// Add a package to the unification table, unifying it with any existing package in the table.
130// Errors if the packages cannot be unified (e.g., if one is exact and the other is not).
131pub(crate) fn add_and_unify(
132    object_id: &ObjectID,
133    store: &dyn PackageStore,
134    resolution_table: &mut ResolutionTable,
135    resolution_fn: fn(&MovePackage) -> Option<VersionConstraint>,
136) -> Result<(), ExecutionError> {
137    let package = get_package(object_id, store)?;
138
139    let Some(resolution) = resolution_fn(&package) else {
140        // If the resolution function returns None, we do not need to add this package to the
141        // resolution table, and this does not contribute to the linkage analysis.
142        return Ok(());
143    };
144    let original_pkg_id = package.original_package_id();
145
146    if let Entry::Vacant(e) = resolution_table.resolution_table.entry(original_pkg_id) {
147        e.insert(resolution);
148    } else {
149        let existing_unifier = resolution_table
150            .resolution_table
151            .get_mut(&original_pkg_id)
152            .expect("Guaranteed to exist");
153        *existing_unifier = existing_unifier.unify(&resolution)?;
154    }
155
156    if !resolution_table
157        .all_versions_resolution_table
158        .contains_key(object_id)
159    {
160        resolution_table
161            .all_versions_resolution_table
162            .insert(*object_id, original_pkg_id);
163    }
164
165    Ok(())
166}