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

[GraphQL] Try to remove objectKeys #21032

Open
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

stefan-mysten
Copy link
Contributor

@stefan-mysten stefan-mysten commented Jan 31, 2025

Description

This PR removes the objectKeys field from ObjectFilter as per the previous deprecation notice. Use multiGetObjects instead.

Test plan

Updated tests accordingly and ensured all existing tests pass.


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: Removed the objectKeys field from ObjectFilter as per the previous deprecation notice. Use multiGetObjects instead to fetch multiple objects.
  • CLI:
  • Rust SDK:

Copy link

vercel bot commented Jan 31, 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 Feb 12, 2025 1:03am
2 Skipped Deployments
Name Status Preview Comments Updated (UTC)
multisig-toolkit ⬜️ Ignored (Inspect) Visit Preview Feb 12, 2025 1:03am
sui-kiosk ⬜️ Ignored (Inspect) Visit Preview Feb 12, 2025 1:03am

@stefan-mysten stefan-mysten temporarily deployed to sui-typescript-aws-kms-test-env January 31, 2025 06:12 — with GitHub Actions Inactive
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.

I think you're mostly there, but can just delete some more stuff...

@@ -71,7 +71,6 @@ impl GasInput {
let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;

let filter = ObjectFilter {
Copy link
Contributor

Choose a reason for hiding this comment

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

This will need to be replaced with a call to multi-get (hopefully a test failed because of this 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.

Maybe in e2e tests, will have to double check.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

Copy link
Contributor

Choose a reason for hiding this comment

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

This fix is not correct as indicated by the E2E tests above -- you are now fetching the latest version of each gas payment object rather than the version specified by the input. As mentioned previously, please use the multi-get API you just introduced.

Comment on lines 1609 to 1610
// If both object IDs and object keys are specified, then we need to query in
// both historical and consistent views, and then union the results.
Copy link
Contributor

Choose a reason for hiding this comment

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

Judging by this comment, this whole first branch should be removed, because it only applies if both object keys and object IDs are provided, which cannot happen any longer.

In fact this whole function becomes:

build_objects_query(
    if !filter.has_filters() {
        View::Historical
    } else {
        View::Consistent
    },
    range,
    page,
    move |query| filter.apply(query),
    move |newer| newer,
)

I was initially confused why we use this "Historical" view if there are no filters. I think I've figured out why, but generally it's pretty confusing, so let me write it out, and then @wlmyng and @emmazzz can correct me:

  • View::Historical makes it so that after we find candidate objects that meet our filter criteria, we don't look for newer versions of those objects (which would cause us to discard the candidate).
  • Previously, when we supported ID filters, key filters and pagination, we had the following semantics:
    • If you supply object keys and other filters, you are asking: "Find these versions of these objects, and tell me if they meet this criteria".
    • If you supply object IDs and other filters, you are asking: "Find the latest versions of these objects, and then tell me if they meet this criteria".
    • If you supply both IDs and Keys, you are really doing two distinct queries.
  • This should hopefully make it clear why we don't discard objects that meet our criteria if we supply keys (we were interested in those specific versions to begin with).
  • The reason we also do this when we don't supply any filter is because then we know that there can't be any newer objects (because we didn't apply any filter, the first query will just always find the latest objects). Could you add this explanation to the function's doc comment?

TL;DR -- I'm looking forward to separating all multi-get APIs from all query APIs.

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 don't really follow as I don't know how the objects query is constructed.

@wlmyng could you verify this logic + let me know what I should update the doc comment to?

crates/sui-graphql-rpc/src/types/object.rs Show resolved Hide resolved
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.

Could you take another look at the changes to gas payment and filter intersection? They seem incorrect to me.

Copy link
Contributor

Choose a reason for hiding this comment

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

nit: these are no longer related to pagination -- it's probably worth putting them in their own test for multi_get?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved!

@@ -118,20 +118,18 @@ module Test::M1 {

//# run-graphql
{
objects_at_version: address(address: "@{A}") {
Copy link
Contributor

Choose a reason for hiding this comment

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

Previously, this query also imposed an ownership constraint -- was that relevant for the test? It seems like from the results, version 3 didn't used to exist in the output and now it does.

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 don't follow what is an ownership constraint.

Copy link
Contributor

Choose a reason for hiding this comment

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

The outer query on address(...) means that the inner query only returns objects owned by that address.

Copy link
Contributor

Choose a reason for hiding this comment

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

The fact that these results have changed is concerning -- before, the numbers were all different, and now they are not. Seems related to the changes to how gas payment lookups work.

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 fixed the code to call Object::query_many which takes the Vec<ObjectKey> and checkpoint_viewed_at, but the result is the same. Something fishy.

@@ -71,7 +71,6 @@ impl GasInput {
let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;

let filter = ObjectFilter {
Copy link
Contributor

Choose a reason for hiding this comment

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

This fix is not correct as indicated by the E2E tests above -- you are now fetching the latest version of each gas payment object rather than the version specified by the input. As mentioned previously, please use the multi-get API you just introduced.

.map(|key| PointLookupKey {
id: key.object_id,
version: key.version.into(),
// Keep track of input keys' indices to ensure same output object order
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be done more efficiently -- data is already a HashMap, so you should be able to construct your desired results by iterating over keys, creating point lookup keys, and then fetching them from that map.

let keys: Vec<_> = keys
    .into_iter()
    .map(|key| PointLookupKey {
        id: key.object_id,
        version: key.version.into(),
    })
    .collect();

let data = loader.load_many(keys.clone()).await;
Ok(keys.into_iter().map(|k| data.get(&k).cloned()).collect())

Writing this also made me realise that if ordering is relevant, then if we don't find an object key, then we should probably return null for that (so that other results line up). The snippet above does that, but it needs to be propagated to the actual implementation, schema, docs and tests.

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 this but it looks like the gas related numbers in E2E tests stayed the same, which is strange.

.flatten()
.filter_map(|(id, v)| v.is_none().then_some(*id))
.collect();
if let (Some(obj_ids), Some(other_obj_ids)) = (&self.object_ids, &other.object_ids) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We are missing a couple of cases here:

  • If only one of the filters have object ids, then I think we should be preserving those IDs in the intersection. I suspect using the intersect::field helper would achieve that.
  • If the resulting intersection is empty, then we need to return None from this function to indicate the fact that we know in advance that the result is inconsistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Think I fixed this.

crates/sui-graphql-rpc/src/types/object.rs Show resolved Hide resolved
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