Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[cli] New alg for tree shaking #21356

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

stefan-mysten
Copy link
Contributor

@stefan-mysten stefan-mysten commented Feb 26, 2025

Description

This PR updates the tree shaking algorithm to use linkage tables on-chain. We need to use that because implicit dependencies feature that is being worked in #21204 would not work with this algorithm. This is due to the way transitive dependencies are computed in the previous tree shaking algorithm. Implicit dependencies will add those system dependencies to the transitive dependencies, thus system packages will get included in the final list of dependencies in the package that is to be published/upgraded after tree shaking is complete.

Test plan

Existing tests. Updated a test for system packages.

cd crates/sui
cargo nextest run -- cli_tests

Release notes

Check each box that your changes affect. If none of the boxes relate to your changes, release notes aren't required.

For each box you select, include information after the relevant heading that describes the impact of your changes that a user might notice and any actions they must take to implement updates.

  • Protocol:
  • Nodes (Validators and Full nodes):
  • gRPC:
  • JSON-RPC:
  • GraphQL:
  • CLI:
  • Rust SDK:

@stefan-mysten stefan-mysten self-assigned this Feb 26, 2025
Copy link

vercel bot commented Feb 26, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
sui-docs ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 6, 2025 0:44am
2 Skipped Deployments
Name Status Preview Comments Updated (UTC)
multisig-toolkit ⬜️ Ignored (Inspect) Visit Preview Mar 6, 2025 0:44am
sui-kiosk ⬜️ Ignored (Inspect) Visit Preview Mar 6, 2025 0:44am

Copy link
Contributor

@amnn amnn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes around sui move build look good to me, but the new algorithm does not seem quite right. I have suggested a fix and a test that would highlight the issue.

Comment on lines 3160 to 3202
compiled_package
.dependency_ids
.published
.retain(|pkg_name, id| {
linkage_table_ids.contains(id)
|| pkgs.contains_key(pkg_name)
|| pkg_name_to_orig_id
.get(pkg_name)
.is_some_and(|orig_id| pkg_ids.contains(orig_id))
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not seem right:

  • compiled_package.dependency_ids.published is a list of storage IDs (they point to the actual version of the package that you want to use)
  • similarly, pkg_ids is also a list of storage IDs, because it was generated by filtering the same list.
  • linkage_table_ids is a list of original IDs (they point to the first version of the package you want to use, regardless of its version), because that is what the key in the linkage table is.
  • values in pkg_name_to_orig_id are also original IDs.

In this code, we are freely comparing original IDs and storage IDs -- this should only work for packages that have never been upgraded -- have you tested this approach in cases involving upgraded packages? The following example should surface an issue if there is one:

  • B is published depending on C v1
  • A is published depending on B and C v2, but does not directly refer to anything in C v2 (the dependency just serves to upgrade B's dependency).

In that example, A's dependency list should include both B, which it does use, and C v2, which it doesn't use but is an upgrade of one of its immediate dependency's transitive dependencies.

The retention test goes something like this, I think:

compiled_package
    .dependency_ids
    .published
    .retain(|pkg_name, id| {
        pkgs.contains_key(pkg_name)
            || pkg_name_to_orig_id
                .get(pkg_name)
                .is_some_and(|orig_id| linkage_table_original_ids.contains(orig_id))
    })

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this test and fixed this part, I think, but I cannot get the test to work properly. It's working when following the same steps with a CLI build. I think the issue is around publishing without tree shaking using old test-transaction-builder, which does not create a Move.lock file.

Need to think a bit what to do.

Copy link
Contributor Author

@stefan-mysten stefan-mysten Mar 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so here's the new test for it
K, K_v2, L_depends_on_K (with code reference to K), and M_depends_on_L_and_K_v2, with code calling function from L_depends_on_K package, but no code reference to K_v2.

All should work, but I have an intuition that all this breaks if there are no Move.lock files and only Move.toml with published-at field. I need to understand how things worked before automated address mgmt.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the test that uses the "old" way of publishing/upgrading, without automated address management, just for my sanity check.

@stefan-mysten stefan-mysten force-pushed the tree_shaking_with_linkage_table branch from 07c4799 to ef77a66 Compare March 6, 2025 00:43
@stefan-mysten stefan-mysten temporarily deployed to sui-typescript-aws-kms-test-env March 6, 2025 00:43 — with GitHub Actions Inactive
mdgeorge4153 added a commit that referenced this pull request Mar 8, 2025
…#21383)

## Description 

This PR adds support for implicit dependencies to the package management at the move layer.

## PR stack

#21204 Add implicit Sui dependencies
#21384 Refactors to BuildConfig
#21383 Add support for implicits to move package management

This also depends on @stefan-mysten 's ongoing tree-shaking work in
#21356


## Test plan 

See `test_sources/implicits/README.md` for a description of the new
snapshot tests

---

## Release notes

Check each box that your changes affect. If none of the boxes relate to
your changes, release notes aren't required.

For each box you select, include information after the relevant heading
that describes the impact of your changes that a user might notice and
any actions they must take to implement updates.

- [ ] Protocol: 
- [ ] Nodes (Validators and Full nodes): 
- [ ] gRPC:
- [ ] JSON-RPC: 
- [ ] GraphQL: 
- [ ] CLI: 
- [ ] Rust SDK:
mdgeorge4153 added a commit that referenced this pull request Mar 9, 2025
## Description 

This removes `sui_move_build::BuildConfig::default` and makes other small changes

## PR stack

#21204 Add implicit Sui dependencies
#21384 Refactors to BuildConfig
#21383 Add support for implicits to move package management

This also depends on @stefan-mysten 's ongoing tree-shaking work in
#21356

## Test plan 

This disables two broken bytecode verification tests. See DVX-786

---

## Release notes

Check each box that your changes affect. If none of the boxes relate to
your changes, release notes aren't required.

For each box you select, include information after the relevant heading
that describes the impact of your changes that a user might notice and
any actions they must take to implement updates.

- [ ] Protocol: 
- [ ] Nodes (Validators and Full nodes): 
- [ ] gRPC:
- [ ] JSON-RPC: 
- [ ] GraphQL: 
- [ ] CLI: 
- [ ] Rust SDK:
Copy link
Contributor

@amnn amnn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are still issues with how this is written, PTAL at the inline comments.

pkg_id_to_name.get(&object_id).unwrap()
)
}
_ => anyhow!("Cannot convert data into an object: {e}"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a call to context in disguise.

Comment on lines +3160 to +3161
p.to_move_package(u64::MAX /* safe as this pkg comes from the network */)
.map_err(|e| anyhow!(e))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this work?

Suggested change
p.to_move_package(u64::MAX /* safe as this pkg comes from the network */)
.map_err(|e| anyhow!(e))
p.to_move_package(u64::MAX /* safe as this pkg comes from the network */)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately no, because p_to_move_package returns a sui_types::ExecutionError and here we need to return an anyhow::Error.

Comment on lines +3202 to +3207
// orig id
for (name, id) in &pkg_name_to_orig_id {
if *linkage_orig_id == id {
pkgs_to_keep.insert(name);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is accidentally quadratic -- create a reverse mapping once to make this look-up efficient.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spurious change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, it's an intended change. I modified before the code to pass SuiClient rather than ReadApi, and now I reverted that to use ReadApi everywhere where's possible, so it comes from that refactoring. Sorry for the confusion.

.map(|(name, id)| (id, name))
.collect();
let immediate_dep_move_pkgs =
fetch_move_packages(read_api, &immediate_dep_pkgs_ids, &pkg_id_to_name).await?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to be a fair bit of massaging of data coming out of find_immediate_deps_pkgs_to_keep to make it work for fetch_move_packages. Does that need to happen here?

  • Could the data come out of find_immediate_deps_pkgs_to_keep in a more appropriate form for what it's about to be used for here?
  • Could any other processing happen inside fetch_move_packages?

Comment on lines +3192 to +3216
let immediate_dep_pkgs_linkage_tables: BTreeMap<_, _> = immediate_dep_move_pkgs
.iter()
.flat_map(|pkg| pkg.linkage_table())
.collect();

// for every immediate dep package, we need to use its linkage table to determine its
// transitive dependencies and ensure that we keep the required packages
let mut pkgs_to_keep: BTreeSet<&Symbol> = BTreeSet::new();
let published_pkgs = compiled_package.dependency_ids.published.clone();
for (linkage_orig_id, upgrade_info) in &immediate_dep_pkgs_linkage_tables {
// orig id
for (name, id) in &pkg_name_to_orig_id {
if *linkage_orig_id == id {
pkgs_to_keep.insert(name);
}
}

// for dependencies on pkgs that are upgrades, find the name of the published
// package by matching the upgraded package id with the published package id
for (name, id) in &published_pkgs {
if &upgrade_info.upgraded_id == id {
pkgs_to_keep.insert(name);
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems suspicious for a couple of reasons:

  • immediate_dep_pkgs_linkage_tables saves all upgrade info, but if two immediate deps depend on the same transitive dep at different versions, then you will arbitrarily pick one.
  • In the upgraded package case, you are checking that the dependency exactly matches the upgraded ID of one of your dependency's dependencies.

These two details alone should set off alarm bells because what if you overwrote the version of the dependency that you meant to match with here? But the situation is worse than that, because the package being published is allowed to upgrade any of its dependency's dependencies as well, and this would not allow for that.

The correct check is to keep every published ID whose corresponding original ID shows up as a key in the linkage table of one of the immediate dependencies.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants