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

fix(graphql): add root object version for dynamic field queries #17934

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8adafa1
fix(graphql): add root object version for dynamic field queries
unmaykr-aftermath May 25, 2024
c045af6
chore(graphql): remove TODO
unmaykr-aftermath May 25, 2024
c61cbe8
refactor(graphql): remove redundant `DynamicField::parent_version` attr
unmaykr-aftermath Jun 4, 2024
5afe7a4
chore(graphql): address review comments
unmaykr-aftermath Jun 4, 2024
8cbe505
feat(graphql): add MissingRootObject error for DF queries
unmaykr-aftermath Jun 9, 2024
52527c9
Revert "feat(graphql): add MissingRootObject error for DF queries"
unmaykr-aftermath Jun 9, 2024
592ffbb
feat(graphql): add MissingRootVersion error
unmaykr-aftermath Jun 9, 2024
dedad22
Revert "feat(graphql): add MissingRootVersion error"
unmaykr-aftermath Jun 13, 2024
bc4d3af
Reapply "feat(graphql): add MissingRootObject error for DF queries"
unmaykr-aftermath Jun 13, 2024
886cbd5
Revert "feat(graphql): add MissingRootObject error for DF queries"
unmaykr-aftermath Jun 13, 2024
3b74764
feat(graphql): relax mechanics for setting the root obj version
unmaykr-aftermath Jun 13, 2024
1f41206
docs(graphql): apply suggestions
unmaykr-aftermath Jun 14, 2024
1ea31c2
docs(graphql): update DynamicField.value
unmaykr-aftermath Jun 15, 2024
cda0593
docs(graphql): apply suggestion for DynamicField.value
unmaykr-aftermath Jun 17, 2024
fecea0c
chore(graphql): update schema for DynamicField.value
unmaykr-aftermath Jun 17, 2024
03124cf
chore(graphql): always return a version in version_for_dynamic_fields
unmaykr-aftermath Jun 17, 2024
538ccff
chore(graphql): update comments and docs
unmaykr-aftermath Jun 17, 2024
4f74c5f
e2e test for rooted dynamic field queries
wlmyng Jun 4, 2024
a0a9f12
test immutable behavior
wlmyng Jun 18, 2024
f6fe4a7
Merge pull request #1 from wlmyng/wlmyng/test-unmaykr-fix-graphql-nes…
unmaykr-aftermath Jun 19, 2024
dfda753
Merge branch 'main' into unmaykr/fix-graphql-nested-dof-queries
unmaykr-aftermath Jun 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions crates/sui-graphql-rpc/src/types/coin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ impl Coin {
name: DynamicFieldName,
) -> Result<Option<DynamicField>> {
OwnerImpl::from(&self.super_.super_)
.dynamic_field(ctx, name, Some(self.super_.super_.version_impl()))
.dynamic_field(ctx, name, self.super_.super_.root_version())
.await
}

Expand All @@ -261,7 +261,7 @@ impl Coin {
name: DynamicFieldName,
) -> Result<Option<DynamicField>> {
OwnerImpl::from(&self.super_.super_)
.dynamic_object_field(ctx, name, Some(self.super_.super_.version_impl()))
.dynamic_object_field(ctx, name, self.super_.super_.root_version())
.await
}

Expand All @@ -284,7 +284,7 @@ impl Coin {
after,
last,
before,
Some(self.super_.super_.version_impl()),
self.super_.super_.root_version(),
)
.await
}
Expand Down Expand Up @@ -336,7 +336,8 @@ impl Coin {
// To maintain consistency, the returned cursor should have the same upper-bound as the
// checkpoint found on the cursor.
let cursor = stored.cursor(checkpoint_viewed_at).encode_cursor();
let object = Object::try_from_stored_history_object(stored, checkpoint_viewed_at)?;
let object =
Object::try_from_stored_history_object(stored, checkpoint_viewed_at, None)?;

let move_ = MoveObject::try_from(&object).map_err(|_| {
Error::Internal(format!(
Expand Down
6 changes: 3 additions & 3 deletions crates/sui-graphql-rpc/src/types/coin_metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ impl CoinMetadata {
name: DynamicFieldName,
) -> Result<Option<DynamicField>> {
OwnerImpl::from(&self.super_.super_)
.dynamic_field(ctx, name, Some(self.super_.super_.version_impl()))
.dynamic_field(ctx, name, self.super_.super_.root_version())
.await
}

Expand All @@ -250,7 +250,7 @@ impl CoinMetadata {
name: DynamicFieldName,
) -> Result<Option<DynamicField>> {
OwnerImpl::from(&self.super_.super_)
.dynamic_object_field(ctx, name, Some(self.super_.super_.version_impl()))
.dynamic_object_field(ctx, name, self.super_.super_.root_version())
.await
}

Expand All @@ -273,7 +273,7 @@ impl CoinMetadata {
after,
last,
before,
Some(self.super_.super_.version_impl()),
self.super_.super_.root_version(),
)
.await
}
Expand Down
29 changes: 18 additions & 11 deletions crates/sui-graphql-rpc/src/types/dynamic_field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub(crate) struct DynamicField {
pub super_: MoveObject,
pub df_object_id: SuiAddress,
pub df_kind: DynamicFieldType,
parent_version: Option<u64>,
}

#[derive(Union)]
Expand Down Expand Up @@ -107,12 +108,11 @@ impl DynamicField {
let obj = MoveObject::query(
ctx,
self.df_object_id,
Object::under_parent(
// TODO (RPC-131): The dynamic object field value's version should be bounded by
// the field's parent version, not the version of the field object itself.
self.super_.super_.version_impl(),
self.super_.super_.checkpoint_viewed_at,
),
if let Some(parent_version) = self.parent_version {
Object::under_parent(parent_version, self.super_.super_.checkpoint_viewed_at)
} else {
Object::latest_at(self.super_.super_.checkpoint_viewed_at)
},
)
.await
.extend()?;
Expand Down Expand Up @@ -181,7 +181,9 @@ impl DynamicField {
)
.await?;

super_.map(Self::try_from).transpose()
super_
.map(|mo| Self::try_from((mo, parent_version)))
.transpose()
}

/// Query the `db` for a `page` of dynamic fields attached to object with ID `parent`. The
Expand Down Expand Up @@ -228,7 +230,11 @@ impl DynamicField {
// checkpoint found on the cursor.
let cursor = stored.cursor(checkpoint_viewed_at).encode_cursor();

let object = Object::try_from_stored_history_object(stored, checkpoint_viewed_at)?;
let object = Object::try_from_stored_history_object(
stored,
checkpoint_viewed_at,
parent_version,
)?;

let move_ = MoveObject::try_from(&object).map_err(|_| {
Error::Internal(format!(
Expand All @@ -237,18 +243,18 @@ impl DynamicField {
))
})?;

let dynamic_field = DynamicField::try_from(move_)?;
let dynamic_field = DynamicField::try_from((move_, parent_version))?;
wlmyng marked this conversation as resolved.
Show resolved Hide resolved
conn.edges.push(Edge::new(cursor, dynamic_field));
}

Ok(conn)
}
}

impl TryFrom<MoveObject> for DynamicField {
impl TryFrom<(MoveObject, Option<u64>)> for DynamicField {
wlmyng marked this conversation as resolved.
Show resolved Hide resolved
type Error = Error;

fn try_from(stored: MoveObject) -> Result<Self, Error> {
fn try_from((stored, parent_version): (MoveObject, Option<u64>)) -> Result<Self, Error> {
let super_ = &stored.super_;

let (df_object_id, df_kind) = match &super_.kind {
Expand Down Expand Up @@ -284,6 +290,7 @@ impl TryFrom<MoveObject> for DynamicField {
super_: stored,
df_object_id,
df_kind,
parent_version,
})
}
}
Expand Down
13 changes: 3 additions & 10 deletions crates/sui-graphql-rpc/src/types/move_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ impl MoveObject {
name: DynamicFieldName,
) -> Result<Option<DynamicField>> {
OwnerImpl::from(&self.super_)
.dynamic_field(ctx, name, Some(self.super_.version_impl()))
.dynamic_field(ctx, name, self.super_.root_version())
.await
}

Expand All @@ -329,7 +329,7 @@ impl MoveObject {
name: DynamicFieldName,
) -> Result<Option<DynamicField>> {
OwnerImpl::from(&self.super_)
.dynamic_object_field(ctx, name, Some(self.super_.version_impl()))
.dynamic_object_field(ctx, name, self.super_.root_version())
.await
}

Expand All @@ -346,14 +346,7 @@ impl MoveObject {
before: Option<object::Cursor>,
) -> Result<Connection<String, DynamicField>> {
OwnerImpl::from(&self.super_)
.dynamic_fields(
ctx,
first,
after,
last,
before,
Some(self.super_.version_impl()),
)
.dynamic_fields(ctx, first, after, last, before, self.super_.root_version())
.await
}

Expand Down
61 changes: 53 additions & 8 deletions crates/sui-graphql-rpc/src/types/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,18 @@ pub(crate) struct Object {
pub kind: ObjectKind,
/// The checkpoint sequence number at which this was viewed at.
pub checkpoint_viewed_at: u64,
/// Optional root parent object version if this is a dynamic field.
///
/// This enables consistent dynamic field reads in the case of chained dynamic object fields,
/// e.g., `Parent -> DOF1 -> DOF2`. In such cases, the object versions may end up like
/// `Parent >= DOF1, DOF2` but `DOF1 < DOF2`. Thus, database queries for dynamic fields must
/// bound the object versions by the version of the root object of the tree.
///
/// Essentially, lamport timestamps of objects are updated for all top-level mutable objects
/// provided as inputs to a transaction as well as any mutated dynamic child objects. However,
/// any dynamic child objects that were loaded but not actually mutated don't end up having
/// their versions updated.
root_version: Option<u64>,
}

/// Type to implement GraphQL fields that are shared by all Objects.
Expand Down Expand Up @@ -462,7 +474,7 @@ impl Object {
name: DynamicFieldName,
) -> Result<Option<DynamicField>> {
OwnerImpl::from(self)
.dynamic_field(ctx, name, Some(self.version_impl()))
.dynamic_field(ctx, name, self.root_version())
.await
}

Expand All @@ -479,7 +491,7 @@ impl Object {
name: DynamicFieldName,
) -> Result<Option<DynamicField>> {
OwnerImpl::from(self)
.dynamic_object_field(ctx, name, Some(self.version_impl()))
.dynamic_object_field(ctx, name, self.root_version())
.await
}

Expand All @@ -496,7 +508,7 @@ impl Object {
before: Option<Cursor>,
) -> Result<Connection<String, DynamicField>> {
OwnerImpl::from(self)
.dynamic_fields(ctx, first, after, last, before, Some(self.version_impl()))
.dynamic_fields(ctx, first, after, last, before, self.root_version())
.await
}

Expand Down Expand Up @@ -676,11 +688,23 @@ impl Object {
address: SuiAddress,
native: NativeObject,
checkpoint_viewed_at: u64,
root_version: Option<u64>,
wlmyng marked this conversation as resolved.
Show resolved Hide resolved
) -> Object {
let root_version = root_version.or_else(|| Self::infer_root_version(&native));
Object {
address,
kind: ObjectKind::NotIndexed(native),
checkpoint_viewed_at,
root_version,
}
}

fn infer_root_version(native: &NativeObject) -> Option<u64> {
match native.as_inner().owner {
NativeOwner::AddressOwner(_) | NativeOwner::Shared { .. } => {
Some(native.as_inner().version().into())
}
_ => None,
wlmyng marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand All @@ -702,6 +726,10 @@ impl Object {
}
}

pub(crate) fn root_version(&self) -> Option<u64> {
wlmyng marked this conversation as resolved.
Show resolved Hide resolved
self.root_version
}

/// Query the database for a `page` of objects, optionally `filter`-ed.
///
/// `checkpoint_viewed_at` represents the checkpoint sequence number at which this page was
Expand Down Expand Up @@ -766,7 +794,8 @@ impl Object {
// To maintain consistency, the returned cursor should have the same upper-bound as the
// checkpoint found on the cursor.
let cursor = stored.cursor(checkpoint_viewed_at).encode_cursor();
let object = Object::try_from_stored_history_object(stored, checkpoint_viewed_at)?;
let object =
Object::try_from_stored_history_object(stored, checkpoint_viewed_at, None)?;
conn.edges.push(Edge::new(cursor, downcast(object)?));
}

Expand Down Expand Up @@ -857,6 +886,7 @@ impl Object {
pub(crate) fn try_from_stored_history_object(
history_object: StoredHistoryObject,
checkpoint_viewed_at: u64,
root_version: Option<u64>,
wlmyng marked this conversation as resolved.
Show resolved Hide resolved
) -> Result<Self, Error> {
let address = addr(&history_object.object_id)?;

Expand All @@ -881,10 +911,13 @@ impl Object {
Error::Internal(format!("Failed to deserialize object {address}"))
})?;

let root_version =
root_version.or_else(|| Self::infer_root_version(&native_object));
Ok(Self {
address,
kind: ObjectKind::Indexed(native_object, history_object),
checkpoint_viewed_at,
root_version,
})
}
NativeObjectStatus::WrappedOrDeleted => Ok(Self {
Expand All @@ -896,6 +929,7 @@ impl Object {
checkpoint_sequence_number: history_object.checkpoint_sequence_number,
}),
checkpoint_viewed_at,
root_version: None,
}),
}
}
Expand Down Expand Up @@ -1167,8 +1201,14 @@ impl Loader<HistoricalKey> for Db {
continue;
}

let object =
Object::try_from_stored_history_object(stored.clone(), key.checkpoint_viewed_at)?;
let object = Object::try_from_stored_history_object(
stored.clone(),
key.checkpoint_viewed_at,
// This will infer the `Object::root_version` if it's not a child object, but if it
// is, it will be set to `None`, so dynamic object queries will be bounded by
// `checkpoint_viewed_at`.
wlmyng marked this conversation as resolved.
Show resolved Hide resolved
None,
)?;
result.insert(*key, object);
}

Expand Down Expand Up @@ -1255,8 +1295,13 @@ impl Loader<LatestAtKey> for Db {
for (group_key, stored) in
group.map_err(|e| Error::Internal(format!("Failed to fetch objects: {e}")))?
{
let object =
Object::try_from_stored_history_object(stored, group_key.checkpoint_viewed_at)?;
let object = Object::try_from_stored_history_object(
stored,
group_key.checkpoint_viewed_at,
// If `LatestAtKey::parent_version` is set, it must have been correctly
// propagated from the `Object::root_version` of some object.
group_key.parent_version,
)?;

let key = LatestAtKey {
id: object.address,
Expand Down
6 changes: 3 additions & 3 deletions crates/sui-graphql-rpc/src/types/stake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ impl StakedSui {
name: DynamicFieldName,
) -> Result<Option<DynamicField>> {
OwnerImpl::from(&self.super_.super_)
.dynamic_field(ctx, name, Some(self.super_.super_.version_impl()))
.dynamic_field(ctx, name, self.super_.super_.root_version())
.await
}

Expand All @@ -269,7 +269,7 @@ impl StakedSui {
name: DynamicFieldName,
) -> Result<Option<DynamicField>> {
OwnerImpl::from(&self.super_.super_)
.dynamic_object_field(ctx, name, Some(self.super_.super_.version_impl()))
.dynamic_object_field(ctx, name, self.super_.super_.root_version())
.await
}

Expand All @@ -292,7 +292,7 @@ impl StakedSui {
after,
last,
before,
Some(self.super_.super_.version_impl()),
self.super_.super_.root_version(),
)
.await
}
Expand Down
9 changes: 5 additions & 4 deletions crates/sui-graphql-rpc/src/types/suins_registration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ impl SuinsRegistration {
name: DynamicFieldName,
) -> Result<Option<DynamicField>> {
OwnerImpl::from(&self.super_.super_)
.dynamic_field(ctx, name, Some(self.super_.super_.version_impl()))
.dynamic_field(ctx, name, self.super_.super_.root_version())
.await
}

Expand All @@ -306,7 +306,7 @@ impl SuinsRegistration {
name: DynamicFieldName,
) -> Result<Option<DynamicField>> {
OwnerImpl::from(&self.super_.super_)
.dynamic_object_field(ctx, name, Some(self.super_.super_.version_impl()))
.dynamic_object_field(ctx, name, self.super_.super_.root_version())
.await
}

Expand All @@ -329,7 +329,7 @@ impl SuinsRegistration {
after,
last,
before,
Some(self.super_.super_.version_impl()),
self.super_.super_.root_version(),
)
.await
}
Expand Down Expand Up @@ -509,7 +509,8 @@ impl NameService {
// name_record. We then assign it to the correct field on `domain_expiration` based on the
// address.
for result in results {
let object = Object::try_from_stored_history_object(result, checkpoint_viewed_at)?;
let object =
Object::try_from_stored_history_object(result, checkpoint_viewed_at, None)?;
let move_object = MoveObject::try_from(&object).map_err(|_| {
Error::Internal(format!(
"Expected {0} to be a NameRecord, but it's not a Move Object.",
Expand Down
Loading
Loading