From 17621aa6490029d833153ed8e4f5689cee3c92e3 Mon Sep 17 00:00:00 2001 From: Ashok Menon Date: Mon, 19 Aug 2024 11:40:25 +0100 Subject: [PATCH] [GraphQL/MovePackage] Paginate by checkpoint (#17696) ## Description Adds a query, `Query.packages` for fetching all packages that were introduced within a given checkpoint range. Useful for fetching package contents in bulk, to do local analyses. ## Test plan New E2E tests: ``` sui$ cargo nextest run -p sui-graphql-e2e-tests \ --features pg_integration \ -- packages/versioning ``` Also tested for performance against a large read replica (the query planner quotes a high estimate for the query but the actual results do not take very long to run because queries on many sub-partitions are eliminated). ## Stack - #17686 - #17687 - #17688 - #17689 - #17691 - #17694 - #17695 - #17542 - #17726 - #17543 - #17692 - #17693 --- ## 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: - [x] GraphQL: Introduces `Query.packages` for paginating through all packages (optionally bounding by the checkpoint the package was introduced in). - [ ] CLI: - [ ] Rust SDK: --- .../tests/packages/versioning.exp | 204 ++++++++++++++++-- .../tests/packages/versioning.move | 54 +++++ .../schema/current_progress_schema.graphql | 24 +++ crates/sui-graphql-rpc/src/types/event.rs | 12 +- .../sui-graphql-rpc/src/types/move_package.rs | 203 ++++++++++++++++- crates/sui-graphql-rpc/src/types/query.rs | 24 ++- .../snapshot_tests__schema_sdl_export.snap | 24 +++ 7 files changed, 523 insertions(+), 22 deletions(-) diff --git a/crates/sui-graphql-e2e-tests/tests/packages/versioning.exp b/crates/sui-graphql-e2e-tests/tests/packages/versioning.exp index 7b8ad9c4c32cc4..7a0e6ec3faeed7 100644 --- a/crates/sui-graphql-e2e-tests/tests/packages/versioning.exp +++ b/crates/sui-graphql-e2e-tests/tests/packages/versioning.exp @@ -1,4 +1,4 @@ -processed 14 tasks +processed 15 tasks init: A: object(0,0) @@ -13,7 +13,7 @@ task 2, line 11: //# create-checkpoint Checkpoint created: 1 -task 3, lines 13-21: +task 3, lines 13-28: //# run-graphql Response: { "data": { @@ -28,21 +28,45 @@ Response: { ] } } + }, + "packages": { + "nodes": [ + { + "address": "0x0000000000000000000000000000000000000000000000000000000000000001", + "version": 1 + }, + { + "address": "0x0000000000000000000000000000000000000000000000000000000000000002", + "version": 1 + }, + { + "address": "0x0000000000000000000000000000000000000000000000000000000000000003", + "version": 1 + }, + { + "address": "0x000000000000000000000000000000000000000000000000000000000000dee9", + "version": 1 + }, + { + "address": "0x175ae86f2df1eb652d57fbe9e44c7f2d67870d2b6776a4356f30930221b63b88", + "version": 1 + } + ] } } } -task 4, lines 23-27: +task 4, lines 30-34: //# upgrade --package P0 --upgrade-capability 1,1 --sender A created: object(4,0) mutated: object(0,0), object(1,1) gas summary: computation_cost: 1000000, storage_cost: 5251600, storage_rebate: 2595780, non_refundable_storage_fee: 26220 -task 5, line 29: +task 5, line 36: //# create-checkpoint Checkpoint created: 2 -task 6, lines 31-39: +task 6, lines 38-53: //# run-graphql Response: { "data": { @@ -60,21 +84,49 @@ Response: { ] } } + }, + "packages": { + "nodes": [ + { + "address": "0x0000000000000000000000000000000000000000000000000000000000000001", + "version": 1 + }, + { + "address": "0x0000000000000000000000000000000000000000000000000000000000000002", + "version": 1 + }, + { + "address": "0x0000000000000000000000000000000000000000000000000000000000000003", + "version": 1 + }, + { + "address": "0x000000000000000000000000000000000000000000000000000000000000dee9", + "version": 1 + }, + { + "address": "0x175ae86f2df1eb652d57fbe9e44c7f2d67870d2b6776a4356f30930221b63b88", + "version": 1 + }, + { + "address": "0x351bc614b36f0f522a64334e4c278d4bfe200234958870c084e0a005f041d681", + "version": 2 + } + ] } } } -task 7, lines 41-46: +task 7, lines 55-60: //# upgrade --package P1 --upgrade-capability 1,1 --sender A created: object(7,0) mutated: object(0,0), object(1,1) gas summary: computation_cost: 1000000, storage_cost: 5426400, storage_rebate: 2595780, non_refundable_storage_fee: 26220 -task 8, line 48: +task 8, line 62: //# create-checkpoint Checkpoint created: 3 -task 9, lines 50-58: +task 9, lines 64-79: //# run-graphql Response: { "data": { @@ -95,11 +147,43 @@ Response: { ] } } + }, + "packages": { + "nodes": [ + { + "address": "0x0000000000000000000000000000000000000000000000000000000000000001", + "version": 1 + }, + { + "address": "0x0000000000000000000000000000000000000000000000000000000000000002", + "version": 1 + }, + { + "address": "0x0000000000000000000000000000000000000000000000000000000000000003", + "version": 1 + }, + { + "address": "0x000000000000000000000000000000000000000000000000000000000000dee9", + "version": 1 + }, + { + "address": "0x175ae86f2df1eb652d57fbe9e44c7f2d67870d2b6776a4356f30930221b63b88", + "version": 1 + }, + { + "address": "0x351bc614b36f0f522a64334e4c278d4bfe200234958870c084e0a005f041d681", + "version": 2 + }, + { + "address": "0x0eae57b7a07b0548b1f6b0c309f0692828ff994e9159b541334b25582980631c", + "version": 3 + } + ] } } } -task 10, lines 60-97: +task 10, lines 81-118: //# run-graphql Response: { "data": { @@ -199,7 +283,7 @@ Response: { } } -task 11, lines 99-136: +task 11, lines 120-157: //# run-graphql Response: { "data": { @@ -290,7 +374,7 @@ Response: { } } -task 12, lines 138-193: +task 12, lines 159-214: //# run-graphql Response: { "data": { @@ -429,7 +513,7 @@ Response: { } } -task 13, lines 195-223: +task 13, lines 216-244: //# run-graphql Response: { "data": { @@ -441,3 +525,99 @@ Response: { "v4": null } } + +task 14, lines 246-277: +//# run-graphql +Response: { + "data": { + "before": { + "nodes": [ + { + "address": "0x0000000000000000000000000000000000000000000000000000000000000001", + "version": 1, + "previousTransactionBlock": { + "effects": { + "checkpoint": { + "sequenceNumber": 0 + } + } + } + }, + { + "address": "0x0000000000000000000000000000000000000000000000000000000000000002", + "version": 1, + "previousTransactionBlock": { + "effects": { + "checkpoint": { + "sequenceNumber": 0 + } + } + } + }, + { + "address": "0x0000000000000000000000000000000000000000000000000000000000000003", + "version": 1, + "previousTransactionBlock": { + "effects": { + "checkpoint": { + "sequenceNumber": 0 + } + } + } + }, + { + "address": "0x000000000000000000000000000000000000000000000000000000000000dee9", + "version": 1, + "previousTransactionBlock": { + "effects": { + "checkpoint": { + "sequenceNumber": 0 + } + } + } + } + ] + }, + "after": { + "nodes": [ + { + "address": "0x351bc614b36f0f522a64334e4c278d4bfe200234958870c084e0a005f041d681", + "version": 2, + "previousTransactionBlock": { + "effects": { + "checkpoint": { + "sequenceNumber": 2 + } + } + } + }, + { + "address": "0x0eae57b7a07b0548b1f6b0c309f0692828ff994e9159b541334b25582980631c", + "version": 3, + "previousTransactionBlock": { + "effects": { + "checkpoint": { + "sequenceNumber": 3 + } + } + } + } + ] + }, + "between": { + "nodes": [ + { + "address": "0x351bc614b36f0f522a64334e4c278d4bfe200234958870c084e0a005f041d681", + "version": 2, + "previousTransactionBlock": { + "effects": { + "checkpoint": { + "sequenceNumber": 2 + } + } + } + } + ] + } + } +} diff --git a/crates/sui-graphql-e2e-tests/tests/packages/versioning.move b/crates/sui-graphql-e2e-tests/tests/packages/versioning.move index f4646294722b70..694072fb9c4455 100644 --- a/crates/sui-graphql-e2e-tests/tests/packages/versioning.move +++ b/crates/sui-graphql-e2e-tests/tests/packages/versioning.move @@ -18,6 +18,13 @@ module P0::m { functions { nodes { name } } } } + + packages(first: 10) { + nodes { + address + version + } + } } //# upgrade --package P0 --upgrade-capability 1,1 --sender A @@ -36,6 +43,13 @@ module P1::m { functions { nodes { name } } } } + + packages(first: 10) { + nodes { + address + version + } + } } //# upgrade --package P1 --upgrade-capability 1,1 --sender A @@ -55,6 +69,13 @@ module P2::m { functions { nodes { name } } } } + + packages(first: 10) { + nodes { + address + version + } + } } //# run-graphql @@ -221,3 +242,36 @@ module P2::m { } } } + +//# run-graphql +{ # Querying packages with checkpoint bounds + before: packages(first: 10, filter: { beforeCheckpoint: 1 }) { + nodes { + address + version + previousTransactionBlock { + effects { checkpoint { sequenceNumber } } + } + } + } + + after: packages(first: 10, filter: { afterCheckpoint: 1 }) { + nodes { + address + version + previousTransactionBlock { + effects { checkpoint { sequenceNumber } } + } + } + } + + between: packages(first: 10, filter: { afterCheckpoint: 1, beforeCheckpoint: 3 }) { + nodes { + address + version + previousTransactionBlock { + effects { checkpoint { sequenceNumber } } + } + } + } +} diff --git a/crates/sui-graphql-rpc/schema/current_progress_schema.graphql b/crates/sui-graphql-rpc/schema/current_progress_schema.graphql index 69472d05780455..95fca9ce535b86 100644 --- a/crates/sui-graphql-rpc/schema/current_progress_schema.graphql +++ b/crates/sui-graphql-rpc/schema/current_progress_schema.graphql @@ -2201,6 +2201,22 @@ type MovePackage implements IObject & IOwner { moduleBcs: Base64 } +""" +Filter for paginating `MovePackage`s that were created within a range of checkpoints. +""" +input MovePackageCheckpointFilter { + """ + Fetch packages that were published strictly after this checkpoint. Omitting this fetches + packages published since genesis. + """ + afterCheckpoint: UInt53 + """ + Fetch packages that were published strictly before this checkpoint. Omitting this fetches + packages published up to the latest checkpoint (inclusive). + """ + beforeCheckpoint: UInt53 +} + type MovePackageConnection { """ Information to aid in pagination. @@ -3115,6 +3131,14 @@ type Query { """ objects(first: Int, after: String, last: Int, before: String, filter: ObjectFilter): ObjectConnection! """ + The Move packages that exist in the network, optionally filtered to be strictly before + `beforeCheckpoint` and/or strictly after `afterCheckpoint`. + + This query will return all versions of a given user package that appear between the + specified checkpoints, but only records the latest versions of system packages. + """ + packages(first: Int, after: String, last: Int, before: String, filter: MovePackageCheckpointFilter): MovePackageConnection! + """ Fetch the protocol config by protocol version (defaults to the latest protocol version known to the GraphQL service). """ diff --git a/crates/sui-graphql-rpc/src/types/event.rs b/crates/sui-graphql-rpc/src/types/event.rs index 16284f01618cd2..6b7ba8ee8b3c26 100644 --- a/crates/sui-graphql-rpc/src/types/event.rs +++ b/crates/sui-graphql-rpc/src/types/event.rs @@ -143,13 +143,13 @@ impl Event { /// checkpoint sequence numbers as the cursor to determine the correct page of results. The /// query can optionally be further `filter`-ed by the `EventFilter`. /// - /// The `checkpoint_viewed_at` parameter is represents the checkpoint sequence number at which - /// this page was queried for. Each entity returned in the connection will inherit this - /// checkpoint, so that when viewing that entity's state, it will be from the reference of this - /// checkpoint_viewed_at parameter. + /// The `checkpoint_viewed_at` parameter represents the checkpoint sequence number at which this + /// page was queried. Each entity returned in the connection will inherit this checkpoint, so + /// that when viewing that entity's state, it will be as if it is being viewed at this + /// checkpoint. /// - /// If the `Page` is set, then this function will defer to the `checkpoint_viewed_at` in - /// the cursor if they are consistent. + /// The cursors in `page` may also include checkpoint viewed at fields. If these are set, they + /// take precedence over the checkpoint that pagination is being conducted in. pub(crate) async fn paginate( db: &Db, page: Page, diff --git a/crates/sui-graphql-rpc/src/types/move_package.rs b/crates/sui-graphql-rpc/src/types/move_package.rs index 1e8490be2465bb..d29612246c3059 100644 --- a/crates/sui-graphql-rpc/src/types/move_package.rs +++ b/crates/sui-graphql-rpc/src/types/move_package.rs @@ -7,7 +7,7 @@ use super::balance::{self, Balance}; use super::base64::Base64; use super::big_int::BigInt; use super::coin::Coin; -use super::cursor::{JsonCursor, Page}; +use super::cursor::{BcsCursor, JsonCursor, Page, RawPaginated, Target}; use super::move_module::MoveModule; use super::move_object::MoveObject; use super::object::{self, Object, ObjectFilter, ObjectImpl, ObjectOwner, ObjectStatus}; @@ -18,14 +18,19 @@ use super::suins_registration::{DomainFormat, SuinsRegistration}; use super::transaction_block::{self, TransactionBlock, TransactionBlockFilter}; use super::type_filter::ExactTypeFilter; use super::uint53::UInt53; -use crate::consistency::ConsistentNamedCursor; +use crate::consistency::{Checkpointed, ConsistentNamedCursor}; use crate::data::{DataLoader, Db, DbConnection, QueryExecutor}; use crate::error::Error; +use crate::raw_query::RawQuery; use crate::types::sui_address::addr; +use crate::{filter, query}; use async_graphql::connection::{Connection, CursorType, Edge}; use async_graphql::dataloader::Loader; use async_graphql::*; -use diesel::{BoolExpressionMethods, ExpressionMethods, JoinOnDsl, QueryDsl}; +use diesel::prelude::QueryableByName; +use diesel::{BoolExpressionMethods, ExpressionMethods, JoinOnDsl, QueryDsl, Selectable}; +use serde::{Deserialize, Serialize}; +use sui_indexer::models::objects::StoredHistoryObject; use sui_indexer::schema::packages; use sui_package_resolver::{error::Error as PackageCacheError, Package as ParsedMovePackage}; use sui_types::is_system_package; @@ -41,6 +46,18 @@ pub(crate) struct MovePackage { pub native: NativeMovePackage, } +/// Filter for paginating `MovePackage`s that were created within a range of checkpoints. +#[derive(InputObject, Debug, Default, Clone)] +pub(crate) struct MovePackageCheckpointFilter { + /// Fetch packages that were published strictly after this checkpoint. Omitting this fetches + /// packages published since genesis. + pub after_checkpoint: Option, + + /// Fetch packages that were published strictly before this checkpoint. Omitting this fetches + /// packages published up to the latest checkpoint (inclusive). + pub before_checkpoint: Option, +} + /// Filter for a point query of a MovePackage, supporting querying different versions of a package /// by their version. Note that different versions of the same user package exist at different IDs /// to each other, so this is different from looking up the historical version of an object. @@ -87,9 +104,31 @@ struct TypeOrigin { defining_id: SuiAddress, } +/// A wrapper around the stored representation of a package, used to implement pagination-related +/// traits. +#[derive(Selectable, QueryableByName)] +#[diesel(table_name = packages)] +struct StoredHistoryPackage { + original_id: Vec, + #[diesel(embed)] + object: StoredHistoryObject, +} + pub(crate) struct MovePackageDowncastError; pub(crate) type CModule = JsonCursor; +pub(crate) type Cursor = BcsCursor; + +/// The inner struct for the `MovePackage` cursor. The package is identified by the checkpoint it +/// was created in, its original ID, and its version, and the `checkpoint_viewed_at` specifies the +/// checkpoint snapshot that the data came from. +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] +pub(crate) struct PackageCursor { + pub checkpoint_sequence_number: u64, + pub original_id: Vec, + pub package_version: u64, + pub checkpoint_viewed_at: u64, +} /// DataLoader key for fetching the storage ID of the (user) package that shares an original (aka /// runtime) ID with the package stored at `package_id`, and whose version is `version`. @@ -574,6 +613,164 @@ impl MovePackage { Error::Internal(format!("{address} is not a package")) })?)) } + + /// Query the database for a `page` of Move packages. The Page uses the checkpoint sequence + /// number the package was created at, its original ID, and its version as the cursor. The query + /// can optionally be filtered by a bound on the checkpoints the packages were created in. + /// + /// The `checkpoint_viewed_at` parameter represents the checkpoint sequence number at which this + /// page was queried. Each entity returned in the connection will inherit this checkpoint, so + /// that when viewing that entity's state, it will be as if it is being viewed at this + /// checkpoint. + /// + /// The cursors in `page` may also include checkpoint viewed at fields. If these are set, they + /// take precedence over the checkpoint that pagination is being conducted in. + pub(crate) async fn paginate_by_checkpoint( + db: &Db, + page: Page, + filter: Option, + checkpoint_viewed_at: u64, + ) -> Result, Error> { + let cursor_viewed_at = page.validate_cursor_consistency()?; + let checkpoint_viewed_at = cursor_viewed_at.unwrap_or(checkpoint_viewed_at); + + let after_checkpoint: Option = filter + .as_ref() + .and_then(|f| f.after_checkpoint) + .map(|v| v.into()); + + // Clamp the "before checkpoint" bound by "checkpoint viewed at". + let before_checkpoint = filter + .as_ref() + .and_then(|f| f.before_checkpoint) + .map(|v| v.into()) + .unwrap_or(u64::MAX) + .min(checkpoint_viewed_at + 1); + + let (prev, next, results) = db + .execute(move |conn| { + let mut q = query!( + r#" + SELECT + p.original_id, + o.* + FROM + packages p + INNER JOIN + objects_history o + ON + p.package_id = o.object_id + AND p.package_version = o.object_version + AND p.checkpoint_sequence_number = o.checkpoint_sequence_number + "# + ); + + q = filter!( + q, + format!("o.checkpoint_sequence_number < {before_checkpoint}") + ); + if let Some(after) = after_checkpoint { + q = filter!(q, format!("{after} < o.checkpoint_sequence_number")); + } + + page.paginate_raw_query::(conn, checkpoint_viewed_at, q) + }) + .await?; + + let mut conn = Connection::new(prev, next); + + // The "checkpoint viewed at" sets a consistent upper bound for the nested queries. + for stored in results { + let cursor = stored.cursor(checkpoint_viewed_at).encode_cursor(); + let package = + MovePackage::try_from_stored_history_object(stored.object, checkpoint_viewed_at)?; + conn.edges.push(Edge::new(cursor, package)); + } + + Ok(conn) + } + + /// `checkpoint_viewed_at` points to the checkpoint snapshot that this `MovePackage` came from. + /// This is stored in the `MovePackage` so that related fields from the package are read from + /// the same checkpoint (consistently). + pub(crate) fn try_from_stored_history_object( + history_object: StoredHistoryObject, + checkpoint_viewed_at: u64, + ) -> Result { + let object = Object::try_from_stored_history_object( + history_object, + checkpoint_viewed_at, + /* root_version */ None, + )?; + Self::try_from(&object).map_err(|_| Error::Internal("Not a package!".to_string())) + } +} + +impl Checkpointed for Cursor { + fn checkpoint_viewed_at(&self) -> u64 { + self.checkpoint_viewed_at + } +} + +impl RawPaginated for StoredHistoryPackage { + fn filter_ge(cursor: &Cursor, query: RawQuery) -> RawQuery { + filter!( + query, + format!( + "o.checkpoint_sequence_number > {cp} OR (\ + o.checkpoint_sequence_number = {cp} AND + p.original_id > '\\x{id}'::bytea OR (\ + p.original_id = '\\x{id}'::bytea AND \ + p.package_version >= {pv}\ + ))", + cp = cursor.checkpoint_sequence_number, + id = hex::encode(&cursor.original_id), + pv = cursor.package_version, + ) + ) + } + + fn filter_le(cursor: &Cursor, query: RawQuery) -> RawQuery { + filter!( + query, + format!( + "o.checkpoint_sequence_number < {cp} OR (\ + o.checkpoint_sequence_number = {cp} AND + p.original_id < '\\x{id}'::bytea OR (\ + p.original_id = '\\x{id}'::bytea AND \ + p.package_version <= {pv}\ + ))", + cp = cursor.checkpoint_sequence_number, + id = hex::encode(&cursor.original_id), + pv = cursor.package_version, + ) + ) + } + + fn order(asc: bool, query: RawQuery) -> RawQuery { + if asc { + query + .order_by("o.checkpoint_sequence_number ASC") + .order_by("p.original_id ASC") + .order_by("p.package_version ASC") + } else { + query + .order_by("o.checkpoint_sequence_number DESC") + .order_by("p.original_id DESC") + .order_by("p.package_version DESC") + } + } +} + +impl Target for StoredHistoryPackage { + fn cursor(&self, checkpoint_viewed_at: u64) -> Cursor { + Cursor::new(PackageCursor { + checkpoint_sequence_number: self.object.checkpoint_sequence_number as u64, + original_id: self.original_id.clone(), + package_version: self.object.object_version as u64, + checkpoint_viewed_at, + }) + } } #[async_trait::async_trait] diff --git a/crates/sui-graphql-rpc/src/types/query.rs b/crates/sui-graphql-rpc/src/types/query.rs index 5aa55e73347738..0970a046749c39 100644 --- a/crates/sui-graphql-rpc/src/types/query.rs +++ b/crates/sui-graphql-rpc/src/types/query.rs @@ -12,7 +12,7 @@ use sui_sdk::SuiClient; use sui_types::transaction::{TransactionData, TransactionKind}; use sui_types::{gas_coin::GAS, transaction::TransactionDataAPI, TypeTag}; -use super::move_package::MovePackage; +use super::move_package::{self, MovePackage, MovePackageCheckpointFilter}; use super::suins_registration::NameService; use super::uint53::UInt53; use super::{ @@ -435,6 +435,28 @@ impl Query { .extend() } + /// The Move packages that exist in the network, optionally filtered to be strictly before + /// `beforeCheckpoint` and/or strictly after `afterCheckpoint`. + /// + /// This query will return all versions of a given user package that appear between the + /// specified checkpoints, but only records the latest versions of system packages. + async fn packages( + &self, + ctx: &Context<'_>, + first: Option, + after: Option, + last: Option, + before: Option, + filter: Option, + ) -> Result> { + let Watermark { checkpoint, .. } = *ctx.data()?; + + let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?; + MovePackage::paginate_by_checkpoint(ctx.data_unchecked(), page, filter, checkpoint) + .await + .extend() + } + /// Fetch the protocol config by protocol version (defaults to the latest protocol /// version known to the GraphQL service). async fn protocol_config( diff --git a/crates/sui-graphql-rpc/tests/snapshots/snapshot_tests__schema_sdl_export.snap b/crates/sui-graphql-rpc/tests/snapshots/snapshot_tests__schema_sdl_export.snap index 61cabeeb1de138..3499ea88329cc9 100644 --- a/crates/sui-graphql-rpc/tests/snapshots/snapshot_tests__schema_sdl_export.snap +++ b/crates/sui-graphql-rpc/tests/snapshots/snapshot_tests__schema_sdl_export.snap @@ -2205,6 +2205,22 @@ type MovePackage implements IObject & IOwner { moduleBcs: Base64 } +""" +Filter for paginating `MovePackage`s that were created within a range of checkpoints. +""" +input MovePackageCheckpointFilter { + """ + Fetch packages that were published strictly after this checkpoint. Omitting this fetches + packages published since genesis. + """ + afterCheckpoint: UInt53 + """ + Fetch packages that were published strictly before this checkpoint. Omitting this fetches + packages published up to the latest checkpoint (inclusive). + """ + beforeCheckpoint: UInt53 +} + type MovePackageConnection { """ Information to aid in pagination. @@ -3119,6 +3135,14 @@ type Query { """ objects(first: Int, after: String, last: Int, before: String, filter: ObjectFilter): ObjectConnection! """ + The Move packages that exist in the network, optionally filtered to be strictly before + `beforeCheckpoint` and/or strictly after `afterCheckpoint`. + + This query will return all versions of a given user package that appear between the + specified checkpoints, but only records the latest versions of system packages. + """ + packages(first: Int, after: String, last: Int, before: String, filter: MovePackageCheckpointFilter): MovePackageConnection! + """ Fetch the protocol config by protocol version (defaults to the latest protocol version known to the GraphQL service). """