sui_adapter_latest/static_programmable_transactions/linkage/
single_linkage.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{
5    data_store::PackageStore,
6    static_programmable_transactions::{
7        linkage::{
8            analysis::LinkageAnalyzer,
9            resolution::{ResolutionTable, VersionConstraint, add_and_unify, get_package},
10            resolved_linkage::{ExecutableLinkage, ResolvedLinkage},
11        },
12        loading::ast::{
13            Command, DeserializedPackage, LoadedFunction, PackagePayload, Transaction, Type,
14        },
15    },
16};
17use move_binary_format::file_format::Visibility;
18use move_vm_runtime::validation::verification::ast::Package as VerifiedPackage;
19use sui_types::{base_types::ObjectID, error::ExecutionErrorTrait};
20use sui_verifier::INIT_FN_NAME;
21
22/// Replace each command's per-call linkage with a single linkage shared by the whole transaction.
23///
24/// Done in two passes:
25///   1. Fold every command's package and type-argument constraints into one `ResolutionTable`,
26///      unifying as we go (an error here means the commands cannot agree on a single set of
27///      package versions).
28///      - Top level functions are pinned `exact`, while their dependencies are
29///        pinned `exact` or `at_least` based on the visibility of the top-level function.
30///        Type-argument packages are always `at_least`.
31///      - Publishes and upgrades introduce their own constraints to the linkage, but only if
32///        they have an `init` function (otherwise they do not contribute to the linkage). See
33///        comments on each of the command arms for details on this.
34///   2. Write the resulting unified linkage back into every `MoveCall`.
35///
36/// Because all calls end up sharing one linkage, every package version selection is consistent
37/// across the transaction.
38pub fn refine_to_single_linkage<E: ExecutionErrorTrait>(
39    txn: &mut Transaction,
40    linkage_analysis: &LinkageAnalyzer,
41    package_store: &dyn PackageStore,
42) -> Result<(), E> {
43    let mut base_linkage = linkage_analysis
44        .config()
45        .resolution_table_with_native_packages::<E>(package_store)?;
46
47    for (i, command) in txn.commands.iter().enumerate() {
48        analyze_command::<E>(command, &mut base_linkage, package_store)
49            .map_err(|e| e.with_command_index(i))?;
50    }
51    let resolved_linkage =
52        ExecutableLinkage::new(ResolvedLinkage::from_resolution_table(base_linkage));
53
54    for (i, command) in txn.commands.iter_mut().enumerate() {
55        write_back_linkage::<E>(command, &resolved_linkage).map_err(|e| e.with_command_index(i))?;
56    }
57
58    Ok(())
59}
60
61/// Fold a single command's contribution into the shared `resolution_table` (pass 1). Only commands
62/// that pull packages into the runtime linkage contribute; the rest are no-ops.
63fn analyze_command<E: ExecutionErrorTrait>(
64    command: &Command,
65    resolution_table: &mut ResolutionTable,
66    store: &dyn PackageStore,
67) -> Result<(), E> {
68    match command {
69        Command::MoveCall(move_call) => {
70            add_call_to_table::<E>(resolution_table, &move_call.function, store)?;
71        }
72        Command::Publish(PackagePayload::Serialized(_), ..) => {
73            invariant_violation!("Unexpected serialized package payload in linkage analysis")
74        }
75        Command::Publish(
76            PackagePayload::Deserialized(DeserializedPackage {
77                deserialized_modules,
78                ..
79            }),
80            _,
81            resolved_linkage,
82        ) => {
83            // A publish only affects the transaction's linkage if the package has an `init`
84            // function: `init` runs as part of the publish, so its dependencies must be resolvable
85            // in this transaction. Without an `init` the freshly published package is not called
86            // and contributes nothing.
87            //
88            // NB: We presuppose here that if there is a function with the name "init" in the
89            // modules being published, then it is the init function for the package.
90            //
91            // If for some reason it is not (i.e., does not conform to `init` function signature
92            // requirements), the entry points verifier will the publish later, and the transaction
93            // as a whole will error.
94            //
95            // `modules` is guaranteed to be non-empty by the `deserialize_modules` function.
96            let has_init_fn = deserialized_modules.iter().any(|module| {
97                module.function_defs().iter().any(|func_def| {
98                    let handle = module.function_handle_at(func_def.function);
99                    let name = module.identifier_at(handle.name);
100                    name == INIT_FN_NAME
101                })
102            });
103            if has_init_fn {
104                for resolved in resolved_linkage.linkage.values() {
105                    add_and_unify(resolved, store, resolution_table, VersionConstraint::exact)?;
106                }
107            }
108        }
109        Command::MakeMoveVec(Some(ty), _) => {
110            add_type_packages::<E>(resolution_table, std::iter::once(ty), store)?;
111        }
112        Command::MakeMoveVec(None, _) => (),
113        // Currently upgrades cannot have init, and therefore do not contribute to the linkage. If
114        // we want to allow init in upgrades, then we will need to analyze the init function here
115        // and add its dependencies to the linkage at that time.
116        Command::Upgrade(_, _, _, _, _) => (),
117        Command::TransferObjects(_, _) | Command::SplitCoins(_, _) | Command::MergeCoins(_, _) => {}
118    };
119    Ok(())
120}
121
122/// Add a `MoveCall`'s target package and type-argument packages to the resolution table.
123///
124/// The called package itself is pinned `exact` (we must run exactly the version being called). Its
125/// dependencies are constrained by the callee's visibility: a public entrypoint is a stable ABI,
126/// so its dependencies may be upgraded (`at_least`); a private/`friend` entrypoint is not, so they
127/// are pinned `exact`. Type-argument packages are always `at_least`, since types resolve upwards
128/// to later versions. This mirrors `LinkageAnalyzer::compute_call_linkage_`.
129fn add_call_to_table<E: ExecutionErrorTrait>(
130    resolution_table: &mut ResolutionTable,
131    function: &LoadedFunction,
132    store: &dyn PackageStore,
133) -> Result<(), E> {
134    let dep_resolution_fn = match function.visibility {
135        Visibility::Public => VersionConstraint::at_least,
136        Visibility::Private | Visibility::Friend => VersionConstraint::exact,
137    };
138    let package: ObjectID = (*function.version_mid.address()).into();
139    add_package::<E>(
140        &package,
141        store,
142        resolution_table,
143        VersionConstraint::exact,
144        dep_resolution_fn,
145    )?;
146    add_type_packages::<E>(resolution_table, function.type_arguments.iter(), store)
147}
148
149/// Resolve every package mentioned by `types`. Types resolve upwards to later versions, so the
150/// package and its deps are both `at_least`.
151fn add_type_packages<'a, E: ExecutionErrorTrait>(
152    resolution_table: &mut ResolutionTable,
153    types: impl IntoIterator<Item = &'a Type>,
154    store: &dyn PackageStore,
155) -> Result<(), E> {
156    for type_defining_id in types.into_iter().flat_map(|ty| ty.all_addresses()) {
157        add_package::<E>(
158            &ObjectID::from(type_defining_id),
159            store,
160            resolution_table,
161            VersionConstraint::at_least,
162            VersionConstraint::at_least,
163        )?;
164    }
165    Ok(())
166}
167
168/// Add a package and its transitive dependencies to the resolution table. The package itself
169/// gets `self_resolution_fn`'s constraint; every transitive dep (per the package's linkage
170/// table) gets `dep_resolution_fn`'s constraint.
171fn add_package<E: ExecutionErrorTrait>(
172    object_id: &ObjectID,
173    store: &dyn PackageStore,
174    resolution_table: &mut ResolutionTable,
175    self_resolution_fn: fn(&VerifiedPackage) -> Option<VersionConstraint>,
176    dep_resolution_fn: fn(&VerifiedPackage) -> Option<VersionConstraint>,
177) -> Result<(), E> {
178    let pkg = get_package(object_id, store)?;
179    let transitive_deps = resolution_table
180        .config
181        .linkage_table(&pkg)
182        .into_values()
183        .map(ObjectID::from);
184    add_and_unify(object_id, store, resolution_table, self_resolution_fn)?;
185    for dep_id in transitive_deps {
186        add_and_unify(&dep_id, store, resolution_table, dep_resolution_fn)?;
187    }
188    Ok(())
189}
190
191/// Overwrite each `MoveCall`'s per-call linkage with the unified transaction-wide linkage (pass 2).
192/// Only `MoveCall`s carry an executable linkage; the other commands need no write-back.
193fn write_back_linkage<E: ExecutionErrorTrait>(
194    command: &mut Command,
195    ptb_linkage: &ExecutableLinkage,
196) -> Result<(), E> {
197    match command {
198        Command::MoveCall(move_call) => {
199            let previous_linkage = &move_call.function.linkage;
200            // Stronger than the length check above: every package the per-call linkage resolved
201            // must still be present in the per-component linkage. Unification only ever adds
202            // packages (the key set is a union across member calls), so a dropped key signals a
203            // bug in how component constraints were folded together.
204            //
205            // Since `linkage`'s keys are a set, this check also implies that
206            // `previous_linkage.0.linkage.len() <= ptb_linkage.0.linkage.len()`.
207            assert_invariant!(
208                previous_linkage
209                    .0
210                    .linkage
211                    .keys()
212                    .all(|k| ptb_linkage.0.linkage.contains_key(k)),
213                "single linkage drops a package that the per-call linkage of MoveCall had resolved"
214            );
215            debug_assert!(
216                previous_linkage.0.linkage.len() <= ptb_linkage.0.linkage.len(),
217                "single linkage has fewer candidates than the per-call linkage of MoveCall"
218            );
219            move_call.function.linkage = ptb_linkage.clone();
220        }
221        Command::TransferObjects(_, _)
222        | Command::SplitCoins(_, _)
223        | Command::MergeCoins(_, _)
224        | Command::MakeMoveVec(_, _)
225        | Command::Publish(_, _, _)
226        | Command::Upgrade(_, _, _, _, _) => (),
227    };
228    Ok(())
229}