1use crate::{
5 data_store::VerifiedPackageStore,
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 Argument, Command, DeserializedPackage, InputArg, InputType, Inputs, LoadedFunction,
14 PackagePayload, Transaction, Type, module_has_init,
15 },
16 },
17};
18use move_binary_format::file_format::Visibility;
19use move_vm_runtime::validation::verification::ast::Package as VerifiedPackage;
20use std::{
21 collections::{BTreeMap, BTreeSet},
22 sync::Arc,
23};
24use sui_protocol_config::ProtocolConfig;
25use sui_types::{
26 Identifier,
27 base_types::ObjectID,
28 error::ExecutionErrorTrait,
29 execution_status::{ExecutionErrorKind, PackageUpgradeError},
30};
31
32pub fn refine_to_single_linkage<E: ExecutionErrorTrait>(
49 txn: &mut Transaction,
50 linkage_analysis: &LinkageAnalyzer,
51 package_store: &VerifiedPackageStore<'_>,
52 protocol_config: &ProtocolConfig,
53) -> Result<(), E> {
54 let mut base_linkage = linkage_analysis
55 .config()
56 .resolution_table_with_native_packages::<E, _>(package_store)?;
57
58 for (i, command) in txn.commands.iter().enumerate() {
59 analyze_command::<E>(command, &mut base_linkage, package_store, protocol_config)
60 .map_err(|e| e.with_command_index(i))?;
61 add_used_input_linkage::<E>(
62 command.arguments(),
63 &txn.inputs,
64 &mut base_linkage,
65 package_store,
66 protocol_config,
67 )
68 .map_err(|e| e.with_command_index(i))?;
69 }
70
71 add_withdrawal_compatibility_input_linkage::<E>(
72 &txn.inputs,
73 &mut base_linkage,
74 package_store,
75 protocol_config,
76 )?;
77
78 if protocol_config.enable_order_independent_upgrade_init_linkage() {
79 for (i, command) in txn.commands.iter().enumerate() {
80 let Command::Upgrade(payload, _, current_package_id, _, resolved_linkage) = command
81 else {
82 continue;
83 };
84 analyze_upgrade_command::<E>(
85 payload,
86 current_package_id,
87 resolved_linkage,
88 &mut base_linkage,
89 package_store,
90 protocol_config,
91 )
92 .map_err(|e| e.with_command_index(i))?;
93 }
94 }
95
96 if protocol_config.harden_linkage_consistency() {
99 for (i, command) in txn.commands.iter().enumerate() {
100 validate_init_linkage_pinning::<E>(command, &base_linkage, package_store)
101 .map_err(|e| e.with_command_index(i))?;
102 }
103 }
104
105 let resolved_linkage =
106 ExecutableLinkage::new(ResolvedLinkage::from_resolution_table(base_linkage));
107
108 assert_invariant!(
110 !protocol_config.harden_linkage_consistency()
111 || resolved_linkage
112 .0
113 .linkage_resolution
114 .iter()
115 .all(|(_, resolution)| { resolution.version.is_some() }),
116 "Unified linkage must resolve every package to a specific version, but found: {:?}",
117 resolved_linkage
118 );
119
120 for (i, command) in txn.commands.iter_mut().enumerate() {
121 write_back_linkage::<E>(command, &resolved_linkage).map_err(|e| e.with_command_index(i))?;
122 }
123
124 txn.unified_linkage = Some(resolved_linkage);
125
126 Ok(())
127}
128
129fn add_used_input_linkage<'a, E: ExecutionErrorTrait>(
130 arguments: impl IntoIterator<Item = &'a Argument>,
131 inputs: &Inputs,
132 resolution_table: &mut ResolutionTable,
133 store: &VerifiedPackageStore<'_>,
134 protocol_config: &ProtocolConfig,
135) -> Result<(), E> {
136 if !protocol_config.harden_linkage_consistency() {
137 return Ok(());
138 }
139
140 for argument in arguments {
141 if let Argument::Input(i) = argument
142 && let Some((_, InputType::Fixed(ty))) = inputs.get(*i as usize)
143 {
144 add_type_packages::<E>(resolution_table, std::iter::once(ty), store)?;
145 }
146 }
147 Ok(())
148}
149
150fn add_withdrawal_compatibility_input_linkage<E: ExecutionErrorTrait>(
151 inputs: &Inputs,
152 resolution_table: &mut ResolutionTable,
153 store: &VerifiedPackageStore<'_>,
154 protocol_config: &ProtocolConfig,
155) -> Result<(), E> {
156 if !protocol_config.harden_linkage_consistency() {
157 return Ok(());
158 }
159
160 add_type_packages::<E>(
161 resolution_table,
162 inputs.iter().filter_map(|(input_arg, input_ty)| {
163 if let InputArg::FundsWithdrawal(withdrawal) = input_arg
164 && withdrawal.from_compatibility_object
165 && let InputType::Fixed(ty) = input_ty
166 {
167 Some(ty)
168 } else {
169 None
170 }
171 }),
172 store,
173 )
174}
175
176fn validate_init_linkage_pinning<E: ExecutionErrorTrait>(
180 command: &Command,
181 resolution_table: &ResolutionTable,
182 store: &VerifiedPackageStore<'_>,
183) -> Result<(), E> {
184 let validate_package_init_linkage = |declared_linkage: &ResolvedLinkage, err_context| {
185 for (original_id, version_id) in &declared_linkage.linkage {
186 match resolution_table.resolution_table.get(original_id) {
187 Some(VersionConstraint::Exact(_, pinned_id)) if pinned_id == version_id => (),
188 other => {
189 invariant_violation!(
190 "{err_context} runs an `init` that requires package {original_id} at \
191 {version_id}, but the transaction linkage pins it to {other:?}"
192 )
193 }
194 }
195 }
196 Ok(())
197 };
198
199 match command {
200 Command::Publish(PackagePayload::Deserialized(pkg), _, resolved_linkage) => {
201 if pkg.has_potential_init() {
202 validate_package_init_linkage(resolved_linkage, "publish")
203 } else {
204 Ok(())
205 }
206 }
207 Command::Upgrade(
208 PackagePayload::Deserialized(pkg),
209 _,
210 current_package_id,
211 _,
212 resolved_linkage,
213 ) => {
214 if upgrade_introduces_new_init::<E>(current_package_id, &pkg.modules_with_init, store)?
215 {
216 validate_package_init_linkage(resolved_linkage, "upgrade")
217 } else {
218 Ok(())
219 }
220 }
221 Command::Publish(PackagePayload::Serialized(_), ..) => {
222 invariant_violation!("Unexpected serialized package payload in linkage analysis")
223 }
224 Command::Upgrade(PackagePayload::Serialized(_), ..) => {
225 invariant_violation!("Unexpected serialized package payload in linkage analysis")
226 }
227 Command::MoveCall(_)
228 | Command::MakeMoveVec(_, _)
229 | Command::TransferObjects(_, _)
230 | Command::SplitCoins(_, _)
231 | Command::MergeCoins(_, _) => Ok(()),
232 }
233}
234
235fn analyze_command<E: ExecutionErrorTrait>(
238 command: &Command,
239 resolution_table: &mut ResolutionTable,
240 store: &VerifiedPackageStore<'_>,
241 protocol_config: &ProtocolConfig,
242) -> Result<(), E> {
243 match command {
244 Command::MoveCall(move_call) => {
245 add_call_to_table::<E>(resolution_table, &move_call.function, store)?;
246 }
247 Command::Publish(PackagePayload::Serialized(_), ..) => {
248 invariant_violation!("Unexpected serialized package payload in linkage analysis")
249 }
250 Command::Publish(PackagePayload::Deserialized(pkg), _, resolved_linkage) => {
251 if pkg.has_potential_init() {
258 for resolved in resolved_linkage.linkage.values() {
259 add_and_unify(resolved, store, resolution_table, VersionConstraint::exact)?;
260 }
261 }
262 }
263 Command::Upgrade(_, _, _, _, _)
264 if protocol_config.enable_order_independent_upgrade_init_linkage() => {}
265 Command::Upgrade(payload, _, current_package_id, _, resolved_linkage) => {
266 analyze_upgrade_command::<E>(
267 payload,
268 current_package_id,
269 resolved_linkage,
270 resolution_table,
271 store,
272 protocol_config,
273 )?;
274 }
275 Command::MakeMoveVec(Some(ty), _) => {
276 add_type_packages::<E>(resolution_table, std::iter::once(ty), store)?;
277 }
278 Command::MakeMoveVec(None, _) => (),
279 Command::TransferObjects(_, _) | Command::SplitCoins(_, _) | Command::MergeCoins(_, _) => {}
280 };
281 Ok(())
282}
283
284fn analyze_upgrade_command<E: ExecutionErrorTrait>(
286 payload: &PackagePayload,
287 current_package_id: &ObjectID,
288 resolved_linkage: &ResolvedLinkage,
289 resolution_table: &mut ResolutionTable,
290 store: &VerifiedPackageStore<'_>,
291 protocol_config: &ProtocolConfig,
292) -> Result<(), E> {
293 if !protocol_config.enable_init_on_upgrade() {
294 return Ok(());
295 }
296
297 let current_pkg = get_package(current_package_id, store)?;
298
299 assert_invariant!(
300 protocol_config.enable_unified_linkage(),
301 "Unified linkage must be enabled before init on upgrade is supported"
302 );
303
304 let upgrade_modules_with_init = match payload {
305 PackagePayload::Serialized(_) => {
306 invariant_violation!("Unexpected serialized package payload in linkage analysis")
307 }
308 PackagePayload::Deserialized(DeserializedPackage {
309 modules_with_init, ..
310 }) => modules_with_init,
311 };
312
313 let current_module_inits = current_pkg
315 .modules()
316 .iter()
317 .map(|(module_id, module)| {
318 (
319 module_id.name().as_str(),
320 module_has_init(module.compiled_module()),
321 )
322 })
323 .collect::<BTreeMap<_, _>>();
324
325 reject_existing_module_added_init::<E>(¤t_module_inits, upgrade_modules_with_init)?;
327
328 if has_new_module_init(
330 current_module_inits.keys().copied().collect(),
331 upgrade_modules_with_init,
332 ) {
333 add_upgrade_init_linkage_to_table::<E>(
334 resolution_table,
335 current_package_id,
336 resolved_linkage,
337 store,
338 protocol_config,
339 )?;
340 }
341
342 Ok(())
343}
344
345fn reject_existing_module_added_init<E: ExecutionErrorTrait>(
349 current_module_inits: &BTreeMap<&str, bool>,
350 upgrade_modules_with_init: &BTreeSet<Identifier>,
351) -> Result<(), E> {
352 for module_name in upgrade_modules_with_init {
353 if current_module_inits.get(module_name.as_str()) == Some(&false) {
354 return Err(<E>::from_kind(ExecutionErrorKind::PackageUpgradeError {
355 upgrade_error: PackageUpgradeError::IncompatibleUpgrade,
356 }));
357 }
358 }
359 Ok(())
360}
361
362fn has_new_module_init(
365 current_module_names: BTreeSet<&str>,
366 upgrade_modules_with_init: &BTreeSet<Identifier>,
367) -> bool {
368 upgrade_modules_with_init
369 .iter()
370 .any(|module_name| !current_module_names.contains(module_name.as_str()))
371}
372
373pub(crate) fn upgrade_introduces_new_init<E: ExecutionErrorTrait>(
376 current_package_id: &ObjectID,
377 upgrade_modules_with_init: &BTreeSet<Identifier>,
378 store: &VerifiedPackageStore<'_>,
379) -> Result<bool, E> {
380 let current_pkg = get_package(current_package_id, store)?;
381 Ok(has_new_module_init(
382 current_pkg
383 .modules()
384 .keys()
385 .map(|module_id| module_id.name().as_str())
386 .collect(),
387 upgrade_modules_with_init,
388 ))
389}
390
391fn add_upgrade_init_linkage_to_table<E: ExecutionErrorTrait>(
402 resolution_table: &mut ResolutionTable,
403 current_package_id: &ObjectID,
404 resolved_linkage: &ResolvedLinkage,
405 store: &VerifiedPackageStore<'_>,
406 protocol_config: &ProtocolConfig,
407) -> Result<(), E> {
408 let current_pkg = get_package(current_package_id, store)?;
409 let pkg_original_id: ObjectID = current_pkg.original_id().into();
410
411 if !resolution_table
412 .resolution_table
413 .contains_key(&pkg_original_id)
414 {
415 for resolved in resolved_linkage.linkage.values() {
416 add_and_unify(resolved, store, resolution_table, VersionConstraint::exact)?;
417 }
418 return Ok(());
419 }
420
421 for (original_id, version_id) in &resolved_linkage.linkage {
422 match resolution_table.resolution_table.get(original_id) {
423 None => {
424 add_and_unify(
425 version_id,
426 store,
427 resolution_table,
428 VersionConstraint::exact,
429 )?;
430 }
431 Some(existing) if existing.object_id() == *version_id => {
432 if protocol_config.harden_linkage_consistency() {
433 add_and_unify(
434 version_id,
435 store,
436 resolution_table,
437 VersionConstraint::exact,
438 )?;
439 }
440 }
441 Some(existing) => {
442 return Err(E::new_with_source(
443 ExecutionErrorKind::InvalidLinkage,
444 format!(
445 "upgrade init linkage conflicts with transaction linkage: package \
446 {original_id} resolves to {} in transaction linkage, but upgrade \
447 linkage requires {version_id}",
448 existing.object_id(),
449 ),
450 ));
451 }
452 }
453 }
454
455 Ok(())
456}
457
458fn add_call_to_table<E: ExecutionErrorTrait>(
466 resolution_table: &mut ResolutionTable,
467 function: &LoadedFunction,
468 store: &VerifiedPackageStore<'_>,
469) -> Result<(), E> {
470 let dep_resolution_fn = match function.visibility {
471 Visibility::Public => VersionConstraint::at_least,
472 Visibility::Private | Visibility::Friend => VersionConstraint::exact,
473 };
474 let package: ObjectID = (*function.version_mid.address()).into();
475 add_package::<E>(
476 &package,
477 store,
478 resolution_table,
479 VersionConstraint::exact,
480 dep_resolution_fn,
481 )?;
482 add_type_packages::<E>(resolution_table, function.type_arguments.iter(), store)
483}
484
485fn add_type_packages<'a, E: ExecutionErrorTrait>(
488 resolution_table: &mut ResolutionTable,
489 types: impl IntoIterator<Item = &'a Type>,
490 store: &VerifiedPackageStore<'_>,
491) -> Result<(), E> {
492 for type_defining_id in types.into_iter().flat_map(|ty| ty.all_addresses()) {
493 add_package::<E>(
494 &ObjectID::from(type_defining_id),
495 store,
496 resolution_table,
497 VersionConstraint::at_least,
498 VersionConstraint::at_least,
499 )?;
500 }
501 Ok(())
502}
503
504fn add_package<E: ExecutionErrorTrait>(
508 object_id: &ObjectID,
509 store: &VerifiedPackageStore<'_>,
510 resolution_table: &mut ResolutionTable,
511 self_resolution_fn: fn(&Arc<VerifiedPackage>) -> Option<VersionConstraint>,
512 dep_resolution_fn: fn(&Arc<VerifiedPackage>) -> Option<VersionConstraint>,
513) -> Result<(), E> {
514 let pkg = get_package(object_id, store)?;
515 let transitive_deps = resolution_table
516 .config
517 .linkage_table(&pkg)
518 .into_values()
519 .map(ObjectID::from);
520 add_and_unify(object_id, store, resolution_table, self_resolution_fn)?;
521 for dep_id in transitive_deps {
522 add_and_unify(&dep_id, store, resolution_table, dep_resolution_fn)?;
523 }
524 Ok(())
525}
526
527fn write_back_linkage<E: ExecutionErrorTrait>(
530 command: &mut Command,
531 ptb_linkage: &ExecutableLinkage,
532) -> Result<(), E> {
533 match command {
534 Command::MoveCall(move_call) => {
535 let previous_linkage = &move_call.function.linkage;
536 assert_invariant!(
544 previous_linkage
545 .0
546 .linkage
547 .keys()
548 .all(|k| ptb_linkage.0.linkage.contains_key(k)),
549 "single linkage drops a package that the per-call linkage of MoveCall had resolved"
550 );
551 debug_assert!(
552 previous_linkage.0.linkage.len() <= ptb_linkage.0.linkage.len(),
553 "single linkage has fewer candidates than the per-call linkage of MoveCall"
554 );
555 move_call.function.linkage = ptb_linkage.clone();
556 }
557 Command::TransferObjects(_, _)
558 | Command::SplitCoins(_, _)
559 | Command::MergeCoins(_, _)
560 | Command::MakeMoveVec(_, _)
561 | Command::Publish(_, _, _)
562 | Command::Upgrade(_, _, _, _, _) => (),
563 };
564 Ok(())
565}