Skip to content

Commit

Permalink
[gql] objects_snapshot test for availableRange-aware object history l…
Browse files Browse the repository at this point in the history
…ookup (MystenLabs#15677)

## Description 

Expose a way to provide config values to ObjectsSnapshotProcessor to
enable e2e tests around objects_snapshot.
1. Adds a SnapshotLagConfig to ObjectsSnapshotProcessor struct
2. ReaderWriterConfig for start_test_indexer_v2, reorganizes some logic
on instantiating a reader or writer indexer_v2
3. RunGraphqlCommand now waits for objects_snapshot catch up in addition
to the existing checkpoint catch up.

snapshot.move to test querying against objects_snapshot and
objects_history tables

## Test Plan 

snapshot.move

---
If your changes are not user-facing and do not break anything, you can
skip the following section. Otherwise, please briefly describe what has
changed under the Release Notes section.

### Type of Change (Check all that apply)

- [ ] protocol change
- [ ] user-visible impact
- [ ] breaking change for a client SDKs
- [ ] breaking change for FNs (FN binary must upgrade)
- [ ] breaking change for validators or node operators (must upgrade
binaries)
- [ ] breaking change for on-chain data layout
- [ ] necessitate either a data wipe or data migration

### Release notes
  • Loading branch information
wlmyng authored Jan 20, 2024
1 parent 83b1ee3 commit 97fd382
Show file tree
Hide file tree
Showing 13 changed files with 452 additions and 54 deletions.
6 changes: 3 additions & 3 deletions crates/sui-cluster-test/src/cluster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use sui_config::Config;
use sui_config::{PersistedConfig, SUI_KEYSTORE_FILENAME, SUI_NETWORK_CONFIG};
use sui_graphql_rpc::config::ConnectionConfig;
use sui_graphql_rpc::test_infra::cluster::start_graphql_server;
use sui_indexer::test_utils::{start_test_indexer, start_test_indexer_v2};
use sui_indexer::test_utils::{start_test_indexer, start_test_indexer_v2, ReaderWriterConfig};
use sui_indexer::IndexerConfig;
use sui_keys::keystore::{AccountKeystore, FileBasedKeystore, Keystore};
use sui_sdk::sui_client_config::{SuiClientConfig, SuiEnv};
Expand Down Expand Up @@ -228,17 +228,17 @@ impl Cluster for LocalNewCluster {
start_test_indexer_v2(
Some(pg_address.clone()),
fullnode_url.clone(),
None,
options.use_indexer_experimental_methods,
ReaderWriterConfig::writer_mode(None),
)
.await;

// Start in reader mode
start_test_indexer_v2(
Some(pg_address),
fullnode_url.clone(),
Some(indexer_address.to_string()),
options.use_indexer_experimental_methods,
ReaderWriterConfig::reader_mode(indexer_address.to_string()),
)
.await;
} else {
Expand Down
96 changes: 96 additions & 0 deletions crates/sui-graphql-e2e-tests/tests/objects/snapshot.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
processed 17 tasks

init:
A: object(0,0)

task 1 'publish'. lines 11-37:
created: object(1,0)
mutated: object(0,1)
gas summary: computation_cost: 1000000, storage_cost: 6171200, storage_rebate: 0, non_refundable_storage_fee: 0

task 2 'run'. lines 39-39:
created: object(2,0)
mutated: object(0,1)
gas summary: computation_cost: 1000000, storage_cost: 2302800, storage_rebate: 978120, non_refundable_storage_fee: 9880

task 3 'create-checkpoint'. lines 41-41:
Checkpoint created: 1

task 4 'run-graphql'. lines 43-56:
Response: {
"data": {
"object": {
"status": "LIVE",
"version": 3,
"asMoveObject": {
"contents": {
"json": {
"id": "0xcf27391f83c42a323595be23151f6ede95a083f451a388ff2e3e25fbbf31a7c3",
"value": "0"
}
}
}
}
}
}

task 5 'run-graphql'. lines 59-73:
Response: {
"data": {
"object": {
"status": "HISTORICAL",
"version": 3,
"asMoveObject": {
"contents": {
"json": {
"id": "0xcf27391f83c42a323595be23151f6ede95a083f451a388ff2e3e25fbbf31a7c3",
"value": "0"
}
}
}
}
}
}

task 6 'run'. lines 75-75:
created: object(6,0)
mutated: object(0,0)
wrapped: object(2,0)
gas summary: computation_cost: 1000000, storage_cost: 2553600, storage_rebate: 1301652, non_refundable_storage_fee: 13148

task 7 'create-checkpoint'. lines 77-77:
Checkpoint created: 2

task 9 'create-checkpoint'. lines 81-81:
Checkpoint created: 3

task 11 'create-checkpoint'. lines 85-85:
Checkpoint created: 4

task 13 'create-checkpoint'. lines 89-89:
Checkpoint created: 5

task 14 'run-graphql'. lines 91-105:
Response: {
"data": {
"object": null
}
}

task 15 'run-graphql'. lines 108-123:
Response: {
"data": {
"object": {
"status": "WRAPPED_OR_DELETED",
"version": 4,
"asMoveObject": null
}
}
}

task 16 'run-graphql'. lines 125-140:
Response: {
"data": {
"object": null
}
}
140 changes: 140 additions & 0 deletions crates/sui-graphql-e2e-tests/tests/objects/snapshot.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

// Objects can continue to be found on the live objects table until they are WrappedOrDeleted. From
// there, the object can be fetched on the objects_history table, until it gets snapshotted into
// objects_snapshot table. This test checks that we correctly fetch data from both the
// objects_snapshot and objects_history tables.

//# init --addresses Test=0x0 --accounts A --simulator --object-snapshot-min-checkpoint-lag 0 --object-snapshot-max-checkpoint-lag 2

//# publish
module Test::M1 {
use sui::object::{Self, UID};
use sui::tx_context::{Self, TxContext};
use sui::transfer;

struct Object has key, store {
id: UID,
value: u64,
}

struct Wrapper has key {
id: UID,
o: Object
}

public entry fun create(value: u64, recipient: address, ctx: &mut TxContext) {
transfer::public_transfer(
Object { id: object::new(ctx), value },
recipient
)
}

public entry fun wrap(o: Object, ctx: &mut TxContext) {
transfer::transfer(Wrapper { id: object::new(ctx), o }, tx_context::sender(ctx))
}
}

//# run Test::M1::create --args 0 @A

//# create-checkpoint 1

//# run-graphql
{
object(
address: "@{obj_2_0}"
) {
status
version
asMoveObject {
contents {
json
}
}
}
}


//# run-graphql
{
object(
address: "@{obj_2_0}"
version: 3
) {
status
version
asMoveObject {
contents {
json
}
}
}
}

//# run Test::M1::wrap --sender A --args object(2,0)

//# create-checkpoint

//# advance-clock --duration-ns 1

//# create-checkpoint

//# advance-clock --duration-ns 1

//# create-checkpoint

//# advance-clock --duration-ns 1

//# create-checkpoint

//# run-graphql
# should not exist on live objects
{
object(
address: "@{obj_2_0}"
) {
status
version
asMoveObject {
contents {
json
}
}
}
}


//# run-graphql
# fetched from objects_snapshot
{
object(
address: "@{obj_2_0}"
version: 4
) {
status
version
asMoveObject {
contents {
json
}
}
}
}

//# run-graphql
# should not exist in either objects_snapshot or objects_history
{
object(
address: "@{obj_2_0}"
version: 3
) {
status
version
asMoveObject {
contents {
json
}
}
}
}
1 change: 1 addition & 0 deletions crates/sui-graphql-rpc/src/server/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ pub mod tests {
connection_config,
DEFAULT_INTERNAL_DATA_SOURCE_PORT,
Arc::new(sim),
None,
)
.await,
)
Expand Down
51 changes: 47 additions & 4 deletions crates/sui-graphql-rpc/src/test_infra/cluster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ use std::sync::Arc;
use std::time::Duration;
use sui_graphql_rpc_client::simple_client::SimpleClient;
use sui_indexer::errors::IndexerError;
pub use sui_indexer::processors_v2::objects_snapshot_processor::SnapshotLagConfig;
use sui_indexer::store::indexer_store_v2::IndexerStoreV2;
use sui_indexer::store::PgIndexerStoreV2;
use sui_indexer::test_utils::start_test_indexer_v2;
use sui_indexer::test_utils::ReaderWriterConfig;
use sui_rest_api::node_state_getter::NodeStateGetter;
use sui_swarm_config::genesis_config::{AccountConfig, DEFAULT_GAS_AMOUNT};
use test_cluster::TestCluster;
Expand All @@ -34,6 +36,7 @@ pub struct ExecutorCluster {
pub indexer_join_handle: JoinHandle<Result<(), IndexerError>>,
pub graphql_server_join_handle: JoinHandle<()>,
pub graphql_client: SimpleClient,
pub snapshot_config: SnapshotLagConfig,
}

pub struct Cluster {
Expand All @@ -44,6 +47,7 @@ pub struct Cluster {
pub graphql_client: SimpleClient,
}

/// Starts a validator, fullnode, indexer, and graphql service for testing.
pub async fn start_cluster(
graphql_connection_config: ConnectionConfig,
internal_data_source_rpc_port: Option<u16>,
Expand All @@ -53,8 +57,13 @@ pub async fn start_cluster(
let val_fn = start_validator_with_fullnode(internal_data_source_rpc_port).await;

// Starts indexer
let (pg_store, pg_handle) =
start_test_indexer_v2(Some(db_url), val_fn.rpc_url().to_string(), None, true).await;
let (pg_store, pg_handle) = start_test_indexer_v2(
Some(db_url),
val_fn.rpc_url().to_string(),
true,
ReaderWriterConfig::writer_mode(None),
)
.await;

// Starts graphql server
let fn_rpc_url = val_fn.rpc_url().to_string();
Expand All @@ -79,10 +88,13 @@ pub async fn start_cluster(
}
}

/// Takes in a simulated instantiation of a Sui blockchain and builds a cluster around it. This
/// cluster is typically used in e2e tests to emulate and test behaviors.
pub async fn serve_executor(
graphql_connection_config: ConnectionConfig,
internal_data_source_rpc_port: u16,
executor: Arc<dyn NodeStateGetter>,
snapshot_config: Option<SnapshotLagConfig>,
) -> ExecutorCluster {
let db_url = graphql_connection_config.db_url.clone();

Expand All @@ -94,12 +106,11 @@ pub async fn serve_executor(
sui_rest_api::start_service(executor_server_url, executor, Some("/rest".to_owned())).await;
});

// Starts indexer
let (pg_store, pg_handle) = start_test_indexer_v2(
Some(db_url),
format!("http://{}", executor_server_url),
None,
true,
ReaderWriterConfig::writer_mode(snapshot_config.clone()),
)
.await;

Expand All @@ -121,6 +132,7 @@ pub async fn serve_executor(
indexer_join_handle: pg_handle,
graphql_server_join_handle: graphql_server_handle,
graphql_client: client,
snapshot_config: snapshot_config.unwrap_or_default(),
}
}

Expand Down Expand Up @@ -197,4 +209,35 @@ impl ExecutorCluster {
.await
.expect("Timeout waiting for indexer to catchup to checkpoint");
}

/// The ObjectsSnapshotProcessor is a long-running task that periodically takes a snapshot of
/// the objects table. This leads to flakiness in tests, so we wait until the objects_snapshot
/// has reached the expected state.
pub async fn wait_for_objects_snapshot_catchup(&self, base_timeout: Duration) {
let mut latest_snapshot_cp = 0;

let latest_cp = self
.indexer_store
.get_latest_tx_checkpoint_sequence_number()
.await
.unwrap()
.unwrap();

tokio::time::timeout(base_timeout, async {
while latest_cp > latest_snapshot_cp + self.snapshot_config.snapshot_max_lag as u64 {
tokio::time::sleep(std::time::Duration::from_secs(
self.snapshot_config.sleep_duration,
))
.await;
latest_snapshot_cp = self
.indexer_store
.get_latest_object_snapshot_checkpoint_sequence_number()
.await
.unwrap()
.unwrap_or_default();
}
})
.await
.expect("Timeout waiting for indexer to update objects snapshot");
}
}
Loading

0 comments on commit 97fd382

Please sign in to comment.