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] Add a new multiGetObjects query on Query. #20300

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

stefan-mysten
Copy link
Contributor

@stefan-mysten stefan-mysten commented Nov 18, 2024

Description

This PR adds a new query for fetching multiple objects by their ids and versions.

Test plan

How did you test the new or updated feature?


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):
  • Indexer:
  • JSON-RPC:
  • GraphQL:
  • CLI:
  • Rust SDK:
  • REST API:

Copy link

vercel bot commented Nov 18, 2024

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 Nov 19, 2024 4:57am
3 Skipped Deployments
Name Status Preview Comments Updated (UTC)
multisig-toolkit ⬜️ Ignored (Inspect) Visit Preview Nov 19, 2024 4:57am
sui-kiosk ⬜️ Ignored (Inspect) Visit Preview Nov 19, 2024 4:57am
sui-typescript-docs ⬜️ Ignored (Inspect) Visit Preview Nov 19, 2024 4:57am

@stefan-mysten stefan-mysten temporarily deployed to sui-typescript-aws-kms-test-env November 18, 2024 03:09 — with GitHub Actions Inactive
let limits = self.reporter.limits;
let mut stack = vec![&value.node];
while let Some(value) = stack.pop() {
match value {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Need to handle V::Variable

@@ -2882,7 +2882,7 @@ input ObjectFilter {
"""
Filter for live or potentially historical objects by their ID and version.
"""
objectKeys: [ObjectKey!]
objectKeys: [ObjectKey!] @deprecated(reason: "Use multiGetObjects instead. This field will be removed in a future release.")
Copy link
Contributor

Choose a reason for hiding this comment

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

does this need to be updated again since you mentioned async-graphql doesnt work with this directive?

Copy link
Contributor

Choose a reason for hiding this comment

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

circling back, then we're losing the ability to select objects that match additional criteria among a list of objects (and optional versions)

crates/sui-graphql-rpc/src/config.rs Show resolved Hide resolved
Comment on lines 311 to 314
/// multiGet queries can only pass a number of keys that does not exceed the max page size.
/// This checks for the number of keys and deducts the raw size of the keys from the payload
/// size.
fn check_multiget_arg(
Copy link
Contributor

Choose a reason for hiding this comment

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

As multi-get functionality is achieved through connections, we do a pretty simple assumption that we will yield connection_page_size results, which is the default_page_size unless an override has been specified by the caller. I wonder if we could do something similar here, assume that a particular multi-get if invoked will yield the maximum allowed number of results?

Then we can do something similar, where in the resolver (such as TransactionBlock::paginate) we have the actual check for ids.len() > limits.max_multiget -> return Error

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How can I tell multi-get functionality is achieved through connections?

This query does not return a page, but returns the number of keys passed in, which cannot be larger than max page size.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry for the confusion, what I meant is that today to do multi-get you go through objects connection, which assumes that we'll yield default_page_size results unless this is explicitly overwritten with first or last. Wonder if we could simplify by making a similar assumption, if we see multiGet, then we defer to the default or max limit for that multiGet

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 see. But this function still needs to account for those input object keys and remove them from the query limit. So I am not sure what you're thinking of.

Copy link
Member

@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.

Requesting changes for the query limit checker stuff mainly!

@@ -2880,7 +2880,8 @@ input ObjectFilter {
"""
objectIds: [SuiAddress!]
"""
Filter for live or potentially historical objects by their ID and version.
Filter for live objects by their ID and version.
For historical versions, use multiGetObjects query instead.
Copy link
Member

Choose a reason for hiding this comment

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

We do want to deprecate this input, it doesn't make a ton of sense to query live objects by their ID and version. Add a deprecation notice here (it can just be in the doc comment if the @deprecated support isn't there in async-graphql) that mentions the following:

  • Why this is being deprecated (it doesn't mean what people think it means).
  • What to use instead.
  • When it's going away (earliest date and major version)

Check out other deprecation notices in the schema for examples:

"""
Limit to transactions that accepted the given object as an input. NOTE: this input filter
has been deprecated in favor of `affectedObject` which offers an easier to under behavior.
This filter will be removed with 1.36.0 (2024-10-14), or at least one release after
`affectedObject` is introduced, whichever is later.
"""
inputObject: SuiAddress
"""
Limit to transactions that output a versioon of this object. NOTE: this input filter has
been deprecated in favor of `affectedObject` which offers an easier to understand behavor.
This filter will be removed with 1.36.0 (2024-10-14), or at least one release after
`affectedObject` is introduced, whichever is later.
"""
changedObject: SuiAddress

@@ -3399,6 +3400,10 @@ type Query {
- `author` is the address of the signer of the transaction or personal msg.
"""
verifyZkloginSignature(bytes: Base64!, signature: Base64!, intentScope: ZkLoginIntentScope!, author: SuiAddress!): ZkLoginVerifyResult!
"""
Copy link
Member

Choose a reason for hiding this comment

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

nit: can you move the implementation of this up near the other accessor functions so it shows up in that order in the schema? Let's put multi-gets between the point lookups and the rich paginated queries (so at the moment, between fn transaction_block and fn coins.

Self {
max_query_depth: 20,
max_query_nodes: 300,
max_output_nodes: 100_000,
max_query_payload_size: 5_000,
max_db_query_cost: 20_000,
default_page_size: 20,
max_page_size: 50,
max_page_size,
max_multi_get_objects_keys: max_page_size, // keep the same as max_page_size
Copy link
Member

Choose a reason for hiding this comment

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

  • Let's not make this the same as max_page_size, the reason we factored it out into its own config is that it can/should be different -- we can start by setting this to 500 or something.
  • Let's move it next to max_transaction_ids below, because that's what it is semantically closest to.

@@ -67,6 +68,9 @@ struct LimitsTraversal<'a> {
input_budget: u32,
output_budget: u32,
depth_seen: u32,

// The total number of object keys in multiGetObjects query
num_obj_keys_seen: u32,
Copy link
Member

Choose a reason for hiding this comment

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

I haven't seen how this is used, but you probably don't need this.

Copy link
Member

Choose a reason for hiding this comment

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

(I was right, you don't need this).

match &item.node {
Selection::Field(f) => {
let name = &f.node.name.node;
if name.starts_with(MULTI_GET_OBJECTS) {
Copy link
Member

Choose a reason for hiding this comment

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

The idea was for support in the query limits checker to be fully generic, so that in future, we could add multi gets without making further changes here (so we wouldn't forget to add them). To achieve that we should look for names that start with "multiGet".

Comment on lines +505 to +506
// If the field being traversed is a multiGet query, multiplicity is the number of
// keys seen in the request.
Copy link
Member

Choose a reason for hiding this comment

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

No, it is the number of keys passed in the parameter to this multi-get query multiplied by the existing multiplicity (similar to how page size works).

Copy link
Member

Choose a reason for hiding this comment

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

Let's write a lot of tests for this:

  • For the potential panic introduced by the expect.
  • For how multiplicity should be handled for multi-gets.

If it's not clear what the limit check change is supposed to be doing, let's discuss during pair programming.

@@ -425,6 +529,10 @@ impl<'a> LimitsTraversal<'a> {
.ok_or_else(|| self.output_node_error())?
};

if multiplicity > self.output_budget {
Copy link
Member

Choose a reason for hiding this comment

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

This change should not be necessary

@@ -883,6 +884,40 @@ impl Object {
}
}

/// Fetch objects by their id and version. If you need to query for live objects, use the
Copy link
Member

Choose a reason for hiding this comment

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

nit: ordering, I think we put the query functions above the paginate functions usually.

Comment on lines +627 to +633
if object_keys.len() > cfg.limits.max_multi_get_objects_keys as usize {
return Err(Error::Client(format!(
"Number of keys exceeds max limit of '{}'",
cfg.limits.max_multi_get_objects_keys
))
.into());
}
Copy link
Member

Choose a reason for hiding this comment

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

this check is being duplicated here and in the query limits check. I think it's fine/good to have it done here -- that mirrors what we do for pages, but let's keep it down to one place where we do the check.

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.

3 participants