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