From a87c153ce20d6af50ff16661dcafa170141c83f5 Mon Sep 17 00:00:00 2001 From: Ashok Menon Date: Fri, 1 Dec 2023 11:38:32 +0000 Subject: [PATCH] [GraphQL/TransactionBlock] ObjectChange Representation (#15074) ## Description Bring `ObjectChange` representation inline with `BalanceChange`, `TransactionBlockEffects` etc. In particular objects are not loaded unless explicitly requested. This change also changes the `idCreated` and `idDeleted` fields to always produce a value. They are marked as optional to give us flexibility to change the schema in the future, not because they would otherwise ever be `None`. NB. I'm anticipating a follow-up change to not rely on the indexed object change at all, and instead derive this data from effects, by augmenting `TransactionEffectsAPI` to calculate this for us from raw effects. ## Test Plan ``` sui-graphql-rpc$ cargo nextest run sui-graphql-e2e-tests$ cargo nextest run -j 1 --features pg_integration ``` ## Stack - #14929 - #14930 - #14934 - #14935 - #14961 - #14974 - #15013 - #15014 - #15015 - #15016 - #15018 - #15020 - #15021 - #15036 - #15037 - #15038 - #15039 - #15040 - #15041 - #15042 - #15043 - #15044 --- .../tests/transactions/programmable.exp | 30 ++--- .../tests/transactions/system.exp | 46 +++---- .../schema/current_progress_schema.graphql | 12 ++ crates/sui-graphql-rpc/src/types/mod.rs | 1 + .../src/types/object_change.rs | 88 +++++++++++++ .../src/types/transaction_block_effects.rs | 122 +----------------- .../snapshot_tests__schema_sdl_export.snap | 12 ++ 7 files changed, 158 insertions(+), 153 deletions(-) create mode 100644 crates/sui-graphql-rpc/src/types/object_change.rs diff --git a/crates/sui-graphql-e2e-tests/tests/transactions/programmable.exp b/crates/sui-graphql-e2e-tests/tests/transactions/programmable.exp index 6d9c36e878d2b..ce1116cce9c6c 100644 --- a/crates/sui-graphql-e2e-tests/tests/transactions/programmable.exp +++ b/crates/sui-graphql-e2e-tests/tests/transactions/programmable.exp @@ -66,8 +66,8 @@ Response: { ], "objectChanges": [ { - "idCreated": null, - "idDeleted": null, + "idCreated": false, + "idDeleted": false, "outputState": { "location": "0x11c5cd8906fc48629f168f5a13a75c8ca77f69ae1ec15edfda29789f1d9638d9", "digest": "9dppxTwUWhC8nVH4Djdv71K75Ze4HEXe5ZU3NcX3TrK" @@ -75,7 +75,7 @@ Response: { }, { "idCreated": true, - "idDeleted": null, + "idDeleted": false, "outputState": { "location": "0xa4a7220856bab19ccaa904d168baa3d733b59c8c067cdcc300502ac352f679af", "digest": "9AR9zGFB4aPcZzniVTVqRTYHwUASw5PXPYsTtomT8jyB" @@ -83,7 +83,7 @@ Response: { }, { "idCreated": true, - "idDeleted": null, + "idDeleted": false, "outputState": { "location": "0xcadb6deed476a072959bd1a031809d737240ccde7917495e16aa9793d6419a74", "digest": "PaCSXQ5RCkt2vc6w2NfZMNBvoCYyM9H6uJJtx1KgEyj" @@ -185,16 +185,16 @@ Response: { ], "objectChanges": [ { - "idCreated": null, - "idDeleted": null, + "idCreated": false, + "idDeleted": false, "outputState": { "location": "0x11c5cd8906fc48629f168f5a13a75c8ca77f69ae1ec15edfda29789f1d9638d9", "digest": "765g4rih8tpnzb9gHEHArtZ3Ye7SJ3CJUapLVUsdcd6K" } }, { - "idCreated": null, - "idDeleted": null, + "idCreated": false, + "idDeleted": false, "outputState": { "location": "0xa4a7220856bab19ccaa904d168baa3d733b59c8c067cdcc300502ac352f679af", "digest": "Hyz4RaTV3cApJs911rX9CURmBd7amvComK4hoNrX7Dmg" @@ -202,7 +202,7 @@ Response: { }, { "idCreated": true, - "idDeleted": null, + "idDeleted": false, "outputState": { "location": "0x260de89d19ced9a26b7efba21d133f71987aa80696228f1dee6178961d0e07be", "digest": "9jMyF1w9TTRgvXdsWAn8d5awmQXYD4gUe1qX586UwWVt" @@ -301,8 +301,8 @@ Response: { ], "objectChanges": [ { - "idCreated": null, - "idDeleted": null, + "idCreated": false, + "idDeleted": false, "outputState": { "location": "0x11c5cd8906fc48629f168f5a13a75c8ca77f69ae1ec15edfda29789f1d9638d9", "digest": "6bbvaLxYq6Fj8bVvhvKkeZM3u27ecvwe9hGkbdsSXd2e", @@ -323,7 +323,7 @@ Response: { }, { "idCreated": true, - "idDeleted": null, + "idDeleted": false, "outputState": { "location": "0x505c5f4993a1fe74c5492945104578ca55d6275a15d64ecc21de6b7b5ef34804", "digest": "5ayrZbSq44X3WqnwNsVvNHGs21YntWobKUoFZQbvVMyw", @@ -345,7 +345,7 @@ Response: { }, { "idCreated": true, - "idDeleted": null, + "idDeleted": false, "outputState": { "location": "0x82ff2affd2e0447baea51fb07098dd4192c38424f41e291c7b47bcae5be0f37c", "digest": "choJQNN7TWytitxF59W6QgnECZkzL28oT77TSkLvox7", @@ -456,8 +456,8 @@ Response: { ], "objectChanges": [ { - "idCreated": null, - "idDeleted": null, + "idCreated": false, + "idDeleted": false, "outputState": { "location": "0x11c5cd8906fc48629f168f5a13a75c8ca77f69ae1ec15edfda29789f1d9638d9", "digest": "EW31fn6wtJxosHoPLnhPNp8tNQgCctYMksKDMY6St4y5" diff --git a/crates/sui-graphql-e2e-tests/tests/transactions/system.exp b/crates/sui-graphql-e2e-tests/tests/transactions/system.exp index 0f393f5700588..c1cbdc1f8f21b 100644 --- a/crates/sui-graphql-e2e-tests/tests/transactions/system.exp +++ b/crates/sui-graphql-e2e-tests/tests/transactions/system.exp @@ -74,7 +74,7 @@ Response: { "objectChanges": [ { "idCreated": true, - "idDeleted": null, + "idDeleted": false, "outputState": { "location": "0x0000000000000000000000000000000000000000000000000000000000000001", "digest": "FvLv2TuVhx1Ga8oCLW4gZmiBnQpT7XkSdGBo753kApGD" @@ -82,7 +82,7 @@ Response: { }, { "idCreated": true, - "idDeleted": null, + "idDeleted": false, "outputState": { "location": "0x0000000000000000000000000000000000000000000000000000000000000002", "digest": "CcD7UEtUeyPhcnqdxS5GFRTQ9xRsx71wZ3fRcGsaKEUp" @@ -90,7 +90,7 @@ Response: { }, { "idCreated": true, - "idDeleted": null, + "idDeleted": false, "outputState": { "location": "0x0000000000000000000000000000000000000000000000000000000000000003", "digest": "4VXaAMUGRedbhHDoNifrFx2iYeC8n5qB1W6BDsUvJc3h" @@ -98,7 +98,7 @@ Response: { }, { "idCreated": true, - "idDeleted": null, + "idDeleted": false, "outputState": { "location": "0x0000000000000000000000000000000000000000000000000000000000000005", "digest": "EVK8EKnUhpqHEGHYX8da98qWhQT965WLxanbCZnwaT1Y" @@ -106,7 +106,7 @@ Response: { }, { "idCreated": true, - "idDeleted": null, + "idDeleted": false, "outputState": { "location": "0x0000000000000000000000000000000000000000000000000000000000000006", "digest": "D1mkJGFbzAZmXfD3ktF38Endz9LtnML94B5kuyQEhR1e" @@ -114,7 +114,7 @@ Response: { }, { "idCreated": true, - "idDeleted": null, + "idDeleted": false, "outputState": { "location": "0x0000000000000000000000000000000000000000000000000000000000000007", "digest": "2aRjNBbkQWJToXLL74Wzv2GDu9sWcFPd5XXgvrgk7FAC" @@ -122,7 +122,7 @@ Response: { }, { "idCreated": true, - "idDeleted": null, + "idDeleted": false, "outputState": { "location": "0x000000000000000000000000000000000000000000000000000000000000dee9", "digest": "FX5F5Ex6asvjDd5xU8VBua8eSGp9YAh7rBse6LJsxwBa" @@ -130,7 +130,7 @@ Response: { }, { "idCreated": true, - "idDeleted": null, + "idDeleted": false, "outputState": { "location": "0x14498a4368db5cefb6ec2fe91cf18636c5d8fc08b91e3d1417ccb0866e8b95d4", "digest": "94HmeZ5AZqsm4X4HStQEkJqiuQWQWogMRepMzHNcfXVS" @@ -138,7 +138,7 @@ Response: { }, { "idCreated": true, - "idDeleted": null, + "idDeleted": false, "outputState": { "location": "0x168e8bf1317ef81399ea9fabee9e217067a257731903b45ca4915c8d049b3940", "digest": "8qJceXqDUSngLbk1rpquzALgcT4WtKfhM78LXepgZmtf" @@ -146,7 +146,7 @@ Response: { }, { "idCreated": true, - "idDeleted": null, + "idDeleted": false, "outputState": { "location": "0x1ca7fcc2fe39d93fd041dbcbcc30b33a366b44cab26acd52e5d99f6cf2a0a6e7", "digest": "5ZKMLAipLWCy8rkKBWVbf3NVbdEtna81wwNvt37PVdhj" @@ -154,7 +154,7 @@ Response: { }, { "idCreated": true, - "idDeleted": null, + "idDeleted": false, "outputState": { "location": "0x3fa556a7dadc08367a52a6eb4a3b602abbc21446c1f703d2ac46866a403d718e", "digest": "BUFgjXdbMvsknRVvLDefPKCzir7EPpkPmhinUQpD6SKy" @@ -162,7 +162,7 @@ Response: { }, { "idCreated": true, - "idDeleted": null, + "idDeleted": false, "outputState": { "location": "0x41a7f84bd3a02e7e5bea80580e40cec419d40ef9925a51cf0128ad84b40f8041", "digest": "9tGfLEvq5kUWYBUaMVSH3Qu996sAv5XAPEXGUFcYecpU" @@ -170,7 +170,7 @@ Response: { }, { "idCreated": true, - "idDeleted": null, + "idDeleted": false, "outputState": { "location": "0x46ea41cad4b69f493f5cf7fcb41c96e63ea97c206107d9b25d29a7d28f683ca7", "digest": "D5vv7Xzd6uDWFuYTSEFvNgfXrRsXSDna9v7h4BK7xQcW" @@ -178,7 +178,7 @@ Response: { }, { "idCreated": true, - "idDeleted": null, + "idDeleted": false, "outputState": { "location": "0x6af2a2b7ca60bf76174adfd3e9c4957f8e937759603182f9b46c7f6c5f19c6d2", "digest": "BWHXYvVDVbLrXAWL91Bgid4Pu9oDyF8hZqNoFsZ6HM7w" @@ -186,7 +186,7 @@ Response: { }, { "idCreated": true, - "idDeleted": null, + "idDeleted": false, "outputState": { "location": "0xcfecb053c69314e75f36561910f3535dd466b6e2e3593708f370e80424617ae7", "digest": "68NRbjATuQoyL78VTc3Gd96h2xoGmevmCpoAQzM7jghG" @@ -194,7 +194,7 @@ Response: { }, { "idCreated": true, - "idDeleted": null, + "idDeleted": false, "outputState": { "location": "0xd73b7dd2e4b15b4030f2d1af51a8e8335d37c9723565005f498df27d8a6d3a4d", "digest": "2b3gYd9RreACiq2Cxv2LJyLS8PWHeXfDNeECYbVaf8FR" @@ -276,8 +276,8 @@ Response: { "balanceChanges": [], "objectChanges": [ { - "idCreated": null, - "idDeleted": null, + "idCreated": false, + "idDeleted": false, "outputState": { "location": "0x0000000000000000000000000000000000000000000000000000000000000006", "digest": "28w3Mp2x4vt94bq4TejrbLcfMsMaevDyPz6h98sgcnw4" @@ -361,8 +361,8 @@ Response: { "balanceChanges": [], "objectChanges": [ { - "idCreated": null, - "idDeleted": null, + "idCreated": false, + "idDeleted": false, "outputState": { "location": "0x0000000000000000000000000000000000000000000000000000000000000005", "digest": "5jzahJer9cg4yJaBZhjpEAbLtzHcKguzMtFBc1poL5fS" @@ -370,7 +370,7 @@ Response: { }, { "idCreated": true, - "idDeleted": null, + "idDeleted": false, "outputState": { "location": "0x5b890eaf2abcfa2ab90b77b8e6f3d5d8609586c3e583baf3dccd5af17edf48d1", "digest": "Y4W89QnJVPhaMeoExCsfdQhF5bs5RELi5xjLgAy1h6a" @@ -378,14 +378,14 @@ Response: { }, { "idCreated": true, - "idDeleted": null, + "idDeleted": false, "outputState": { "location": "0xa84a25da7fef87ac6141a4920a87564ddb5bd8964333f04e7bd49facb831ba43", "digest": "2dUtbkQSCPJewe9WJsxvAdu1rft1AVXwXMuWYFkYnAxr" } }, { - "idCreated": null, + "idCreated": false, "idDeleted": true, "outputState": null } diff --git a/crates/sui-graphql-rpc/schema/current_progress_schema.graphql b/crates/sui-graphql-rpc/schema/current_progress_schema.graphql index 87873f8e25921..0f0a26752a74d 100644 --- a/crates/sui-graphql-rpc/schema/current_progress_schema.graphql +++ b/crates/sui-graphql-rpc/schema/current_progress_schema.graphql @@ -1057,9 +1057,21 @@ type Object implements ObjectOwner { dynamicFieldConnection(first: Int, after: String, last: Int, before: String): DynamicFieldConnection } +""" +Effect on an individual Object (keyed by its ID). +""" type ObjectChange { + """ + The contents of the object at the end of the transaction. + """ outputState: Object + """ + Whether the ID was created in this transaction. + """ idCreated: Boolean + """ + Whether the ID was deleted in this transaction. + """ idDeleted: Boolean } diff --git a/crates/sui-graphql-rpc/src/types/mod.rs b/crates/sui-graphql-rpc/src/types/mod.rs index 65965823f59d7..d128108d2e1d8 100644 --- a/crates/sui-graphql-rpc/src/types/mod.rs +++ b/crates/sui-graphql-rpc/src/types/mod.rs @@ -28,6 +28,7 @@ pub(crate) mod move_type; pub(crate) mod move_value; pub(crate) mod name_service; pub(crate) mod object; +pub(crate) mod object_change; pub(crate) mod open_move_type; pub(crate) mod owner; pub(crate) mod protocol_config; diff --git a/crates/sui-graphql-rpc/src/types/object_change.rs b/crates/sui-graphql-rpc/src/types/object_change.rs new file mode 100644 index 0000000000000..4904320a997a6 --- /dev/null +++ b/crates/sui-graphql-rpc/src/types/object_change.rs @@ -0,0 +1,88 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use async_graphql::*; +use sui_indexer::types_v2::IndexedObjectChange; + +use super::{object::Object, sui_address::SuiAddress}; +use crate::{context_data::db_data_provider::PgManager, error::Error}; + +pub(crate) struct ObjectChange { + // TODO: input_key (waiting for object history) + output_key: Option<(SuiAddress, u64)>, + id_created: bool, + id_deleted: bool, +} + +/// Effect on an individual Object (keyed by its ID). +#[Object] +impl ObjectChange { + /// The contents of the object at the end of the transaction. + async fn output_state(&self, ctx: &Context<'_>) -> Result> { + let Some((id, version)) = self.output_key else { + return Ok(None); + }; + + ctx.data_unchecked::() + .fetch_obj(id, Some(version)) + .await + .extend() + } + + /// Whether the ID was created in this transaction. + async fn id_created(&self) -> Option { + Some(self.id_created) + } + + /// Whether the ID was deleted in this transaction. + async fn id_deleted(&self) -> Option { + Some(self.id_deleted) + } +} + +impl ObjectChange { + pub(crate) fn read(bytes: &[u8]) -> Result { + use IndexedObjectChange as O; + + let stored: O = bcs::from_bytes(bytes) + .map_err(|e| Error::Internal(format!("Cannot deserialize ObjectChange: {e}")))?; + + Ok(match stored { + O::Published { + package_id: object_id, + version, + .. + } + | O::Created { + object_id, version, .. + } => ObjectChange { + output_key: Some((object_id.into(), version.value())), + id_created: true, + id_deleted: false, + }, + + O::Transferred { + object_id, version, .. + } + | O::Mutated { + object_id, version, .. + } => ObjectChange { + output_key: Some((object_id.into(), version.value())), + id_created: false, + id_deleted: false, + }, + + O::Deleted { .. } => ObjectChange { + output_key: None, + id_created: false, + id_deleted: true, + }, + + O::Wrapped { .. } => ObjectChange { + output_key: None, + id_created: false, + id_deleted: false, + }, + }) + } +} diff --git a/crates/sui-graphql-rpc/src/types/transaction_block_effects.rs b/crates/sui-graphql-rpc/src/types/transaction_block_effects.rs index 5ce7828301bc8..b0193f256d661 100644 --- a/crates/sui-graphql-rpc/src/types/transaction_block_effects.rs +++ b/crates/sui-graphql-rpc/src/types/transaction_block_effects.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use async_graphql::*; -use sui_indexer::{models_v2::transactions::StoredTransaction, types_v2::IndexedObjectChange}; +use sui_indexer::models_v2::transactions::StoredTransaction; use sui_types::{ effects::{TransactionEffects as NativeTransactionEffects, TransactionEffectsAPI}, execution_status::ExecutionStatus as NativeExecutionStatus, @@ -12,7 +12,7 @@ use crate::{context_data::db_data_provider::PgManager, error::Error}; use super::{ balance_change::BalanceChange, base64::Base64, checkpoint::Checkpoint, date_time::DateTime, - epoch::Epoch, gas::GasEffects, object::Object, sui_address::SuiAddress, + epoch::Epoch, gas::GasEffects, object_change::ObjectChange, transaction_block::TransactionBlock, }; @@ -32,14 +32,6 @@ pub enum ExecutionStatus { Failure, } -#[derive(Clone, SimpleObject)] -pub(crate) struct ObjectChange { - // TODO: input_state (waiting for object history) - pub output_state: Option, - pub id_created: Option, - pub id_deleted: Option, -} - #[Object] impl TransactionBlockEffects { /// The transaction that ran to produce these effects. @@ -124,18 +116,10 @@ impl TransactionBlockEffects { // TODO object_reads /// The effect this transaction had on objects on-chain. - async fn object_changes(&self, ctx: &Context<'_>) -> Result>> { - let mut changes = vec![]; - - for bcs in self.stored.object_changes.iter().flatten() { - let object_change: IndexedObjectChange = bcs::from_bytes(bcs) - .map_err(|_| { - Error::Internal( - "Cannot convert bcs bytes into IndexedObjectChange object".to_string(), - ) - }) - .extend()?; - changes.push(ObjectChange::from(object_change, ctx).await.extend()?); + async fn object_changes(&self) -> Result>> { + let mut changes = Vec::with_capacity(self.stored.object_changes.len()); + for change in self.stored.object_changes.iter().flatten() { + changes.push(ObjectChange::read(change).extend()?); } Ok(Some(changes)) @@ -145,8 +129,7 @@ impl TransactionBlockEffects { /// addresses and objects. async fn balance_changes(&self) -> Result>> { let mut changes = Vec::with_capacity(self.stored.balance_changes.len()); - for change in &self.stored.balance_changes { - let Some(change) = change else { continue }; + for change in self.stored.balance_changes.iter().flatten() { changes.push(BalanceChange::read(change).extend()?); } @@ -185,97 +168,6 @@ impl TransactionBlockEffects { } } -// TODO this should be replaced together with the whole TXBLOCKEFFECTS once the indexer has this stuff implemented -// see effects_v2.rs in indexer -impl ObjectChange { - async fn from(object_change: IndexedObjectChange, ctx: &Context<'_>) -> Result { - match object_change { - IndexedObjectChange::Created { - object_id, version, .. - } => { - let sui_address = SuiAddress::from_bytes(object_id.into_bytes()).map_err(|_| { - Error::Internal("Cannot decode a SuiAddress from object_id".to_string()) - })?; - let output_state = ctx - .data_unchecked::() - .fetch_obj(sui_address, Some(version.value())) - .await?; - Ok(Self { - output_state, - id_created: Some(true), - id_deleted: None, - }) - } - - IndexedObjectChange::Published { - package_id, - version, - .. - } => { - let sui_address = - SuiAddress::from_bytes(package_id.into_bytes()).map_err(|_| { - Error::Internal("Cannot decode a SuiAddress from package_id".to_string()) - })?; - let output_state = ctx - .data_unchecked::() - .fetch_obj(sui_address, Some(version.value())) - .await?; - Ok(Self { - output_state, - id_created: Some(true), - id_deleted: None, - }) - } - IndexedObjectChange::Transferred { - object_id, version, .. - } => { - let sui_address = SuiAddress::from_bytes(object_id.into_bytes()).map_err(|_| { - Error::Internal("Cannot decode a SuiAddress from object_id".to_string()) - })?; - // TODO - // I assume the output is a different object as it probably has a different - // owner (the recipient) + the version + digest are different - let output_state = ctx - .data_unchecked::() - .fetch_obj(sui_address, Some(version.value())) - .await?; - - Ok(Self { - output_state, - id_created: None, - id_deleted: None, - }) - } - IndexedObjectChange::Mutated { - object_id, version, .. - } => { - let sui_address = SuiAddress::from_bytes(object_id.into_bytes()).map_err(|_| { - Error::Internal("Cannot decode a SuiAddress from object_id".to_string()) - })?; - let output_state = ctx - .data_unchecked::() - .fetch_obj(sui_address, Some(version.value())) - .await?; - Ok(Self { - output_state, - id_created: None, - id_deleted: None, - }) - } - IndexedObjectChange::Deleted { .. } => Ok(Self { - output_state: None, - id_created: None, - id_deleted: Some(true), - }), - IndexedObjectChange::Wrapped { .. } => Ok(Self { - output_state: None, - id_created: None, - id_deleted: None, - }), - } - } -} - impl TryFrom for TransactionBlockEffects { type Error = Error; 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 85dfbdbec8d9c..706afa7fa9181 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 @@ -1061,9 +1061,21 @@ type Object implements ObjectOwner { dynamicFieldConnection(first: Int, after: String, last: Int, before: String): DynamicFieldConnection } +""" +Effect on an individual Object (keyed by its ID). +""" type ObjectChange { + """ + The contents of the object at the end of the transaction. + """ outputState: Object + """ + Whether the ID was created in this transaction. + """ idCreated: Boolean + """ + Whether the ID was deleted in this transaction. + """ idDeleted: Boolean }