From 467e00c45ed18a73149b0ed4b5a23afb4b2ef20d Mon Sep 17 00:00:00 2001 From: Emma Zhong Date: Thu, 29 Aug 2024 21:23:17 -0700 Subject: [PATCH] =?UTF-8?q?[pick][GraphQL/Events]=20Disable=20filtering=20?= =?UTF-8?q?on=20both=20`event=5Ftype`=20and=20`emitting=E2=80=A6=20(#19135?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …_module` (#18740) ## Description Cherry pick event indexer + graphql changes. ## 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: --------- Co-authored-by: wlmyng <127570466+wlmyng@users.noreply.github.com> Co-authored-by: Lu Zhang <8418040+longbowlu@users.noreply.github.com> --- .github/workflows/bridge.yml | 4 +- bridge/evm/.gitignore | 3 +- bridge/evm/README.md | 13 +- bridge/evm/foundry.toml | 15 +- bridge/evm/remappings.txt | 13 +- bridge/evm/soldeer.lock | 24 -- .../event_connection/combo_filter_error.exp | 43 +++ .../event_connection/combo_filter_error.move | 51 +++ .../tests/event_connection/no_filter.exp | 254 ++++++++++++++ .../tests/event_connection/no_filter.move | 59 ++++ .../tests/event_connection/tx_digest.exp | 326 ++++++++++++++++++ .../tests/event_connection/tx_digest.move | 226 ++++++++++++ .../tests/event_connection/type_filter.exp | 211 ++++++++++++ .../tests/event_connection/type_filter.move | 140 ++++++++ .../event_connection/type_param_filter.exp | 182 ++++++++++ .../event_connection/type_param_filter.move | 92 +++++ crates/sui-graphql-rpc/schema.graphql | 6 +- .../sui-graphql-rpc/src/types/event/cursor.rs | 183 ++++++++++ .../sui-graphql-rpc/src/types/event/filter.rs | 44 +++ .../src/types/event/lookups.rs | 158 +++++++++ .../src/types/{event.rs => event/mod.rs} | 256 +++++--------- crates/sui-graphql-rpc/src/types/query.rs | 4 +- .../sui-graphql-rpc/src/types/type_filter.rs | 115 +----- .../snapshot_tests__schema_sdl_export.snap | 6 +- .../pg/2023-08-19-044020_events/up.sql | 8 - crates/sui-indexer/src/models/events.rs | 16 - crates/sui-indexer/src/schema/pg.rs | 8 - crates/sui-indexer/tests/ingestion_tests.rs | 69 +--- 28 files changed, 2093 insertions(+), 436 deletions(-) delete mode 100644 bridge/evm/soldeer.lock create mode 100644 crates/sui-graphql-e2e-tests/tests/event_connection/combo_filter_error.exp create mode 100644 crates/sui-graphql-e2e-tests/tests/event_connection/combo_filter_error.move create mode 100644 crates/sui-graphql-e2e-tests/tests/event_connection/no_filter.exp create mode 100644 crates/sui-graphql-e2e-tests/tests/event_connection/no_filter.move create mode 100644 crates/sui-graphql-e2e-tests/tests/event_connection/tx_digest.exp create mode 100644 crates/sui-graphql-e2e-tests/tests/event_connection/tx_digest.move create mode 100644 crates/sui-graphql-e2e-tests/tests/event_connection/type_filter.exp create mode 100644 crates/sui-graphql-e2e-tests/tests/event_connection/type_filter.move create mode 100644 crates/sui-graphql-e2e-tests/tests/event_connection/type_param_filter.exp create mode 100644 crates/sui-graphql-e2e-tests/tests/event_connection/type_param_filter.move create mode 100644 crates/sui-graphql-rpc/src/types/event/cursor.rs create mode 100644 crates/sui-graphql-rpc/src/types/event/filter.rs create mode 100644 crates/sui-graphql-rpc/src/types/event/lookups.rs rename crates/sui-graphql-rpc/src/types/{event.rs => event/mod.rs} (50%) diff --git a/.github/workflows/bridge.yml b/.github/workflows/bridge.yml index 3bd5fea50a24e..ff3a4f8288e2b 100644 --- a/.github/workflows/bridge.yml +++ b/.github/workflows/bridge.yml @@ -92,7 +92,7 @@ jobs: - name: Install Foundry Dependencies working-directory: bridge/evm run: | - forge soldeer update + forge install https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable@v5.0.1 https://github.com/foundry-rs/forge-std@v1.3.0 https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades --no-git --no-commit - name: cargo test run: | cargo nextest run --profile ci -E 'package(sui-bridge)' @@ -114,7 +114,7 @@ jobs: - name: Install Foundry Dependencies working-directory: bridge/evm run: | - forge soldeer update + forge install https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable@v5.0.1 https://github.com/foundry-rs/forge-std@v1.3.0 https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades --no-git --no-commit - name: Check Bridge EVM Unit Tests shell: bash working-directory: bridge/evm diff --git a/bridge/evm/.gitignore b/bridge/evm/.gitignore index eede9111c4f1a..b03db232ccd9e 100644 --- a/bridge/evm/.gitignore +++ b/bridge/evm/.gitignore @@ -10,4 +10,5 @@ out*/ lcov.info broadcast/**/31337 -dependencies +lib/* + diff --git a/bridge/evm/README.md b/bridge/evm/README.md index 94667326299ca..97cbdb270a45c 100644 --- a/bridge/evm/README.md +++ b/bridge/evm/README.md @@ -1,6 +1,6 @@ # 🏄‍♂️ Quick Start -This project leverages [Foundry](https://github.com/foundry-rs/foundry) to manage dependencies (via soldeer), contract compilation, testing, deployment, and on chain interactions via Solidity scripting. +This project leverages [Foundry](https://github.com/foundry-rs/foundry) to manage dependencies, contract compilation, testing, deployment, and on chain interactions via Solidity scripting. #### Environment configuration @@ -14,7 +14,7 @@ Duplicate rename the `.env.example` file to `.env`. You'll need accounts and api To install the project dependencies, run: ```bash -forge soldeer update +forge install https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable@v5.0.1 https://github.com/foundry-rs/forge-std@v1.3.0 https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades --no-git --no-commit ``` #### Compilation @@ -28,7 +28,8 @@ forge compile #### Testing ```bash -forge test +forge clean +forge test --ffi ``` #### Coverage @@ -44,13 +45,15 @@ forge coverage > The file should be named `.json` and should have the same fields and in the same order (alphabetical) as the `example.json`. ```bash -forge script script/deploy_bridge.s.sol --rpc-url <> --broadcast --verify +forge clean +forge script script/deploy_bridge.s.sol --rpc-url <> --broadcast --verify --ffi ``` **Local deployment** ```bash -forge script script/deploy_bridge.s.sol --fork-url anvil --broadcast +forge clean +forge script script/deploy_bridge.s.sol --fork-url anvil --broadcast --ffi ``` All deployments are saved in the `broadcast` directory. diff --git a/bridge/evm/foundry.toml b/bridge/evm/foundry.toml index ba31fcbba8e08..b2a3ebfec2a6d 100644 --- a/bridge/evm/foundry.toml +++ b/bridge/evm/foundry.toml @@ -3,31 +3,20 @@ src = 'contracts' test = 'test' no_match_test = "testSkip" out = 'out' -libs = ['dependencies'] +libs = ['lib'] solc = "0.8.20" build_info = true extra_output = ["storageLayout"] fs_permissions = [{ access = "read", path = "/"}] gas_reports = ["SuiBridge"] -ffi = true - [fmt] line_length = 100 - [fuzz] runs = 1000 - [rpc_endpoints] mainnet = "${MAINNET_RPC_URL}" sepolia = "${SEPOLIA_RPC_URL}" anvil = "http://localhost:8545" - [etherscan] sepolia = { key = "${ETHERSCAN_API_KEY}" } -mainnet = { key = "${ETHERSCAN_API_KEY}" } - -[dependencies] -forge-std = "1.9.2" -openzeppelin-foundry-upgrades = "0.3.1" -"@openzeppelin-contracts-upgradeable" = "5.0.1" -"@openzeppelin-contracts" = "5.0.1" \ No newline at end of file +mainnet = { key = "${ETHERSCAN_API_KEY}" } \ No newline at end of file diff --git a/bridge/evm/remappings.txt b/bridge/evm/remappings.txt index c680ee33d8dd9..5279b569511f7 100644 --- a/bridge/evm/remappings.txt +++ b/bridge/evm/remappings.txt @@ -1,8 +1,5 @@ -@forge-std=dependencies/forge-std-1.9.2/src -@openzeppelin/foundry-upgrades=dependencies/openzeppelin-foundry-upgrades-0.3.1/src -@openzeppelin/contracts=dependencies/@openzeppelin-contracts-5.0.1 -@openzeppelin/contracts-upgradeable=dependencies/@openzeppelin-contracts-upgradeable-5.0.1 -@forge-std-1.9.2=dependencies/forge-std-1.9.2 -@openzeppelin-foundry-upgrades-0.3.1=dependencies/openzeppelin-foundry-upgrades-0.3.1 -@openzeppelin-contracts-upgradeable-5.0.1=dependencies/@openzeppelin-contracts-upgradeable-5.0.1 -@openzeppelin-contracts-5.0.1=dependencies/@openzeppelin-contracts-5.0.1 \ No newline at end of file +@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/ +@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ +@openzeppelin/openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/ +ds-test/=lib/forge-std/lib/ds-test/src/ +forge-std/=lib/openzeppelin-foundry-upgrades/lib/forge-std/src/ \ No newline at end of file diff --git a/bridge/evm/soldeer.lock b/bridge/evm/soldeer.lock deleted file mode 100644 index 20bd2407a347b..0000000000000 --- a/bridge/evm/soldeer.lock +++ /dev/null @@ -1,24 +0,0 @@ - -[[dependencies]] -name = "forge-std" -version = "1.9.2" -source = "https://soldeer-revisions.s3.amazonaws.com/forge-std/1_9_2_06-08-2024_17:31:25_forge-std-1.9.2.zip" -checksum = "20fd008c7c69b6c737cc0284469d1c76497107bc3e004d8381f6d8781cb27980" - -[[dependencies]] -name = "openzeppelin-foundry-upgrades" -version = "0.3.1" -source = "https://soldeer-revisions.s3.amazonaws.com/openzeppelin-foundry-upgrades/0_3_1_25-06-2024_18:12:33_openzeppelin-foundry-upgrades.zip" -checksum = "16a43c67b7c62e4a638b669b35f7b19c98a37278811fe910750b62b6e6fdffa7" - -[[dependencies]] -name = "@openzeppelin-contracts-upgradeable" -version = "5.0.1" -source = "https://soldeer-revisions.s3.amazonaws.com/@openzeppelin-contracts-upgradeable/5_0_1_22-01-2024_13:15:10_contracts-upgradeable.zip" -checksum = "cca37ad1d376a5c3954d1c2a8d2675339f182eee535caa7ba7ebf8d589a2c19a" - -[[dependencies]] -name = "@openzeppelin-contracts" -version = "5.0.1" -source = "https://soldeer-revisions.s3.amazonaws.com/@openzeppelin-contracts/5_0_1_22-01-2024_13:14:01_contracts.zip" -checksum = "c256cbf6f5f38d3b65c7528bbffb530d0bdb818a20c9d5b61235a829202d7df7" diff --git a/crates/sui-graphql-e2e-tests/tests/event_connection/combo_filter_error.exp b/crates/sui-graphql-e2e-tests/tests/event_connection/combo_filter_error.exp new file mode 100644 index 0000000000000..2e134867efbdc --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/event_connection/combo_filter_error.exp @@ -0,0 +1,43 @@ +processed 5 tasks + +init: +A: object(0,0), B: object(0,1) + +task 1, lines 9-28: +//# publish +created: object(1,0) +mutated: object(0,2) +gas summary: computation_cost: 1000000, storage_cost: 5380800, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2, line 30: +//# run Test::M2::emit_emit_a --sender A --args 20 +events: Event { package_id: Test, transaction_module: Identifier("M2"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [20, 0, 0, 0, 0, 0, 0, 0] } +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 3, line 32: +//# create-checkpoint +Checkpoint created: 1 + +task 4, lines 34-51: +//# run-graphql +Response: { + "data": null, + "errors": [ + { + "message": "Filtering by both emitting module and event type is not supported", + "locations": [ + { + "line": 2, + "column": 3 + } + ], + "path": [ + "events" + ], + "extensions": { + "code": "BAD_USER_INPUT" + } + } + ] +} diff --git a/crates/sui-graphql-e2e-tests/tests/event_connection/combo_filter_error.move b/crates/sui-graphql-e2e-tests/tests/event_connection/combo_filter_error.move new file mode 100644 index 0000000000000..ad38316463e76 --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/event_connection/combo_filter_error.move @@ -0,0 +1,51 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Tests that fetching events filtered on both emitting module and event would result +// in an error. + +//# init --protocol-version 51 --addresses Test=0x0 --accounts A B --simulator + +//# publish +module Test::M1 { + use sui::event; + + public struct EventA has copy, drop { + new_value: u64 + } + + public fun emit_a(value: u64) { + event::emit(EventA { new_value: value }) + } +} + +module Test::M2 { + use Test::M1; + + public fun emit_emit_a(value: u64) { + M1::emit_a(value); + } +} + +//# run Test::M2::emit_emit_a --sender A --args 20 + +//# create-checkpoint + +//# run-graphql +{ + events(filter: {sender: "@{A}", emittingModule: "@{Test}::M2", eventType: "@{Test}::M1::EventA"}) { + nodes { + sendingModule { + name + } + type { + repr + } + sender { + address + } + json + bcs + } + } +} diff --git a/crates/sui-graphql-e2e-tests/tests/event_connection/no_filter.exp b/crates/sui-graphql-e2e-tests/tests/event_connection/no_filter.exp new file mode 100644 index 0000000000000..3d916fe5b9508 --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/event_connection/no_filter.exp @@ -0,0 +1,254 @@ +processed 6 tasks + +init: +A: object(0,0) + +task 1, lines 6-25: +//# publish +created: object(1,0) +mutated: object(0,1) +gas summary: computation_cost: 1000000, storage_cost: 4970400, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2, line 27: +//# run Test::M1::emit --sender A --args 0 +events: Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [0, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [1, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [2, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [3, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [4, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [5, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [6, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [7, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [8, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [9, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [10, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [11, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [12, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [13, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [14, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [15, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [16, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [17, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [18, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [19, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [20, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [21, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [22, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [23, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [24, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [25, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [26, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [27, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [28, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [29, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [30, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [31, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [32, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [33, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [34, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [35, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [36, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [37, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [38, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [39, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [40, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [41, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [42, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [43, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [44, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [45, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [46, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [47, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [48, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [49, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [50, 0, 0, 0, 0, 0, 0, 0] } +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 3, line 29: +//# create-checkpoint +Checkpoint created: 1 + +task 4, lines 31-44: +//# run-graphql +Response: { + "data": { + "events": { + "pageInfo": { + "hasPreviousPage": false, + "hasNextPage": true, + "startCursor": "eyJ0eCI6MiwiZSI6MCwiYyI6MX0", + "endCursor": "eyJ0eCI6MiwiZSI6MTksImMiOjF9" + }, + "nodes": [ + { + "json": { + "new_value": "0" + } + }, + { + "json": { + "new_value": "1" + } + }, + { + "json": { + "new_value": "2" + } + }, + { + "json": { + "new_value": "3" + } + }, + { + "json": { + "new_value": "4" + } + }, + { + "json": { + "new_value": "5" + } + }, + { + "json": { + "new_value": "6" + } + }, + { + "json": { + "new_value": "7" + } + }, + { + "json": { + "new_value": "8" + } + }, + { + "json": { + "new_value": "9" + } + }, + { + "json": { + "new_value": "10" + } + }, + { + "json": { + "new_value": "11" + } + }, + { + "json": { + "new_value": "12" + } + }, + { + "json": { + "new_value": "13" + } + }, + { + "json": { + "new_value": "14" + } + }, + { + "json": { + "new_value": "15" + } + }, + { + "json": { + "new_value": "16" + } + }, + { + "json": { + "new_value": "17" + } + }, + { + "json": { + "new_value": "18" + } + }, + { + "json": { + "new_value": "19" + } + } + ] + } + } +} + +task 5, lines 46-59: +//# run-graphql --cursors {"tx":2,"e":19,"c":1} +Response: { + "data": { + "events": { + "pageInfo": { + "hasPreviousPage": true, + "hasNextPage": true, + "startCursor": "eyJ0eCI6MiwiZSI6MjAsImMiOjF9", + "endCursor": "eyJ0eCI6MiwiZSI6MzksImMiOjF9" + }, + "nodes": [ + { + "json": { + "new_value": "20" + } + }, + { + "json": { + "new_value": "21" + } + }, + { + "json": { + "new_value": "22" + } + }, + { + "json": { + "new_value": "23" + } + }, + { + "json": { + "new_value": "24" + } + }, + { + "json": { + "new_value": "25" + } + }, + { + "json": { + "new_value": "26" + } + }, + { + "json": { + "new_value": "27" + } + }, + { + "json": { + "new_value": "28" + } + }, + { + "json": { + "new_value": "29" + } + }, + { + "json": { + "new_value": "30" + } + }, + { + "json": { + "new_value": "31" + } + }, + { + "json": { + "new_value": "32" + } + }, + { + "json": { + "new_value": "33" + } + }, + { + "json": { + "new_value": "34" + } + }, + { + "json": { + "new_value": "35" + } + }, + { + "json": { + "new_value": "36" + } + }, + { + "json": { + "new_value": "37" + } + }, + { + "json": { + "new_value": "38" + } + }, + { + "json": { + "new_value": "39" + } + } + ] + } + } +} diff --git a/crates/sui-graphql-e2e-tests/tests/event_connection/no_filter.move b/crates/sui-graphql-e2e-tests/tests/event_connection/no_filter.move new file mode 100644 index 0000000000000..aaca18be1a12a --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/event_connection/no_filter.move @@ -0,0 +1,59 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --protocol-version 48 --addresses Test=0x0 --accounts A --simulator + +//# publish +module Test::M1 { + use sui::event; + + public struct EventA has copy, drop { + new_value: u64 + } + + public entry fun no_emit(value: u64): u64 { + value + } + + public entry fun emit(value: u64) { + let mut i = 0; + while (i < 51) { + event::emit(EventA { new_value: value + i }); + i = i + 1; + } + } +} + +//# run Test::M1::emit --sender A --args 0 + +//# create-checkpoint + +//# run-graphql +{ + events { + pageInfo { + hasPreviousPage + hasNextPage + startCursor + endCursor + } + nodes { + json + } + } +} + +//# run-graphql --cursors {"tx":2,"e":19,"c":1} +{ + events(after: "@{cursor_0}") { + pageInfo { + hasPreviousPage + hasNextPage + startCursor + endCursor + } + nodes { + json + } + } +} diff --git a/crates/sui-graphql-e2e-tests/tests/event_connection/tx_digest.exp b/crates/sui-graphql-e2e-tests/tests/event_connection/tx_digest.exp new file mode 100644 index 0000000000000..12f3b6c2ebbb3 --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/event_connection/tx_digest.exp @@ -0,0 +1,326 @@ +processed 21 tasks + +init: +A: object(0,0), B: object(0,1) + +task 1, lines 10-26: +//# publish +created: object(1,0) +mutated: object(0,2) +gas summary: computation_cost: 1000000, storage_cost: 4795600, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2, line 28: +//# run Test::M1::no_emit --sender A --args 0 +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 3, line 30: +//# run Test::M1::emit_2 --sender A --args 2 +events: Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [2, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [3, 0, 0, 0, 0, 0, 0, 0] } +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 4, line 32: +//# run Test::M1::emit_2 --sender B --args 4 +events: Event { package_id: Test, transaction_module: Identifier("M1"), sender: B, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [4, 0, 0, 0, 0, 0, 0, 0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: B, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [5, 0, 0, 0, 0, 0, 0, 0] } +mutated: object(0,1) +gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 5, line 34: +//# create-checkpoint +Checkpoint created: 1 + +task 6, lines 36-43: +//# run-graphql +Response: { + "data": { + "transactionBlocks": { + "nodes": [ + { + "digest": "AXoD3PWjAdYov3o7FaWgAqJA8RmvQrjwxGxAi2MNEujz" + }, + { + "digest": "3nuQk9o2VVoqWbF6gS5vBTPwVLhMRbFJREDCvQJUavZ2" + }, + { + "digest": "5VAhspujQVcgJNvqe9Ed8BuFTeZdTRCCTzS6WwSZ9Dke" + }, + { + "digest": "8n1pk5fYM7v7tvsh4dcKxLRy8uf3he24FZCwdEKi9cSj" + }, + { + "digest": "4dqR1zeomDMNHbUAZjooSZXrosPEb67gvvsUFeUSet9v" + } + ] + } + } +} + +task 7, lines 45-55: +//# run-graphql +Response: { + "data": { + "events": { + "edges": [ + { + "cursor": "eyJ0eCI6MywiZSI6MCwiYyI6MX0", + "node": { + "json": { + "new_value": "2" + } + } + }, + { + "cursor": "eyJ0eCI6MywiZSI6MSwiYyI6MX0", + "node": { + "json": { + "new_value": "3" + } + } + } + ] + } + } +} + +task 8, lines 57-68: +//# run-graphql --cursors {"tx":3,"e":1,"c":1} +Response: { + "data": { + "events": { + "edges": [] + } + } +} + +task 9, lines 70-83: +//# run-graphql --cursors {"tx":1,"e":1,"c":1} +Response: { + "data": { + "events": { + "edges": [] + } + } +} + +task 10, lines 86-96: +//# run-graphql +Response: { + "data": { + "events": { + "edges": [ + { + "cursor": "eyJ0eCI6NCwiZSI6MCwiYyI6MX0", + "node": { + "json": { + "new_value": "4" + } + } + }, + { + "cursor": "eyJ0eCI6NCwiZSI6MSwiYyI6MX0", + "node": { + "json": { + "new_value": "5" + } + } + } + ] + } + } +} + +task 11, lines 98-108: +//# run-graphql --cursors {"tx":4,"e":0,"c":1} +Response: { + "data": { + "events": { + "edges": [ + { + "cursor": "eyJ0eCI6NCwiZSI6MSwiYyI6MX0", + "node": { + "json": { + "new_value": "5" + } + } + } + ] + } + } +} + +task 12, lines 111-121: +//# run-graphql +Response: { + "data": { + "events": { + "edges": [ + { + "cursor": "eyJ0eCI6MywiZSI6MCwiYyI6MX0", + "node": { + "json": { + "new_value": "2" + } + } + }, + { + "cursor": "eyJ0eCI6MywiZSI6MSwiYyI6MX0", + "node": { + "json": { + "new_value": "3" + } + } + } + ] + } + } +} + +task 13, lines 123-134: +//# run-graphql --cursors {"tx":3,"e":1,"c":1} +Response: { + "data": { + "events": { + "edges": [ + { + "cursor": "eyJ0eCI6MywiZSI6MCwiYyI6MX0", + "node": { + "json": { + "new_value": "2" + } + } + } + ] + } + } +} + +task 14, lines 136-149: +//# run-graphql --cursors {"tx":4,"e":1,"c":1} +Response: { + "data": { + "events": { + "edges": [] + } + } +} + +task 15, lines 152-162: +//# run-graphql +Response: { + "data": { + "events": { + "edges": [ + { + "cursor": "eyJ0eCI6NCwiZSI6MCwiYyI6MX0", + "node": { + "json": { + "new_value": "4" + } + } + }, + { + "cursor": "eyJ0eCI6NCwiZSI6MSwiYyI6MX0", + "node": { + "json": { + "new_value": "5" + } + } + } + ] + } + } +} + +task 16, lines 164-174: +//# run-graphql --cursors {"tx":4,"e":1,"c":1} +Response: { + "data": { + "events": { + "edges": [ + { + "cursor": "eyJ0eCI6NCwiZSI6MCwiYyI6MX0", + "node": { + "json": { + "new_value": "4" + } + } + } + ] + } + } +} + +task 17, lines 176-187: +//# run-graphql +Response: { + "data": { + "events": { + "edges": [ + { + "cursor": "eyJ0eCI6MywiZSI6MCwiYyI6MX0", + "node": { + "json": { + "new_value": "2" + } + } + }, + { + "cursor": "eyJ0eCI6MywiZSI6MSwiYyI6MX0", + "node": { + "json": { + "new_value": "3" + } + } + } + ] + } + } +} + +task 18, lines 189-200: +//# run-graphql +Response: { + "data": { + "events": { + "edges": [ + { + "cursor": "eyJ0eCI6NCwiZSI6MCwiYyI6MX0", + "node": { + "json": { + "new_value": "4" + } + } + }, + { + "cursor": "eyJ0eCI6NCwiZSI6MSwiYyI6MX0", + "node": { + "json": { + "new_value": "5" + } + } + } + ] + } + } +} + +task 19, lines 202-213: +//# run-graphql +Response: { + "data": { + "events": { + "edges": [] + } + } +} + +task 20, lines 215-226: +//# run-graphql +Response: { + "data": { + "events": { + "edges": [] + } + } +} diff --git a/crates/sui-graphql-e2e-tests/tests/event_connection/tx_digest.move b/crates/sui-graphql-e2e-tests/tests/event_connection/tx_digest.move new file mode 100644 index 0000000000000..58b17e5fd21d8 --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/event_connection/tx_digest.move @@ -0,0 +1,226 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Tests that fetching events filtered on a tx digest that has no events correctly returns no nodes. +// Also tests that fetching events filtered on a tx digest that has events returns the correct +// number of page-limit-bound nodes. + +//# init --protocol-version 48 --addresses Test=0x0 --accounts A B --simulator + +//# publish +module Test::M1 { + use sui::event; + + public struct EventA has copy, drop { + new_value: u64 + } + + public entry fun no_emit(value: u64): u64 { + value + } + + public entry fun emit_2(value: u64) { + event::emit(EventA { new_value: value }); + event::emit(EventA { new_value: value + 1}) + } +} + +//# run Test::M1::no_emit --sender A --args 0 + +//# run Test::M1::emit_2 --sender A --args 2 + +//# run Test::M1::emit_2 --sender B --args 4 + +//# create-checkpoint + +//# run-graphql +{ + transactionBlocks { + nodes { + digest + } + } +} + +//# run-graphql +{ + events(filter: {transactionDigest: "8n1pk5fYM7v7tvsh4dcKxLRy8uf3he24FZCwdEKi9cSj"}) { + edges { + cursor + node { + json + } + } + } +} + +//# run-graphql --cursors {"tx":3,"e":1,"c":1} +# When the tx digest and after cursor are on the same tx, we'll use the after cursor's event sequence number +{ + events(after: "@{cursor_0}" filter: {transactionDigest: "8n1pk5fYM7v7tvsh4dcKxLRy8uf3he24FZCwdEKi9cSj"}) { + edges { + cursor + node { + json + } + } + } +} + +//# run-graphql --cursors {"tx":1,"e":1,"c":1} +# If the after cursor does not match the transaction digest's tx sequence number, +# we will get an empty response, since it's not possible to fetch an event +# that isn't of the same tx sequence number +{ + events(after: "@{cursor_0}" filter: {transactionDigest: "8n1pk5fYM7v7tvsh4dcKxLRy8uf3he24FZCwdEKi9cSj"}) { + edges { + cursor + node { + json + } + } + } +} + + +//# run-graphql +{ + events(filter: {transactionDigest: "4dqR1zeomDMNHbUAZjooSZXrosPEb67gvvsUFeUSet9v"}) { + edges { + cursor + node { + json + } + } + } +} + +//# run-graphql --cursors {"tx":4,"e":0,"c":1} +{ + events(after: "@{cursor_0}" filter: {transactionDigest: "4dqR1zeomDMNHbUAZjooSZXrosPEb67gvvsUFeUSet9v"}) { + edges { + cursor + node { + json + } + } + } +} + + +//# run-graphql +{ + events(last: 10 filter: {transactionDigest: "8n1pk5fYM7v7tvsh4dcKxLRy8uf3he24FZCwdEKi9cSj"}) { + edges { + cursor + node { + json + } + } + } +} + +//# run-graphql --cursors {"tx":3,"e":1,"c":1} +# When the tx digest and cursor are on the same tx, we'll use the cursor's event sequence number +{ + events(last: 10 before: "@{cursor_0}" filter: {transactionDigest: "8n1pk5fYM7v7tvsh4dcKxLRy8uf3he24FZCwdEKi9cSj"}) { + edges { + cursor + node { + json + } + } + } +} + +//# run-graphql --cursors {"tx":4,"e":1,"c":1} +# If the cursor does not match the transaction digest's tx sequence number, +# we will get an empty response, since it's not possible to fetch an event +# that isn't of the same tx sequence number +{ + events(last: 10 before: "@{cursor_0}" filter: {transactionDigest: "8n1pk5fYM7v7tvsh4dcKxLRy8uf3he24FZCwdEKi9cSj"}) { + edges { + cursor + node { + json + } + } + } +} + + +//# run-graphql +{ + events(last: 10 filter: {transactionDigest: "4dqR1zeomDMNHbUAZjooSZXrosPEb67gvvsUFeUSet9v"}) { + edges { + cursor + node { + json + } + } + } +} + +//# run-graphql --cursors {"tx":4,"e":1,"c":1} +{ + events(last: 10 before: "@{cursor_0}" filter: {transactionDigest: "4dqR1zeomDMNHbUAZjooSZXrosPEb67gvvsUFeUSet9v"}) { + edges { + cursor + node { + json + } + } + } +} + +//# run-graphql +# correct sender +{ + events(filter: {sender: "@{A}" transactionDigest: "8n1pk5fYM7v7tvsh4dcKxLRy8uf3he24FZCwdEKi9cSj"}) { + edges { + cursor + node { + json + } + } + } +} + +//# run-graphql +# correct sender +{ + events(filter: {sender: "@{B}" transactionDigest: "4dqR1zeomDMNHbUAZjooSZXrosPEb67gvvsUFeUSet9v"}) { + edges { + cursor + node { + json + } + } + } +} + +//# run-graphql +# incorrect sender +{ + events(filter: {sender: "@{B}" transactionDigest: "8n1pk5fYM7v7tvsh4dcKxLRy8uf3he24FZCwdEKi9cSj"}) { + edges { + cursor + node { + json + } + } + } +} + +//# run-graphql +# incorrect sender +{ + events(filter: {sender: "@{A}" transactionDigest: "4dqR1zeomDMNHbUAZjooSZXrosPEb67gvvsUFeUSet9v"}) { + edges { + cursor + node { + json + } + } + } +} diff --git a/crates/sui-graphql-e2e-tests/tests/event_connection/type_filter.exp b/crates/sui-graphql-e2e-tests/tests/event_connection/type_filter.exp new file mode 100644 index 0000000000000..3afb31848780e --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/event_connection/type_filter.exp @@ -0,0 +1,211 @@ +processed 12 tasks + +init: +A: object(0,0), B: object(0,1) + +task 1, lines 6-34: +//# publish +created: object(1,0) +mutated: object(0,2) +gas summary: computation_cost: 1000000, storage_cost: 6604400, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2, line 36: +//# run Test::M2::emit_emit_a --sender A --args 20 +events: Event { package_id: Test, transaction_module: Identifier("M2"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [] }, contents: [20, 0, 0, 0, 0, 0, 0, 0] } +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 3, line 38: +//# create-checkpoint +Checkpoint created: 1 + +task 4, lines 40-57: +//# run-graphql +Response: { + "data": { + "events": { + "nodes": [ + { + "sendingModule": { + "name": "M2" + }, + "type": { + "repr": "0x6edb181eb03cea19a3c4b09d2d6b5de8d0a741df186d072d18b2030eb36faee1::M1::EventA" + }, + "sender": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + }, + "json": { + "new_value": "20" + }, + "bcs": "FAAAAAAAAAA=" + } + ] + } + } +} + +task 5, line 59: +//# run Test::M2::emit_b --sender A --args 42 +events: Event { package_id: Test, transaction_module: Identifier("M2"), sender: A, type_: StructTag { address: Test, module: Identifier("M2"), name: Identifier("EventB"), type_params: [] }, contents: [42, 0, 0, 0, 0, 0, 0, 0] } +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 6, line 61: +//# run Test::M2::emit_b --sender B --args 43 +events: Event { package_id: Test, transaction_module: Identifier("M2"), sender: B, type_: StructTag { address: Test, module: Identifier("M2"), name: Identifier("EventB"), type_params: [] }, contents: [43, 0, 0, 0, 0, 0, 0, 0] } +mutated: object(0,1) +gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 7, line 63: +//# create-checkpoint +Checkpoint created: 2 + +task 8, lines 65-82: +//# run-graphql +Response: { + "data": { + "events": { + "nodes": [ + { + "sendingModule": { + "name": "M2" + }, + "type": { + "repr": "0x6edb181eb03cea19a3c4b09d2d6b5de8d0a741df186d072d18b2030eb36faee1::M1::EventA" + }, + "sender": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + }, + "json": { + "new_value": "20" + }, + "bcs": "FAAAAAAAAAA=" + } + ] + } + } +} + +task 9, lines 84-101: +//# run-graphql +Response: { + "data": { + "events": { + "nodes": [ + { + "sendingModule": { + "name": "M2" + }, + "type": { + "repr": "0x6edb181eb03cea19a3c4b09d2d6b5de8d0a741df186d072d18b2030eb36faee1::M2::EventB" + }, + "sender": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + }, + "json": { + "new_value": "42" + }, + "bcs": "KgAAAAAAAAA=" + } + ] + } + } +} + +task 10, lines 103-120: +//# run-graphql +Response: { + "data": { + "events": { + "nodes": [ + { + "sendingModule": { + "name": "M2" + }, + "type": { + "repr": "0x6edb181eb03cea19a3c4b09d2d6b5de8d0a741df186d072d18b2030eb36faee1::M1::EventA" + }, + "sender": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + }, + "json": { + "new_value": "20" + }, + "bcs": "FAAAAAAAAAA=" + }, + { + "sendingModule": { + "name": "M2" + }, + "type": { + "repr": "0x6edb181eb03cea19a3c4b09d2d6b5de8d0a741df186d072d18b2030eb36faee1::M2::EventB" + }, + "sender": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + }, + "json": { + "new_value": "42" + }, + "bcs": "KgAAAAAAAAA=" + } + ] + } + } +} + +task 11, lines 122-139: +//# run-graphql +Response: { + "data": { + "events": { + "nodes": [ + { + "sendingModule": { + "name": "M2" + }, + "type": { + "repr": "0x6edb181eb03cea19a3c4b09d2d6b5de8d0a741df186d072d18b2030eb36faee1::M1::EventA" + }, + "sender": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + }, + "json": { + "new_value": "20" + }, + "bcs": "FAAAAAAAAAA=" + }, + { + "sendingModule": { + "name": "M2" + }, + "type": { + "repr": "0x6edb181eb03cea19a3c4b09d2d6b5de8d0a741df186d072d18b2030eb36faee1::M2::EventB" + }, + "sender": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + }, + "json": { + "new_value": "42" + }, + "bcs": "KgAAAAAAAAA=" + }, + { + "sendingModule": { + "name": "M2" + }, + "type": { + "repr": "0x6edb181eb03cea19a3c4b09d2d6b5de8d0a741df186d072d18b2030eb36faee1::M2::EventB" + }, + "sender": { + "address": "0xa7b032703878aa74c3126935789fd1d4d7e111d5911b09247d6963061c312b5a" + }, + "json": { + "new_value": "43" + }, + "bcs": "KwAAAAAAAAA=" + } + ] + } + } +} diff --git a/crates/sui-graphql-e2e-tests/tests/event_connection/type_filter.move b/crates/sui-graphql-e2e-tests/tests/event_connection/type_filter.move new file mode 100644 index 0000000000000..0ca546a3a94ef --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/event_connection/type_filter.move @@ -0,0 +1,140 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --protocol-version 51 --addresses Test=0x0 --accounts A B --simulator + +//# publish +module Test::M1 { + use sui::event; + + public struct EventA has copy, drop { + new_value: u64 + } + + public fun emit_a(value: u64) { + event::emit(EventA { new_value: value }) + } +} + +module Test::M2 { + use sui::event; + use Test::M1; + + public struct EventB has copy, drop { + new_value: u64 + } + + public fun emit_emit_a(value: u64) { + M1::emit_a(value); + } + + public fun emit_b(value: u64) { + event::emit(EventB { new_value: value }) + } +} + +//# run Test::M2::emit_emit_a --sender A --args 20 + +//# create-checkpoint + +//# run-graphql +{ + events(filter: {sender: "@{A}", eventType: "@{Test}::M1::EventA"}) { + nodes { + sendingModule { + name + } + type { + repr + } + sender { + address + } + json + bcs + } + } +} + +//# run Test::M2::emit_b --sender A --args 42 + +//# run Test::M2::emit_b --sender B --args 43 + +//# create-checkpoint + +//# run-graphql +{ + events(filter: {sender: "@{A}", eventType: "@{Test}::M1"}) { + nodes { + sendingModule { + name + } + type { + repr + } + sender { + address + } + json + bcs + } + } +} + +//# run-graphql +{ + events(filter: {sender: "@{A}", eventType: "@{Test}::M2"}) { + nodes { + sendingModule { + name + } + type { + repr + } + sender { + address + } + json + bcs + } + } +} + +//# run-graphql +{ + events(filter: {sender: "@{A}", eventType: "@{Test}"}) { + nodes { + sendingModule { + name + } + type { + repr + } + sender { + address + } + json + bcs + } + } +} + +//# run-graphql +{ + events(filter: {eventType: "@{Test}"}) { + nodes { + sendingModule { + name + } + type { + repr + } + sender { + address + } + json + bcs + } + } +} + diff --git a/crates/sui-graphql-e2e-tests/tests/event_connection/type_param_filter.exp b/crates/sui-graphql-e2e-tests/tests/event_connection/type_param_filter.exp new file mode 100644 index 0000000000000..383af1455f8f3 --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/event_connection/type_param_filter.exp @@ -0,0 +1,182 @@ +processed 10 tasks + +init: +A: object(0,0), B: object(0,1) + +task 1, lines 6-29: +//# publish +created: object(1,0) +mutated: object(0,2) +gas summary: computation_cost: 1000000, storage_cost: 5996400, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2, line 32: +//# run Test::M1::emit_T1 --sender A +events: Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [Struct(StructTag { address: Test, module: Identifier("M1"), name: Identifier("T1"), type_params: [] })] }, contents: [0] } +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 3, line 34: +//# run Test::M1::emit_T2 --sender A +events: Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [Struct(StructTag { address: Test, module: Identifier("M1"), name: Identifier("T2"), type_params: [] })] }, contents: [0] } +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 4, line 36: +//# run Test::M1::emit_both --sender A +events: Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [Struct(StructTag { address: Test, module: Identifier("M1"), name: Identifier("T1"), type_params: [] })] }, contents: [0] }, Event { package_id: Test, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: Test, module: Identifier("M1"), name: Identifier("EventA"), type_params: [Struct(StructTag { address: Test, module: Identifier("M1"), name: Identifier("T2"), type_params: [] })] }, contents: [0] } +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 5, line 38: +//# create-checkpoint +Checkpoint created: 1 + +task 6, lines 40-47: +//# run-graphql +Response: { + "data": { + "transactionBlocks": { + "nodes": [ + { + "digest": "J7mHXcoa7LXwyjzZUWsk8zvYZjek359TM4d2hQK4LGHo" + }, + { + "digest": "Ch3i5cdtNPU5v8oSg3V5cdKtZDa3YjqjMd7Qh4NQLAx6" + }, + { + "digest": "6taTf6v2NQCFtd9A4nw3mBbkHwUw8RUoJjqQGSB5cBNt" + }, + { + "digest": "AEXpVZ7Vpsk7ZSTsR2QNPT7zhq8oqpJXRGgx3kAaTdn" + }, + { + "digest": "9nu1ivpL9hHcbJ9GwGfmD3Kuet5w74t2GBp8f1Ggy3UD" + } + ] + } + } +} + +task 7, lines 49-62: +//# run-graphql +Response: { + "data": { + "events": { + "nodes": [ + { + "type": { + "repr": "0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::EventA<0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::T1>" + }, + "sender": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + }, + "json": { + "value": { + "dummy_field": false + } + } + }, + { + "type": { + "repr": "0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::EventA<0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::T2>" + }, + "sender": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + }, + "json": { + "value": { + "dummy_field": false + } + } + }, + { + "type": { + "repr": "0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::EventA<0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::T1>" + }, + "sender": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + }, + "json": { + "value": { + "dummy_field": false + } + } + }, + { + "type": { + "repr": "0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::EventA<0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::T2>" + }, + "sender": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + }, + "json": { + "value": { + "dummy_field": false + } + } + } + ] + } + } +} + +task 8, lines 64-77: +//# run-graphql +Response: { + "data": { + "events": { + "nodes": [ + { + "type": { + "repr": "0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::EventA<0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::T1>" + }, + "sender": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + }, + "json": { + "value": { + "dummy_field": false + } + } + }, + { + "type": { + "repr": "0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::EventA<0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::T1>" + }, + "sender": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + }, + "json": { + "value": { + "dummy_field": false + } + } + } + ] + } + } +} + +task 9, lines 79-92: +//# run-graphql +Response: { + "data": { + "events": { + "nodes": [ + { + "type": { + "repr": "0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::EventA<0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::T2>" + }, + "sender": { + "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" + }, + "json": { + "value": { + "dummy_field": false + } + } + } + ] + } + } +} diff --git a/crates/sui-graphql-e2e-tests/tests/event_connection/type_param_filter.move b/crates/sui-graphql-e2e-tests/tests/event_connection/type_param_filter.move new file mode 100644 index 0000000000000..128362999b50b --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/event_connection/type_param_filter.move @@ -0,0 +1,92 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --protocol-version 51 --addresses Test=0x0 --accounts A B --simulator + +//# publish +module Test::M1 { + use sui::event; + + public struct T1 has copy, drop {} + public struct T2 has copy, drop {} + + public struct EventA has copy, drop { + value: T + } + + public fun emit_T1() { + event::emit(EventA { value: T1 {} }) + } + + public fun emit_T2() { + event::emit(EventA { value: T2 {} }) + } + + public fun emit_both() { + event::emit(EventA { value: T1 {} }); + event::emit(EventA { value: T2 {} }) + } +} + + +//# run Test::M1::emit_T1 --sender A + +//# run Test::M1::emit_T2 --sender A + +//# run Test::M1::emit_both --sender A + +//# create-checkpoint + +//# run-graphql +{ + transactionBlocks { + nodes { + digest + } + } +} + +//# run-graphql +{ + events(filter: {eventType: "@{Test}::M1::EventA"}) { + nodes { + type { + repr + } + sender { + address + } + json + } + } +} + +//# run-graphql +{ + events(filter: {eventType: "@{Test}::M1::EventA<@{Test}::M1::T1>"}) { + nodes { + type { + repr + } + sender { + address + } + json + } + } +} + +//# run-graphql +{ + events(filter: {eventType: "@{Test}::M1::EventA<@{Test}::M1::T2>", transactionDigest: "9nu1ivpL9hHcbJ9GwGfmD3Kuet5w74t2GBp8f1Ggy3UD"}) { + nodes { + type { + repr + } + sender { + address + } + json + } + } +} diff --git a/crates/sui-graphql-rpc/schema.graphql b/crates/sui-graphql-rpc/schema.graphql index defc55cc329d2..faca90cc4e354 100644 --- a/crates/sui-graphql-rpc/schema.graphql +++ b/crates/sui-graphql-rpc/schema.graphql @@ -1272,6 +1272,8 @@ input EventFilter { PTB and emits an event. Modules can be filtered by their package, or package::module. + We currently do not support filtering by emitting module and event type + at the same time so if both are provided in one filter, the query will error. """ emittingModule: String """ @@ -3312,7 +3314,9 @@ type Query { """ transactionBlocks(first: Int, after: String, last: Int, before: String, filter: TransactionBlockFilter, scanLimit: Int): TransactionBlockConnection! """ - The events that exist in the network. + Query events that are emitted in the network. + We currently do not support filtering by emitting module and event type + at the same time so if both are provided in one filter, the query will error. """ events(first: Int, after: String, last: Int, before: String, filter: EventFilter): EventConnection! """ diff --git a/crates/sui-graphql-rpc/src/types/event/cursor.rs b/crates/sui-graphql-rpc/src/types/event/cursor.rs new file mode 100644 index 0000000000000..9a64399f1fab6 --- /dev/null +++ b/crates/sui-graphql-rpc/src/types/event/cursor.rs @@ -0,0 +1,183 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ + consistency::Checkpointed, + filter, + raw_query::RawQuery, + types::cursor::{self, Paginated, RawPaginated, ScanLimited, Target}, +}; +use diesel::{ + backend::Backend, + deserialize::{self, FromSql, QueryableByName}, + row::NamedRow, + BoolExpressionMethods, ExpressionMethods, QueryDsl, +}; +use serde::{Deserialize, Serialize}; +use sui_indexer::{models::events::StoredEvent, schema::events}; + +use super::Query; + +/// Contents of an Event's cursor. +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] +pub(crate) struct EventKey { + /// Transaction Sequence Number + pub tx: u64, + + /// Event Sequence Number + pub e: u64, + + /// The checkpoint sequence number this was viewed at. + #[serde(rename = "c")] + pub checkpoint_viewed_at: u64, +} + +pub(crate) type Cursor = cursor::JsonCursor; + +/// Results from raw queries in Diesel can only be deserialized into structs that implement +/// `QueryableByName`. This struct is used to represent a row of `tx_sequence_number` and +/// `event_sequence_number` returned from subqueries against event lookup tables. +#[derive(Clone, Debug)] +pub struct EvLookup { + pub tx: i64, + pub ev: i64, +} + +impl Paginated for StoredEvent { + type Source = events::table; + + fn filter_ge(cursor: &Cursor, query: Query) -> Query { + use events::dsl::{event_sequence_number as event, tx_sequence_number as tx}; + query.filter( + tx.gt(cursor.tx as i64) + .or(tx.eq(cursor.tx as i64).and(event.ge(cursor.e as i64))), + ) + } + + fn filter_le(cursor: &Cursor, query: Query) -> Query { + use events::dsl::{event_sequence_number as event, tx_sequence_number as tx}; + query.filter( + tx.lt(cursor.tx as i64) + .or(tx.eq(cursor.tx as i64).and(event.le(cursor.e as i64))), + ) + } + + fn order(asc: bool, query: Query) -> Query { + use events::dsl; + if asc { + query + .order_by(dsl::tx_sequence_number.asc()) + .then_order_by(dsl::event_sequence_number.asc()) + } else { + query + .order_by(dsl::tx_sequence_number.desc()) + .then_order_by(dsl::event_sequence_number.desc()) + } + } +} + +impl RawPaginated for StoredEvent { + fn filter_ge(cursor: &Cursor, query: RawQuery) -> RawQuery { + filter!( + query, + format!( + "ROW(tx_sequence_number, event_sequence_number) >= ({}, {})", + cursor.tx, cursor.e + ) + ) + } + + fn filter_le(cursor: &Cursor, query: RawQuery) -> RawQuery { + filter!( + query, + format!( + "ROW(tx_sequence_number, event_sequence_number) <= ({}, {})", + cursor.tx, cursor.e + ) + ) + } + + fn order(asc: bool, query: RawQuery) -> RawQuery { + if asc { + query.order_by("tx_sequence_number ASC, event_sequence_number ASC") + } else { + query.order_by("tx_sequence_number DESC, event_sequence_number DESC") + } + } +} + +impl Target for StoredEvent { + fn cursor(&self, checkpoint_viewed_at: u64) -> Cursor { + Cursor::new(EventKey { + tx: self.tx_sequence_number as u64, + e: self.event_sequence_number as u64, + checkpoint_viewed_at, + }) + } +} + +impl Checkpointed for Cursor { + fn checkpoint_viewed_at(&self) -> u64 { + self.checkpoint_viewed_at + } +} + +impl ScanLimited for Cursor {} + +impl Target for EvLookup { + fn cursor(&self, checkpoint_viewed_at: u64) -> Cursor { + Cursor::new(EventKey { + tx: self.tx as u64, + e: self.ev as u64, + checkpoint_viewed_at, + }) + } +} + +impl RawPaginated for EvLookup { + fn filter_ge(cursor: &Cursor, query: RawQuery) -> RawQuery { + filter!( + query, + format!( + "ROW(tx_sequence_number, event_sequence_number) >= ({}, {})", + cursor.tx, cursor.e + ) + ) + } + + fn filter_le(cursor: &Cursor, query: RawQuery) -> RawQuery { + filter!( + query, + format!( + "ROW(tx_sequence_number, event_sequence_number) <= ({}, {})", + cursor.tx, cursor.e + ) + ) + } + + fn order(asc: bool, query: RawQuery) -> RawQuery { + if asc { + query.order_by("tx_sequence_number ASC, event_sequence_number ASC") + } else { + query.order_by("tx_sequence_number DESC, event_sequence_number DESC") + } + } +} + +/// `sql_query` raw queries require `QueryableByName`. The default implementation looks for a table +/// based on the struct name, and it also expects the struct's fields to reflect the table's +/// columns. We can override this behavior by implementing `QueryableByName` for our struct. For +/// `EvLookup`, its fields are derived from the common `tx_sequence_number` and +/// `event_sequence_number` columns for all events-related tables. +impl QueryableByName for EvLookup +where + DB: Backend, + i64: FromSql, +{ + fn build<'a>(row: &impl NamedRow<'a, DB>) -> deserialize::Result { + let tx = NamedRow::get::(row, "tx_sequence_number")?; + let ev = NamedRow::get::(row, "event_sequence_number")?; + + Ok(Self { tx, ev }) + } +} diff --git a/crates/sui-graphql-rpc/src/types/event/filter.rs b/crates/sui-graphql-rpc/src/types/event/filter.rs new file mode 100644 index 0000000000000..0a02a31be8969 --- /dev/null +++ b/crates/sui-graphql-rpc/src/types/event/filter.rs @@ -0,0 +1,44 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::types::{ + digest::Digest, + sui_address::SuiAddress, + type_filter::{ModuleFilter, TypeFilter}, +}; +use async_graphql::*; + +#[derive(InputObject, Clone, Default)] +pub(crate) struct EventFilter { + pub sender: Option, + pub transaction_digest: Option, + // Enhancement (post-MVP) + // after_checkpoint + // before_checkpoint + /// Events emitted by a particular module. An event is emitted by a + /// particular module if some function in the module is called by a + /// PTB and emits an event. + /// + /// Modules can be filtered by their package, or package::module. + /// We currently do not support filtering by emitting module and event type + /// at the same time so if both are provided in one filter, the query will error. + pub emitting_module: Option, + + /// This field is used to specify the type of event emitted. + /// + /// Events can be filtered by their type's package, package::module, + /// or their fully qualified type name. + /// + /// Generic types can be queried by either the generic type name, e.g. + /// `0x2::coin::Coin`, or by the full type name, such as + /// `0x2::coin::Coin<0x2::sui::SUI>`. + pub event_type: Option, + // Enhancement (post-MVP) + // pub start_time + // pub end_time + + // Enhancement (post-MVP) + // pub any + // pub all + // pub not +} diff --git a/crates/sui-graphql-rpc/src/types/event/lookups.rs b/crates/sui-graphql-rpc/src/types/event/lookups.rs new file mode 100644 index 0000000000000..93a9a55bf6e6b --- /dev/null +++ b/crates/sui-graphql-rpc/src/types/event/lookups.rs @@ -0,0 +1,158 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ + data::pg::bytea_literal, + filter, query, + raw_query::RawQuery, + types::{ + cursor::Page, + digest::Digest, + sui_address::SuiAddress, + type_filter::{ModuleFilter, TypeFilter}, + }, +}; + +use std::fmt::Write; + +use super::Cursor; + +fn select_ev(sender: Option, from: &str) -> RawQuery { + let query = query!(format!( + "SELECT tx_sequence_number, event_sequence_number FROM {}", + from + )); + + if let Some(sender) = sender { + return query.filter(format!("sender = {}", bytea_literal(sender.as_slice()))); + } + + query +} + +pub(crate) fn select_sender(sender: SuiAddress) -> RawQuery { + select_ev(Some(sender), "event_senders") +} + +pub(crate) fn select_event_type(event_type: &TypeFilter, sender: Option) -> RawQuery { + match event_type { + TypeFilter::ByModule(ModuleFilter::ByPackage(p)) => { + filter!( + select_ev(sender, "event_struct_package"), + format!("package = {}", bytea_literal(p.as_slice())) + ) + } + TypeFilter::ByModule(ModuleFilter::ByModule(p, m)) => { + filter!( + select_ev(sender, "event_struct_module"), + format!( + "package = {} and module = {{}}", + bytea_literal(p.as_slice()) + ), + m + ) + } + TypeFilter::ByType(tag) => { + let package = tag.address; + let module = tag.module.to_string(); + let mut name = tag.name.as_str().to_owned(); + let (table, col_name) = if tag.type_params.is_empty() { + ("event_struct_name", "type_name") + } else { + let mut prefix = "<"; + for param in &tag.type_params { + name += prefix; + // SAFETY: write! to String always succeeds. + write!( + name, + "{}", + param.to_canonical_display(/* with_prefix */ true) + ) + .unwrap(); + prefix = ", "; + } + name += ">"; + ("event_struct_instantiation", "type_instantiation") + }; + + filter!( + select_ev(sender, table), + format!( + "package = {} and module = {{}} and {} = {{}}", + bytea_literal(package.as_slice()), + col_name + ), + module, + name + ) + } + } +} + +pub(crate) fn select_emit_module( + emit_module: &ModuleFilter, + sender: Option, +) -> RawQuery { + match emit_module { + ModuleFilter::ByPackage(p) => { + filter!( + select_ev(sender, "event_emit_package"), + format!("package = {}", bytea_literal(p.as_slice())) + ) + } + ModuleFilter::ByModule(p, m) => { + filter!( + select_ev(sender, "event_emit_module"), + format!( + "package = {} and module = {{}}", + bytea_literal(p.as_slice()) + ), + m + ) + } + } +} + +/// Adds filters to bound an events query from above and below based on cursors and filters. The +/// query will always at least be bounded by `tx_hi`, the current exclusive upperbound on +/// transaction sequence numbers, based on the consistency cursor. +pub(crate) fn add_bounds( + mut query: RawQuery, + tx_digest_filter: &Option, + page: &Page, + tx_hi: i64, +) -> RawQuery { + query = filter!(query, format!("tx_sequence_number < {}", tx_hi)); + + if let Some(after) = page.after() { + query = filter!( + query, + format!( + "ROW(tx_sequence_number, event_sequence_number) >= ({}, {})", + after.tx, after.e + ) + ); + } + + if let Some(before) = page.before() { + query = filter!( + query, + format!( + "ROW(tx_sequence_number, event_sequence_number) <= ({}, {})", + before.tx, before.e + ) + ); + } + + if let Some(digest) = tx_digest_filter { + query = filter!( + query, + format!( + "tx_sequence_number = (SELECT tx_sequence_number FROM tx_digests WHERE tx_digest = {})", + bytea_literal(digest.as_slice()), + ) + ); + } + + query +} diff --git a/crates/sui-graphql-rpc/src/types/event.rs b/crates/sui-graphql-rpc/src/types/event/mod.rs similarity index 50% rename from crates/sui-graphql-rpc/src/types/event.rs rename to crates/sui-graphql-rpc/src/types/event/mod.rs index cb558c2fba6c3..10c130c7f43c1 100644 --- a/crates/sui-graphql-rpc/src/types/event.rs +++ b/crates/sui-graphql-rpc/src/types/event/mod.rs @@ -3,28 +3,33 @@ use std::str::FromStr; -use super::cursor::{self, Page, Paginated, ScanLimited, Target}; -use super::digest::Digest; -use super::type_filter::{ModuleFilter, TypeFilter}; +use super::cursor::{Page, Target}; use super::{ address::Address, base64::Base64, date_time::DateTime, move_module::MoveModule, - move_value::MoveValue, sui_address::SuiAddress, + move_value::MoveValue, }; -use crate::consistency::Checkpointed; -use crate::data::{self, QueryExecutor}; +use crate::data::{self, DbConnection, QueryExecutor}; +use crate::query; use crate::{data::Db, error::Error}; use async_graphql::connection::{Connection, CursorType, Edge}; use async_graphql::*; -use diesel::{BoolExpressionMethods, ExpressionMethods, NullableExpressionMethods, QueryDsl}; -use serde::{Deserialize, Serialize}; +use cursor::EvLookup; +use diesel::{ExpressionMethods, QueryDsl}; +use lookups::{add_bounds, select_emit_module, select_event_type, select_sender}; use sui_indexer::models::{events::StoredEvent, transactions::StoredTransaction}; -use sui_indexer::schema::{events, transactions, tx_senders}; +use sui_indexer::schema::{checkpoints, events}; use sui_types::base_types::ObjectID; use sui_types::Identifier; use sui_types::{ base_types::SuiAddress as NativeSuiAddress, event::Event as NativeEvent, parse_sui_struct_tag, }; +mod cursor; +mod filter; +mod lookups; +pub(crate) use cursor::Cursor; +pub(crate) use filter::EventFilter; + /// A Sui node emits one of the following events: /// Move event /// Publish event @@ -40,56 +45,8 @@ pub(crate) struct Event { pub checkpoint_viewed_at: u64, } -/// Contents of an Event's cursor. -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] -pub(crate) struct EventKey { - /// Transaction Sequence Number - tx: u64, - - /// Event Sequence Number - e: u64, - - /// The checkpoint sequence number this was viewed at. - #[serde(rename = "c")] - checkpoint_viewed_at: u64, -} - -pub(crate) type Cursor = cursor::JsonCursor; type Query = data::Query; -#[derive(InputObject, Clone, Default)] -pub(crate) struct EventFilter { - pub sender: Option, - pub transaction_digest: Option, - // Enhancement (post-MVP) - // after_checkpoint - // before_checkpoint - /// Events emitted by a particular module. An event is emitted by a - /// particular module if some function in the module is called by a - /// PTB and emits an event. - /// - /// Modules can be filtered by their package, or package::module. - pub emitting_module: Option, - - /// This field is used to specify the type of event emitted. - /// - /// Events can be filtered by their type's package, package::module, - /// or their fully qualified type name. - /// - /// Generic types can be queried by either the generic type name, e.g. - /// `0x2::coin::Coin`, or by the full type name, such as - /// `0x2::coin::Coin<0x2::sui::SUI>`. - pub event_type: Option, - // Enhancement (post-MVP) - // pub start_time - // pub end_time - - // Enhancement (post-MVP) - // pub any - // pub all - // pub not -} - #[Object] impl Event { /// The Move module containing some function that when called by @@ -158,64 +115,82 @@ impl Event { let cursor_viewed_at = page.validate_cursor_consistency()?; let checkpoint_viewed_at = cursor_viewed_at.unwrap_or(checkpoint_viewed_at); + // Construct tx and ev sequence number query with table-relevant filters, if they exist. The + // resulting query will look something like `SELECT tx_sequence_number, + // event_sequence_number FROM lookup_table WHERE ...`. If no filter is provided we don't + // need to use any lookup tables and can just query `events` table, as can be seen in the + // code below. + let query_constraint = match (filter.sender, &filter.emitting_module, &filter.event_type) { + (None, None, None) => None, + (Some(sender), None, None) => Some(select_sender(sender)), + (sender, None, Some(event_type)) => Some(select_event_type(event_type, sender)), + (sender, Some(module), None) => Some(select_emit_module(module, sender)), + (_, Some(_), Some(_)) => { + return Err(Error::Client( + "Filtering by both emitting module and event type is not supported".to_string(), + )) + } + }; + + use checkpoints::dsl; let (prev, next, results) = db .execute(move |conn| { - page.paginate_query::(conn, checkpoint_viewed_at, move || { - let mut query = events::dsl::events.into_boxed(); - - // Bound events by the provided `checkpoint_viewed_at`. From EXPLAIN - // ANALYZE, using the checkpoint sequence number directly instead of - // translating into a transaction sequence number bound is more efficient. - query = query.filter( - events::dsl::checkpoint_sequence_number.le(checkpoint_viewed_at as i64), - ); - - // The transactions table doesn't have an index on the senders column, so use - // `tx_senders`. - if let Some(sender) = &filter.sender { - query = query.filter( - events::dsl::tx_sequence_number.eq_any( - tx_senders::dsl::tx_senders - .select(tx_senders::dsl::tx_sequence_number) - .filter(tx_senders::dsl::sender.eq(sender.into_vec())), - ), - ) - } - - if let Some(digest) = &filter.transaction_digest { - // Since the event filter takes in a single tx_digest, we know that - // there will only be one corresponding transaction. We can use - // single_value() to tell the query planner that we expect only one - // instead of a range of values, which will subsequently speed up query - // execution time. - query = query.filter( - events::dsl::tx_sequence_number.nullable().eq( - transactions::dsl::transactions - .select(transactions::dsl::tx_sequence_number) - .filter( - transactions::dsl::transaction_digest.eq(digest.to_vec()), - ) - .single_value(), - ), - ) - } - - if let Some(module) = &filter.emitting_module { - query = module.apply(query, events::dsl::package, events::dsl::module); - } - - if let Some(type_) = &filter.event_type { - query = type_.apply( - query, - events::dsl::event_type, - events::dsl::event_type_package, - events::dsl::event_type_module, - events::dsl::event_type_name, - ); - } - - query - }) + let tx_hi: i64 = conn.first(move || { + dsl::checkpoints.select(dsl::network_total_transactions) + .filter(dsl::sequence_number.eq(checkpoint_viewed_at as i64)) + })?; + + let (prev, next, mut events): (bool, bool, Vec) = + if let Some(filter_query) = query_constraint { + let query = add_bounds(filter_query, &filter.transaction_digest, &page, tx_hi); + + let (prev, next, results) = + page.paginate_raw_query::(conn, checkpoint_viewed_at, query)?; + + let ev_lookups = results + .into_iter() + .map(|x| (x.tx, x.ev)) + .collect::>(); + + if ev_lookups.is_empty() { + return Ok::<_, diesel::result::Error>((prev, next, vec![])); + } + + // Unlike a multi-get on a single column which can be serviced by a query `IN + // (...)`, because events have a composite primary key, the query planner tends + // to perform a sequential scan when given a list of tuples to lookup. A query + // using `UNION ALL` allows us to leverage the index on the composite key. + let events = conn.results(move || { + // Diesel's DSL does not current support chained `UNION ALL`, so we have to turn + // to `RawQuery` here. + let query_string = ev_lookups.iter() + .map(|&(tx, ev)| { + format!("SELECT * FROM events WHERE tx_sequence_number = {} AND event_sequence_number = {}", tx, ev) + }) + .collect::>() + .join(" UNION ALL "); + + query!(query_string).into_boxed() + })?; + (prev, next, events) + } else { + // No filter is provided so we add bounds to the basic `SELECT * FROM + // events` query and call it a day. + let query = add_bounds(query!("SELECT * FROM events"), &filter.transaction_digest, &page, tx_hi); + let (prev, next, events_iter) = page.paginate_raw_query::(conn, checkpoint_viewed_at, query)?; + let events = events_iter.collect::>(); + (prev, next, events) + }; + + // UNION ALL does not guarantee order, so we need to sort the results. Whether + // `first` or `last, the result set is always sorted in ascending order. + events.sort_by(|a, b| { + a.tx_sequence_number.cmp(&b.tx_sequence_number) + .then_with(|| a.event_sequence_number.cmp(&b.event_sequence_number)) + }); + + + Ok::<_, diesel::result::Error>((prev, next, events)) }) .await?; @@ -256,7 +231,6 @@ impl Event { tx_sequence_number: stored_tx.tx_sequence_number, event_sequence_number: idx as i64, transaction_digest: stored_tx.transaction_digest.clone(), - checkpoint_sequence_number: stored_tx.checkpoint_sequence_number, #[cfg(feature = "postgres-feature")] senders: vec![Some(native_event.sender.to_vec())], package: native_event.package_id.to_vec(), @@ -264,9 +238,6 @@ impl Event { event_type: native_event .type_ .to_canonical_string(/* with_prefix */ true), - event_type_package: native_event.type_.address.to_vec(), - event_type_module: native_event.type_.module.to_string(), - event_type_name: native_event.type_.name.to_string(), bcs: native_event.contents.clone(), timestamp_ms: stored_tx.timestamp_ms, }; @@ -312,54 +283,3 @@ impl Event { }) } } - -impl Paginated for StoredEvent { - type Source = events::table; - - fn filter_ge(cursor: &Cursor, query: Query) -> Query { - use events::dsl::{event_sequence_number as event, tx_sequence_number as tx}; - query.filter( - tx.gt(cursor.tx as i64) - .or(tx.eq(cursor.tx as i64).and(event.ge(cursor.e as i64))), - ) - } - - fn filter_le(cursor: &Cursor, query: Query) -> Query { - use events::dsl::{event_sequence_number as event, tx_sequence_number as tx}; - query.filter( - tx.lt(cursor.tx as i64) - .or(tx.eq(cursor.tx as i64).and(event.le(cursor.e as i64))), - ) - } - - fn order(asc: bool, query: Query) -> Query { - use events::dsl; - if asc { - query - .order_by(dsl::tx_sequence_number.asc()) - .then_order_by(dsl::event_sequence_number.asc()) - } else { - query - .order_by(dsl::tx_sequence_number.desc()) - .then_order_by(dsl::event_sequence_number.desc()) - } - } -} - -impl Target for StoredEvent { - fn cursor(&self, checkpoint_viewed_at: u64) -> Cursor { - Cursor::new(EventKey { - tx: self.tx_sequence_number as u64, - e: self.event_sequence_number as u64, - checkpoint_viewed_at, - }) - } -} - -impl Checkpointed for Cursor { - fn checkpoint_viewed_at(&self) -> u64 { - self.checkpoint_viewed_at - } -} - -impl ScanLimited for Cursor {} diff --git a/crates/sui-graphql-rpc/src/types/query.rs b/crates/sui-graphql-rpc/src/types/query.rs index f403fbf8657b5..f6f4704b3009f 100644 --- a/crates/sui-graphql-rpc/src/types/query.rs +++ b/crates/sui-graphql-rpc/src/types/query.rs @@ -414,7 +414,9 @@ impl Query { .extend() } - /// The events that exist in the network. + /// Query events that are emitted in the network. + /// We currently do not support filtering by emitting module and event type + /// at the same time so if both are provided in one filter, the query will error. async fn events( &self, ctx: &Context<'_>, diff --git a/crates/sui-graphql-rpc/src/types/type_filter.rs b/crates/sui-graphql-rpc/src/types/type_filter.rs index f2028483989ff..16f9dd03181f1 100644 --- a/crates/sui-graphql-rpc/src/types/type_filter.rs +++ b/crates/sui-graphql-rpc/src/types/type_filter.rs @@ -2,18 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use super::{string_input::impl_string_input, sui_address::SuiAddress}; +use crate::filter; use crate::raw_query::RawQuery; -use crate::{ - data::{DieselBackend, Query}, - filter, -}; use async_graphql::*; -use diesel::{ - expression::{is_aggregate::No, ValidGrouping}, - query_builder::QueryFragment, - sql_types::{Binary, Text}, - AppearsOnTable, Expression, ExpressionMethods, QueryDsl, QuerySource, -}; use move_core_types::language_storage::StructTag; use std::{fmt, result::Result, str::FromStr}; use sui_types::{ @@ -67,89 +58,7 @@ pub(crate) enum Error { InvalidFormat(&'static str), } -/// Trait for a field that can be used in a query. -pub(crate) trait Field: - ExpressionMethods - + Expression - + QueryFragment - + AppearsOnTable - + ValidGrouping<(), IsAggregate = No> - + Send - + 'static -{ -} - -impl Field for T where - T: ExpressionMethods - + Expression - + QueryFragment - + AppearsOnTable - + ValidGrouping<(), IsAggregate = No> - + Send - + 'static -{ -} - impl TypeFilter { - /// Modify `query` to apply this filter to `type_field`, `package_field`, `module_field` - /// and `name_field`, where `type_field` stores the full type tag while the rest - /// store the package, module and name of the type tag respectively. The new query - /// after applying the filter is returned. - pub(crate) fn apply( - &self, - query: Query, - // Field storing the full type tag, including type parameters. - type_field: T, - package_field: P, - module_field: M, - // Name field only includes the name of the struct, like `Coin`, not including type parameters. - name_field: N, - ) -> Query - where - Query: QueryDsl, - T: Field, - P: Field, - M: Field, - N: Field, - QS: QuerySource, - { - match self { - TypeFilter::ByModule(ModuleFilter::ByPackage(p)) => { - query.filter(package_field.eq(p.into_vec())) - } - - TypeFilter::ByModule(ModuleFilter::ByModule(p, m)) => query - .filter(package_field.eq(p.into_vec())) - .filter(module_field.eq(m.clone())), - - // A type filter without type parameters is interpreted as either an exact match, or a - // match for all generic instantiations of the type so we check against only package, module - // and name fields. - TypeFilter::ByType(tag) if tag.type_params.is_empty() => { - let p = tag.address.to_vec(); - let m = tag.module.to_string(); - let n = tag.name.to_string(); - query - .filter(package_field.eq(p)) - .filter(module_field.eq(m)) - .filter(name_field.eq(n)) - } - - TypeFilter::ByType(tag) => { - let p = tag.address.to_vec(); - let m = tag.module.to_string(); - let n = tag.name.to_string(); - let exact = tag.to_canonical_string(/* with_prefix */ true); - // We check against the full type field for an exact match, including type parameters. - query - .filter(package_field.eq(p)) - .filter(module_field.eq(m)) - .filter(name_field.eq(n)) - .filter(type_field.eq(exact)) - } - } - } - /// Modify `query` to apply this filter to `field`, returning the new query. pub(crate) fn apply_raw( &self, @@ -295,28 +204,6 @@ impl FqNameFilter { } impl ModuleFilter { - /// Modify `query` to apply this filter, treating `package` as the column containing the package - /// address and `module` as the module containing the module name. - pub(crate) fn apply( - &self, - query: Query, - package: P, - module: M, - ) -> Query - where - Query: QueryDsl, - P: Field, - M: Field, - QS: QuerySource, - { - match self { - ModuleFilter::ByPackage(p) => query.filter(package.eq(p.into_vec())), - ModuleFilter::ByModule(p, m) => query - .filter(package.eq(p.into_vec())) - .filter(module.eq(m.clone())), - } - } - /// Try to create a filter whose results are the intersection of the results of the input /// filters (`self` and `other`). This may not be possible if the resulting filter is /// inconsistent (e.g. a filter that requires the module's package to be at two different 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 fd04f186f34b6..ed3ec693362f4 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 @@ -1276,6 +1276,8 @@ input EventFilter { PTB and emits an event. Modules can be filtered by their package, or package::module. + We currently do not support filtering by emitting module and event type + at the same time so if both are provided in one filter, the query will error. """ emittingModule: String """ @@ -3316,7 +3318,9 @@ type Query { """ transactionBlocks(first: Int, after: String, last: Int, before: String, filter: TransactionBlockFilter, scanLimit: Int): TransactionBlockConnection! """ - The events that exist in the network. + Query events that are emitted in the network. + We currently do not support filtering by emitting module and event type + at the same time so if both are provided in one filter, the query will error. """ events(first: Int, after: String, last: Int, before: String, filter: EventFilter): EventConnection! """ diff --git a/crates/sui-indexer/migrations/pg/2023-08-19-044020_events/up.sql b/crates/sui-indexer/migrations/pg/2023-08-19-044020_events/up.sql index dfbfa3ea14495..14aa6a098161f 100644 --- a/crates/sui-indexer/migrations/pg/2023-08-19-044020_events/up.sql +++ b/crates/sui-indexer/migrations/pg/2023-08-19-044020_events/up.sql @@ -4,7 +4,6 @@ CREATE TABLE events tx_sequence_number BIGINT NOT NULL, event_sequence_number BIGINT NOT NULL, transaction_digest bytea NOT NULL, - checkpoint_sequence_number bigint NOT NULL, -- array of SuiAddress in bytes. All signers of the transaction. senders bytea[] NOT NULL, -- bytes of the entry package ID. Notice that the package and module here @@ -15,11 +14,6 @@ CREATE TABLE events module text NOT NULL, -- StructTag in Display format, fully qualified including type parameters event_type text NOT NULL, - -- Components of the StructTag of the event type: package, module, - -- name (name of the struct, without type parameters) - event_type_package bytea NOT NULL, - event_type_module text NOT NULL, - event_type_name text NOT NULL, -- timestamp of the checkpoint when the event was emitted timestamp_ms BIGINT NOT NULL, -- bcs of the Event contents (Event.contents) @@ -30,5 +24,3 @@ CREATE TABLE events_partition_0 PARTITION OF events FOR VALUES FROM (0) TO (MAXV CREATE INDEX events_package ON events (package, tx_sequence_number, event_sequence_number); CREATE INDEX events_package_module ON events (package, module, tx_sequence_number, event_sequence_number); CREATE INDEX events_event_type ON events (event_type text_pattern_ops, tx_sequence_number, event_sequence_number); -CREATE INDEX events_type_package_module_name ON events (event_type_package, event_type_module, event_type_name, tx_sequence_number, event_sequence_number); -CREATE INDEX events_checkpoint_sequence_number ON events (checkpoint_sequence_number); diff --git a/crates/sui-indexer/src/models/events.rs b/crates/sui-indexer/src/models/events.rs index 455b22e1741fe..01f79c41d6ab2 100644 --- a/crates/sui-indexer/src/models/events.rs +++ b/crates/sui-indexer/src/models/events.rs @@ -31,9 +31,6 @@ pub struct StoredEvent { #[diesel(sql_type = diesel::sql_types::Binary)] pub transaction_digest: Vec, - #[diesel(sql_type = diesel::sql_types::BigInt)] - pub checkpoint_sequence_number: i64, - #[cfg(feature = "postgres-feature")] #[diesel(sql_type = diesel::sql_types::Array>)] pub senders: Vec>>, @@ -52,15 +49,6 @@ pub struct StoredEvent { #[diesel(sql_type = diesel::sql_types::Text)] pub event_type: String, - #[diesel(sql_type = diesel::sql_types::Binary)] - pub event_type_package: Vec, - - #[diesel(sql_type = diesel::sql_types::Text)] - pub event_type_module: String, - - #[diesel(sql_type = diesel::sql_types::Text)] - pub event_type_name: String, - #[diesel(sql_type = diesel::sql_types::BigInt)] pub timestamp_ms: i64, @@ -81,7 +69,6 @@ impl From for StoredEvent { tx_sequence_number: event.tx_sequence_number as i64, event_sequence_number: event.event_sequence_number as i64, transaction_digest: event.transaction_digest.into_inner().to_vec(), - checkpoint_sequence_number: event.checkpoint_sequence_number as i64, #[cfg(feature = "postgres-feature")] senders: event .senders @@ -94,9 +81,6 @@ impl From for StoredEvent { package: event.package.to_vec(), module: event.module.clone(), event_type: event.event_type.clone(), - event_type_package: event.event_type_package.to_vec(), - event_type_module: event.event_type_module.clone(), - event_type_name: event.event_type_name.clone(), bcs: event.bcs.clone(), timestamp_ms: event.timestamp_ms as i64, } diff --git a/crates/sui-indexer/src/schema/pg.rs b/crates/sui-indexer/src/schema/pg.rs index 2515c98d34c06..c1720c94b37a1 100644 --- a/crates/sui-indexer/src/schema/pg.rs +++ b/crates/sui-indexer/src/schema/pg.rs @@ -137,14 +137,10 @@ diesel::table! { tx_sequence_number -> Int8, event_sequence_number -> Int8, transaction_digest -> Bytea, - checkpoint_sequence_number -> Int8, senders -> Array>, package -> Bytea, module -> Text, event_type -> Text, - event_type_package -> Bytea, - event_type_module -> Text, - event_type_name -> Text, timestamp_ms -> Int8, bcs -> Bytea, } @@ -155,14 +151,10 @@ diesel::table! { tx_sequence_number -> Int8, event_sequence_number -> Int8, transaction_digest -> Bytea, - checkpoint_sequence_number -> Int8, senders -> Array>, package -> Bytea, module -> Text, event_type -> Text, - event_type_package -> Bytea, - event_type_module -> Text, - event_type_name -> Text, timestamp_ms -> Int8, bcs -> Bytea, } diff --git a/crates/sui-indexer/tests/ingestion_tests.rs b/crates/sui-indexer/tests/ingestion_tests.rs index af67a061bbde3..8eee88f8bd448 100644 --- a/crates/sui-indexer/tests/ingestion_tests.rs +++ b/crates/sui-indexer/tests/ingestion_tests.rs @@ -5,7 +5,6 @@ mod ingestion_tests { use diesel::ExpressionMethods; use diesel::{QueryDsl, RunQueryDsl}; - use move_core_types::language_storage::StructTag; use simulacrum::Simulacrum; use std::net::SocketAddr; use std::path::PathBuf; @@ -14,18 +13,14 @@ mod ingestion_tests { use sui_indexer::db::get_pool_connection; use sui_indexer::errors::Context; use sui_indexer::errors::IndexerError; - use sui_indexer::models::{ - events::StoredEvent, objects::StoredObject, transactions::StoredTransaction, - }; - use sui_indexer::schema::{events, objects, transactions}; + use sui_indexer::models::{objects::StoredObject, transactions::StoredTransaction}; + use sui_indexer::schema::{objects, transactions}; use sui_indexer::store::{indexer_store::IndexerStore, PgIndexerStore}; use sui_indexer::test_utils::{start_test_indexer, ReaderWriterConfig}; use sui_types::base_types::SuiAddress; use sui_types::effects::TransactionEffectsAPI; use sui_types::gas_coin::GasCoin; - use sui_types::{ - Identifier, SUI_FRAMEWORK_PACKAGE_ID, SUI_SYSTEM_ADDRESS, SUI_SYSTEM_PACKAGE_ID, - }; + use sui_types::SUI_FRAMEWORK_PACKAGE_ID; use tempfile::tempdir; use tokio::task::JoinHandle; @@ -93,24 +88,6 @@ mod ingestion_tests { Ok(()) } - /// Wait for the indexer to catch up to the given epoch id. - async fn wait_for_epoch( - pg_store: &PgIndexerStore, - epoch: u64, - ) -> Result<(), IndexerError> { - tokio::time::timeout(Duration::from_secs(10), async { - while { - let cp_opt = pg_store.get_latest_epoch_id().unwrap(); - cp_opt.is_none() || (cp_opt.unwrap() < epoch) - } { - tokio::time::sleep(Duration::from_secs(1)).await; - } - }) - .await - .expect("Timeout waiting for indexer to catchup to epoch"); - Ok(()) - } - #[tokio::test] pub async fn test_transaction_table() -> Result<(), IndexerError> { let mut sim = Simulacrum::new(); @@ -156,46 +133,6 @@ mod ingestion_tests { Ok(()) } - #[tokio::test] - pub async fn test_event_type() -> Result<(), IndexerError> { - let mut sim = Simulacrum::new(); - let data_ingestion_path = tempdir().unwrap().into_path(); - sim.set_data_ingestion_path(data_ingestion_path.clone()); - - // Advance the epoch to generate some events. - sim.advance_epoch(false); - - let (_, pg_store, _) = set_up(Arc::new(sim), data_ingestion_path).await; - - // Wait for the epoch to change so we can get some events. - wait_for_epoch(&pg_store, 1).await?; - - // Read the event from the database directly. - let db_event: StoredEvent = read_only_blocking!(&pg_store.blocking_cp(), |conn| { - events::table - .filter(events::event_type_name.eq("SystemEpochInfoEvent")) - .first::(conn) - }) - .context("Failed reading SystemEpochInfoEvent from PostgresDB")?; - - let event_type_tag = StructTag { - address: SUI_SYSTEM_ADDRESS, - module: Identifier::new("sui_system_state_inner").unwrap(), - name: Identifier::new("SystemEpochInfoEvent").unwrap(), - type_params: vec![], - }; - - // Check that the different components of the event type were stored correctly. - assert_eq!( - db_event.event_type, - event_type_tag.to_canonical_string(true) - ); - assert_eq!(db_event.event_type_package, SUI_SYSTEM_PACKAGE_ID.to_vec()); - assert_eq!(db_event.event_type_module, "sui_system_state_inner"); - assert_eq!(db_event.event_type_name, "SystemEpochInfoEvent"); - Ok(()) - } - #[tokio::test] pub async fn test_object_type() -> Result<(), IndexerError> { let mut sim = Simulacrum::new();