diff --git a/sui-execution/latest/sui-adapter/src/package_unification.rs b/sui-execution/latest/sui-adapter/src/package_unification.rs index 8904c1fda6806..678227119953b 100644 --- a/sui-execution/latest/sui-adapter/src/package_unification.rs +++ b/sui-execution/latest/sui-adapter/src/package_unification.rs @@ -11,11 +11,8 @@ use sui_types::{ }; pub struct PTBLinkageMetadata { - pub types: Vec, - pub entry_functions: Vec, - pub non_entry_functions: Vec, - pub publication_linkages: Vec, - pub upgrading_packages: Vec, + pub config: LinkageConfig, + pub unification_table: BTreeMap, pub all_packages: BTreeMap, } @@ -45,25 +42,57 @@ impl LinkageConfig { fix_types: false, } } + + pub fn generate_top_level_fn_constraint( + &self, + ) -> for<'a> fn(&'a MovePackage) -> ConflictResolution { + if self.fix_top_level_functions { + exact_constraint + } else { + at_least_constraint + } + } + + pub fn generate_type_constraint(&self) -> for<'a> fn(&'a MovePackage) -> ConflictResolution { + if self.fix_types { + exact_constraint + } else { + at_least_constraint + } + } +} + +pub fn exact_constraint<'a>(pkg: &MovePackage) -> ConflictResolution { + ConflictResolution::Exact(pkg.version(), pkg.id()) +} + +pub fn at_least_constraint<'a>(pkg: &MovePackage) -> ConflictResolution { + ConflictResolution::AtLeast(pkg.version(), pkg.id()) +} + +pub fn never_constraint<'a>(pkg: &MovePackage) -> ConflictResolution { + ConflictResolution::Never(pkg.id()) } impl ConflictResolution { - pub fn unify(&mut self, other: &ConflictResolution) -> anyhow::Result<()> { + pub fn unify(&self, other: &ConflictResolution) -> anyhow::Result { match (&self, other) { // If we ever try to unify with a Never we fail. (ConflictResolution::Never(_), _) | (_, ConflictResolution::Never(_)) => { return Err(anyhow::anyhow!( - "Cannot unify with Never: {:?} and {:?}", + "UNIFICATION ERROR: Cannot unify with Never: {:?} and {:?}", self, other )); } // If we have two exact resolutions, they must be the same. (ConflictResolution::Exact(sv, self_id), ConflictResolution::Exact(ov, other_id)) => { - if self_id != other_id { + if self_id != other_id || sv != ov { return Err(anyhow::anyhow!( - "Exact/exact conflicting resolutions for packages: {self_id}@{sv} and {other_id}@{ov}", + "UNIFICATION ERROR: Exact/exact conflicting resolutions for packages: {self_id}@{sv} and {other_id}@{ov}", )); + } else { + Ok(ConflictResolution::Exact(*sv, *self_id)) } } // Take the max if you have two at least resolutions. @@ -77,29 +106,30 @@ impl ConflictResolution { *oid }; - *self = ConflictResolution::AtLeast(*self_version.max(other_version), id); + Ok(ConflictResolution::AtLeast( + *self_version.max(other_version), + id, + )) } // If you unify an exact and an at least, the exact must be greater than or equal to // the at least. It unifies to an exact. ( - ConflictResolution::Exact(exact_version, self_id), - ConflictResolution::AtLeast(at_least_version, oid), + ConflictResolution::Exact(exact_version, exact_id), + ConflictResolution::AtLeast(at_least_version, at_least_id), ) | ( - ConflictResolution::AtLeast(at_least_version, oid), - ConflictResolution::Exact(exact_version, self_id), + ConflictResolution::AtLeast(at_least_version, at_least_id), + ConflictResolution::Exact(exact_version, exact_id), ) => { if exact_version < at_least_version { return Err(anyhow::anyhow!( - "Exact/at least Conflicting resolutions for packages: Exact {self_id}@{exact_version} and {oid}@{at_least_version}", + "UNIFICATION ERROR: Exact/at least Conflicting resolutions for packages: Exact {exact_id}@{exact_version} and {at_least_id}@{at_least_version}", )); } - *self = ConflictResolution::Exact(*exact_version, *self_id); + Ok(ConflictResolution::Exact(*exact_version, *exact_id)) } } - - Ok(()) } } @@ -107,14 +137,12 @@ impl PTBLinkageMetadata { pub fn from_ptb( ptb: &ProgrammableTransaction, store: &dyn BackingPackageStore, + config: LinkageConfig, ) -> anyhow::Result { let mut linkage = PTBLinkageMetadata { - types: Vec::new(), - entry_functions: Vec::new(), - non_entry_functions: Vec::new(), - publication_linkages: Vec::new(), - upgrading_packages: Vec::new(), + unification_table: BTreeMap::new(), all_packages: BTreeMap::new(), + config, }; for command in &ptb.commands { @@ -164,20 +192,29 @@ impl PTBLinkageMetadata { .map(|info| info.upgraded_id) .collect::>(); - // load transitive deps - for id in transitive_deps { - self.get_package(&id, store)?; + for ty in &programmable_move_call.type_arguments { + self.add_type(ty, store)?; } // Register function entrypoint if function.is_entry && function.visibility != Visibility::Public { - self.entry_functions.push(pkg_id); - } else { - self.non_entry_functions.push(pkg_id); - } + self.add_and_unify(&pkg_id, store, exact_constraint)?; - for ty in &programmable_move_call.type_arguments { - self.add_type(ty, store)?; + // transitive closure of entry functions are fixed + for object_id in transitive_deps.iter() { + self.add_and_unify(object_id, store, exact_constraint)?; + } + } else { + self.add_and_unify( + &pkg_id, + store, + self.config.generate_top_level_fn_constraint(), + )?; + + // transitive closure of non-entry functions are at-least + for object_id in transitive_deps.iter() { + self.add_and_unify(object_id, store, at_least_constraint)?; + } } } Command::MakeMoveVec(type_input, _) => { @@ -186,18 +223,15 @@ impl PTBLinkageMetadata { } } Command::Upgrade(_, transitive_deps, upgrading_object_id, _) => { - self.upgrading_packages.push(*upgrading_object_id); - self.get_package(upgrading_object_id, store)?; + self.add_and_unify(upgrading_object_id, store, never_constraint)?; - self.publication_linkages.extend_from_slice(transitive_deps); for object_id in transitive_deps { - self.get_package(object_id, store)?; + self.add_and_unify(object_id, store, exact_constraint)?; } } Command::Publish(_, transitive_deps) => { - self.publication_linkages.extend_from_slice(transitive_deps); for object_id in transitive_deps { - self.get_package(object_id, store)?; + self.add_and_unify(object_id, store, exact_constraint)?; } } Command::TransferObjects(_, _) => (), @@ -225,10 +259,11 @@ impl PTBLinkageMetadata { stack.push(&**type_input); } TypeInput::Struct(struct_input) => { - let pkg = self - .get_package(&ObjectID::from(struct_input.address), store)? - .id(); - self.types.push(pkg); + self.add_and_unify( + &ObjectID::from(struct_input.address), + store, + self.config.generate_type_constraint(), + )?; for ty in struct_input.type_params.iter() { stack.push(ty); } @@ -238,8 +273,6 @@ impl PTBLinkageMetadata { Ok(()) } - // Gather and dedup all packages loaded by the PTB. - // Also gathers the versions of each package loaded by the PTB at the same time. fn get_package( &mut self, object_id: &ObjectID, @@ -259,119 +292,28 @@ impl PTBLinkageMetadata { .get(object_id) .expect("Guaranteed to exist")) } -} - -impl PTBLinkageMetadata { - pub fn try_compute_unified_linkage( - &self, - linking_config: LinkageConfig, - ) -> anyhow::Result> { - let mut unification_table = BTreeMap::new(); - - // Any packages that are being upgraded cannot be called in the transaction - for object_id in self.upgrading_packages.iter() { - Self::add_and_unify( - &mut unification_table, - &self.all_packages, - object_id, - |pkg| ConflictResolution::Never(pkg.id()), - )?; - } - - // Linkages for packages that are to be published must be exact. - for object_id in self.publication_linkages.iter() { - Self::add_and_unify( - &mut unification_table, - &self.all_packages, - object_id, - |pkg| ConflictResolution::Exact(pkg.version(), pkg.id()), - )?; - } - - for object_id in self.entry_functions.iter() { - Self::add_and_unify( - &mut unification_table, - &self.all_packages, - object_id, - |pkg| ConflictResolution::Exact(pkg.version(), pkg.id()), - )?; - - // transitive closure of entry functions are fixed - for dep_id in self.all_packages[object_id] - .linkage_table() - .values() - .map(|info| &info.upgraded_id) - { - Self::add_and_unify(&mut unification_table, &self.all_packages, dep_id, |pkg| { - ConflictResolution::Exact(pkg.version(), pkg.id()) - })?; - } - } - - // Types can be fixed or not depending on config. - for object_id in self.types.iter() { - Self::add_and_unify( - &mut unification_table, - &self.all_packages, - object_id, - |pkg| { - if linking_config.fix_types { - ConflictResolution::Exact(pkg.version(), pkg.id()) - } else { - ConflictResolution::AtLeast(pkg.version(), *object_id) - } - }, - )?; - } - - // Top level functions can be fixed or not depending on config. But they won't ever - // transitively fix their dependencies. - for object_id in self.non_entry_functions.iter() { - Self::add_and_unify( - &mut unification_table, - &self.all_packages, - object_id, - |pkg| { - if linking_config.fix_top_level_functions { - ConflictResolution::Exact(pkg.version(), pkg.id()) - } else { - ConflictResolution::AtLeast(pkg.version(), *object_id) - } - }, - )?; - } - - Ok(unification_table - .into_values() - .flat_map(|unifier| match unifier { - ConflictResolution::Exact(_, object_id) => Some(object_id), - ConflictResolution::AtLeast(_, object_id) => Some(object_id), - ConflictResolution::Never(_) => None, - }) - .collect()) - } // Add a package to the unification table, unifying it with any existing package in the table. // Errors if the packages cannot be unified (e.g., if one is exact and the other is not). fn add_and_unify( - unification_table: &mut BTreeMap, - loaded_top_level_packages: &BTreeMap, + &mut self, object_id: &ObjectID, - resolution_fn: impl Fn(&MovePackage) -> ConflictResolution, + store: &dyn BackingPackageStore, + resolution_fn: fn(&MovePackage) -> ConflictResolution, ) -> anyhow::Result<()> { - let package = loaded_top_level_packages - .get(object_id) - .ok_or_else(|| anyhow::anyhow!("Object {} not found in any package", object_id))?; + let package = self.get_package(object_id, store)?; let resolution = resolution_fn(package); + let original_pkg_id = package.original_package_id(); - if unification_table.contains_key(&package.original_package_id()) { - let existing_unifier = unification_table - .get_mut(&package.original_package_id()) + if self.unification_table.contains_key(&original_pkg_id) { + let existing_unifier = self + .unification_table + .get_mut(&original_pkg_id) .expect("Guaranteed to exist"); - existing_unifier.unify(&resolution)?; + *existing_unifier = existing_unifier.unify(&resolution)?; } else { - unification_table.insert(package.original_package_id(), resolution); + self.unification_table.insert(original_pkg_id, resolution); } Ok(())