From 9f1dc1d24b59ddd1ec6cc1bc00525853b303294a Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Mon, 1 Jun 2026 21:38:22 -0300 Subject: [PATCH] split indexer integration tests --- integration_tests/src/lib.rs | 45 ++ integration_tests/tests/indexer.rs | 278 ------------ .../tests/indexer_block_batching.rs | 40 ++ integration_tests/tests/indexer_ffi.rs | 404 ------------------ .../tests/indexer_ffi_block_batching.rs | 66 +++ .../tests/indexer_ffi_helpers/mod.rs | 91 ++++ .../tests/indexer_ffi_state_consistency.rs | 151 +++++++ ...dexer_ffi_state_consistency_with_labels.rs | 104 +++++ .../tests/indexer_state_consistency.rs | 118 +++++ .../indexer_state_consistency_with_labels.rs | 88 ++++ integration_tests/tests/indexer_test_run.rs | 25 ++ .../tests/indexer_test_run_ffi.rs | 37 ++ 12 files changed, 765 insertions(+), 682 deletions(-) delete mode 100644 integration_tests/tests/indexer.rs create mode 100644 integration_tests/tests/indexer_block_batching.rs delete mode 100644 integration_tests/tests/indexer_ffi.rs create mode 100644 integration_tests/tests/indexer_ffi_block_batching.rs create mode 100644 integration_tests/tests/indexer_ffi_helpers/mod.rs create mode 100644 integration_tests/tests/indexer_ffi_state_consistency.rs create mode 100644 integration_tests/tests/indexer_ffi_state_consistency_with_labels.rs create mode 100644 integration_tests/tests/indexer_state_consistency.rs create mode 100644 integration_tests/tests/indexer_state_consistency_with_labels.rs create mode 100644 integration_tests/tests/indexer_test_run.rs create mode 100644 integration_tests/tests/indexer_test_run_ffi.rs diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index d3fa7c64..48f69559 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -3,4 +3,49 @@ //! non-test consumers (e.g. `integration_bench`) can depend on them without //! pulling in the test files. +use std::time::Duration; + +use anyhow::{Context as _, Result}; +use log::info; pub use test_fixtures::*; + +/// Maximum time to wait for the indexer to catch up to the sequencer. +pub const L2_TO_L1_TIMEOUT: Duration = Duration::from_mins(6); + +/// Poll the indexer until its last finalized block id reaches the sequencer's +/// current last block id or until [`L2_TO_L1_TIMEOUT`] elapses. +/// Returns the last indexer block id observed. +pub async fn wait_for_indexer_to_catch_up(ctx: &TestContext) -> Result { + use indexer_service_rpc::RpcClient as _; + + let block_id_to_catch_up = + sequencer_service_rpc::RpcClient::get_last_block_id(ctx.sequencer_client()).await?; + let mut last_ind: u64 = 1; + let inner = async { + loop { + let ind = ctx + .indexer_client() + .get_last_finalized_block_id() + .await? + .unwrap_or(0); + last_ind = ind; + if ind >= block_id_to_catch_up { + let last_seq = + sequencer_service_rpc::RpcClient::get_last_block_id(ctx.sequencer_client()) + .await?; + info!( + "Indexer caught up. Indexer last block id: {ind}. Current sequencer last block id: {last_seq}" + ); + return Ok(ind); + } + tokio::time::sleep(Duration::from_secs(2)).await; + } + }; + tokio::time::timeout(L2_TO_L1_TIMEOUT, inner) + .await + .with_context(|| { + format!( + "Indexer failed to catch up within {L2_TO_L1_TIMEOUT:?}. Last indexer block id observed: {last_ind}, but needed to catch up to at least {block_id_to_catch_up}" + ) + })? +} diff --git a/integration_tests/tests/indexer.rs b/integration_tests/tests/indexer.rs deleted file mode 100644 index d7d499cc..00000000 --- a/integration_tests/tests/indexer.rs +++ /dev/null @@ -1,278 +0,0 @@ -#![expect( - clippy::shadow_unrelated, - clippy::tests_outside_test_module, - reason = "We don't care about these in tests" -)] - -use std::time::Duration; - -use anyhow::{Context as _, Result}; -use indexer_service_rpc::RpcClient as _; -use integration_tests::{ - TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, private_mention, public_mention, - verify_commitment_is_in_state, -}; -use lee::AccountId; -use log::info; -use wallet::{ - account::Label, - cli::{CliAccountMention, Command, programs::native_token_transfer::AuthTransferSubcommand}, -}; - -/// Maximum time to wait for the indexer to catch up to the sequencer. -const L2_TO_L1_TIMEOUT: Duration = Duration::from_mins(6); - -/// Poll the indexer until its last finalized block id reaches the sequencer's -/// current last block id or until [`L2_TO_L1_TIMEOUT_MILLIS`] elapses. -/// Returns the last indexer block id observed. -async fn wait_for_indexer_to_catch_up(ctx: &TestContext) -> Result { - let block_id_to_catch_up = - sequencer_service_rpc::RpcClient::get_last_block_id(ctx.sequencer_client()).await?; - let mut last_ind: u64 = 1; - let inner = async { - loop { - let ind = ctx - .indexer_client() - .get_last_finalized_block_id() - .await? - .unwrap_or(0); - last_ind = ind; - if ind >= block_id_to_catch_up { - let last_seq = - sequencer_service_rpc::RpcClient::get_last_block_id(ctx.sequencer_client()) - .await?; - info!( - "Indexer caught up. Indexer last block id: {ind}. Current sequencer last block id: {last_seq}" - ); - return Ok(ind); - } - tokio::time::sleep(Duration::from_secs(2)).await; - } - }; - tokio::time::timeout(L2_TO_L1_TIMEOUT, inner) - .await - .with_context(|| { - format!( - "Indexer failed to catch up within {L2_TO_L1_TIMEOUT:?}. Last indexer block id observed: {last_ind}, but needed to catch up to at least {block_id_to_catch_up}" - ) - })? -} - -#[tokio::test] -async fn indexer_test_run() -> Result<()> { - let ctx = TestContext::new().await?; - - let last_block_indexer = wait_for_indexer_to_catch_up(&ctx).await?; - - let last_block_seq = - sequencer_service_rpc::RpcClient::get_last_block_id(ctx.sequencer_client()).await?; - - info!("Last block on seq now is {last_block_seq}"); - info!("Last block on ind now is {last_block_indexer}"); - - assert!(last_block_indexer > 0); - - Ok(()) -} - -#[tokio::test] -async fn indexer_block_batching() -> Result<()> { - let ctx = TestContext::new().await?; - - info!("Waiting for indexer to parse blocks"); - let last_block_indexer = wait_for_indexer_to_catch_up(&ctx).await?; - - info!("Last block on ind now is {last_block_indexer}"); - - assert!(last_block_indexer > 0); - - // Getting wide batch to fit all blocks (from latest backwards) - let mut block_batch = ctx.indexer_client().get_blocks(None, 100).await.unwrap(); - - // Reverse to check chain consistency from oldest to newest - block_batch.reverse(); - - // Checking chain consistency - let mut prev_block_hash = block_batch.first().unwrap().header.hash; - - for block in &block_batch[1..] { - assert_eq!(block.header.prev_block_hash, prev_block_hash); - - info!("Block {} chain-consistent", block.header.block_id); - - prev_block_hash = block.header.hash; - } - - Ok(()) -} - -#[tokio::test] -async fn indexer_state_consistency() -> Result<()> { - let mut ctx = TestContext::new().await?; - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: public_mention(ctx.existing_public_accounts()[0]), - to: Some(public_mention(ctx.existing_public_accounts()[1])), - to_npk: None, - to_vpk: None, - to_identifier: Some(0), - amount: 100, - }); - - wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let acc_1_balance = sequencer_service_rpc::RpcClient::get_account_balance( - ctx.sequencer_client(), - ctx.existing_public_accounts()[0], - ) - .await?; - let acc_2_balance = sequencer_service_rpc::RpcClient::get_account_balance( - ctx.sequencer_client(), - ctx.existing_public_accounts()[1], - ) - .await?; - - info!("Balance of sender: {acc_1_balance:#?}"); - info!("Balance of receiver: {acc_2_balance:#?}"); - - assert_eq!(acc_1_balance, 9900); - assert_eq!(acc_2_balance, 20100); - - let from: AccountId = ctx.existing_private_accounts()[0]; - let to: AccountId = ctx.existing_private_accounts()[1]; - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: private_mention(from), - to: Some(private_mention(to)), - to_npk: None, - to_vpk: None, - to_identifier: Some(0), - amount: 100, - }); - - wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let new_commitment1 = ctx - .wallet() - .get_private_account_commitment(from) - .context("Failed to get private account commitment for sender")?; - assert!(verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client()).await); - - let new_commitment2 = ctx - .wallet() - .get_private_account_commitment(to) - .context("Failed to get private account commitment for receiver")?; - assert!(verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()).await); - - info!("Successfully transferred privately to owned account"); - - info!("Waiting for indexer to parse blocks"); - wait_for_indexer_to_catch_up(&ctx).await?; - - let acc1_ind_state = ctx - .indexer_client() - .get_account(ctx.existing_public_accounts()[0].into()) - .await - .unwrap(); - let acc2_ind_state = ctx - .indexer_client() - .get_account(ctx.existing_public_accounts()[1].into()) - .await - .unwrap(); - - info!("Checking correct state transition"); - let acc1_seq_state = sequencer_service_rpc::RpcClient::get_account( - ctx.sequencer_client(), - ctx.existing_public_accounts()[0], - ) - .await?; - let acc2_seq_state = sequencer_service_rpc::RpcClient::get_account( - ctx.sequencer_client(), - ctx.existing_public_accounts()[1], - ) - .await?; - - assert_eq!(acc1_ind_state, acc1_seq_state.into()); - assert_eq!(acc2_ind_state, acc2_seq_state.into()); - - // ToDo: Check private state transition - - Ok(()) -} - -#[tokio::test] -async fn indexer_state_consistency_with_labels() -> Result<()> { - let mut ctx = TestContext::new().await?; - - // Assign labels to both accounts - let from_label = Label::new("idx-sender-label"); - let to_label = Label::new("idx-receiver-label"); - - let label_cmd = Command::Account(wallet::cli::account::AccountSubcommand::Label { - account_id: public_mention(ctx.existing_public_accounts()[0]), - label: from_label.clone(), - }); - wallet::cli::execute_subcommand(ctx.wallet_mut(), label_cmd).await?; - - let label_cmd = Command::Account(wallet::cli::account::AccountSubcommand::Label { - account_id: public_mention(ctx.existing_public_accounts()[1]), - label: to_label.clone(), - }); - wallet::cli::execute_subcommand(ctx.wallet_mut(), label_cmd).await?; - - // Send using labels instead of account IDs - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: CliAccountMention::Label(from_label), - to: Some(CliAccountMention::Label(to_label)), - to_npk: None, - to_vpk: None, - to_identifier: Some(0), - amount: 100, - }); - - wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let acc_1_balance = sequencer_service_rpc::RpcClient::get_account_balance( - ctx.sequencer_client(), - ctx.existing_public_accounts()[0], - ) - .await?; - let acc_2_balance = sequencer_service_rpc::RpcClient::get_account_balance( - ctx.sequencer_client(), - ctx.existing_public_accounts()[1], - ) - .await?; - - assert_eq!(acc_1_balance, 9900); - assert_eq!(acc_2_balance, 20100); - - info!("Waiting for indexer to parse blocks"); - wait_for_indexer_to_catch_up(&ctx).await?; - - let acc1_ind_state = ctx - .indexer_client() - .get_account(ctx.existing_public_accounts()[0].into()) - .await - .unwrap(); - let acc1_seq_state = sequencer_service_rpc::RpcClient::get_account( - ctx.sequencer_client(), - ctx.existing_public_accounts()[0], - ) - .await?; - - assert_eq!(acc1_ind_state, acc1_seq_state.into()); - - info!("Indexer state is consistent after label-based transfer"); - - Ok(()) -} diff --git a/integration_tests/tests/indexer_block_batching.rs b/integration_tests/tests/indexer_block_batching.rs new file mode 100644 index 00000000..d5999f10 --- /dev/null +++ b/integration_tests/tests/indexer_block_batching.rs @@ -0,0 +1,40 @@ +#![expect( + clippy::tests_outside_test_module, + reason = "We don't care about these in tests" +)] + +use anyhow::Result; +use indexer_service_rpc::RpcClient as _; +use integration_tests::{TestContext, wait_for_indexer_to_catch_up}; +use log::info; + +#[tokio::test] +async fn indexer_block_batching() -> Result<()> { + let ctx = TestContext::new().await?; + + info!("Waiting for indexer to parse blocks"); + let last_block_indexer = wait_for_indexer_to_catch_up(&ctx).await?; + + info!("Last block on ind now is {last_block_indexer}"); + + assert!(last_block_indexer > 0); + + // Getting wide batch to fit all blocks (from latest backwards) + let mut block_batch = ctx.indexer_client().get_blocks(None, 100).await.unwrap(); + + // Reverse to check chain consistency from oldest to newest + block_batch.reverse(); + + // Checking chain consistency + let mut prev_block_hash = block_batch.first().unwrap().header.hash; + + for block in &block_batch[1..] { + assert_eq!(block.header.prev_block_hash, prev_block_hash); + + info!("Block {} chain-consistent", block.header.block_id); + + prev_block_hash = block.header.hash; + } + + Ok(()) +} diff --git a/integration_tests/tests/indexer_ffi.rs b/integration_tests/tests/indexer_ffi.rs deleted file mode 100644 index ebb0ee78..00000000 --- a/integration_tests/tests/indexer_ffi.rs +++ /dev/null @@ -1,404 +0,0 @@ -#![expect( - clippy::shadow_unrelated, - clippy::tests_outside_test_module, - clippy::undocumented_unsafe_blocks, - reason = "We don't care about these in tests" -)] - -use std::{ - ffi::{CString, c_char}, - fs::File, - io::Write as _, - net::SocketAddr, - time::Duration, -}; - -use anyhow::{Context as _, Result}; -use indexer_ffi::{ - IndexerServiceFFI, OperationStatus, Runtime, - api::{ - PointerResult, - lifecycle::InitializedIndexerServiceFFIResult, - types::{FfiAccountId, FfiOption, FfiVec, account::FfiAccount, block::FfiBlock}, - }, -}; -use integration_tests::{ - BlockingTestContext, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, private_mention, - public_mention, verify_commitment_is_in_state, -}; -use lee::AccountId; -use log::{debug, info}; -use tempfile::TempDir; -use wallet::{ - account::Label, - cli::{Command, programs::native_token_transfer::AuthTransferSubcommand}, -}; - -/// Maximum time to wait for the indexer to catch up to the sequencer. -const L2_TO_L1_TIMEOUT: Duration = Duration::from_mins(6); - -unsafe extern "C" { - unsafe fn query_last_block( - runtime: *const Runtime, - indexer: *const IndexerServiceFFI, - ) -> PointerResult; - - unsafe fn query_block_vec( - runtime: *const Runtime, - indexer: *const IndexerServiceFFI, - before: FfiOption, - limit: u64, - ) -> PointerResult, OperationStatus>; - - unsafe fn query_account( - runtime: *const Runtime, - indexer: *const IndexerServiceFFI, - account_id: FfiAccountId, - ) -> PointerResult; - - unsafe fn start_indexer( - runtime: *const Runtime, - config_path: *const c_char, - port: u16, - ) -> InitializedIndexerServiceFFIResult; -} - -fn setup_indexer_ffi( - runtime: &Runtime, - bedrock_addr: SocketAddr, -) -> Result<(IndexerServiceFFI, TempDir)> { - let temp_indexer_dir = - tempfile::tempdir().context("Failed to create temp dir for indexer home")?; - - debug!( - "Using temp indexer home at {}", - temp_indexer_dir.path().display() - ); - - let indexer_config = - integration_tests::config::indexer_config(bedrock_addr, temp_indexer_dir.path().to_owned()) - .context("Failed to create Indexer config")?; - - let config_json = serde_json::to_vec(&indexer_config)?; - let config_path = temp_indexer_dir.path().join("indexer_config.json"); - let mut file = File::create(config_path.as_path())?; - file.write_all(&config_json)?; - file.flush()?; - - let res = - // SAFETY: lib function ensures validity of value. - unsafe { start_indexer(std::ptr::from_ref(runtime), CString::new(config_path.to_str().unwrap())?.as_ptr(), 0) }; - - if res.error.is_error() { - anyhow::bail!("Indexer FFI error {:?}", res.error); - } - - Ok(( - // SAFETY: lib function ensures validity of value. - unsafe { std::ptr::read(res.value) }, - temp_indexer_dir, - )) -} - -/// Prepare setup for tests. -fn setup() -> Result<(BlockingTestContext, IndexerServiceFFI, TempDir)> { - let ctx = TestContext::builder().disable_indexer().build_blocking()?; - // Safety: ctx runtime is valid for the lifetime of the returned Runtime - let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) }; - let (indexer_ffi, indexer_dir) = setup_indexer_ffi(&runtime, ctx.ctx().bedrock_addr())?; - - Ok((ctx, indexer_ffi, indexer_dir)) -} - -#[test] -fn indexer_test_run_ffi() -> Result<()> { - let (ctx, indexer_ffi, _indexer_dir) = setup()?; - - // RUN OBSERVATION - std::thread::sleep(L2_TO_L1_TIMEOUT); - - // Safety: ctx runtime is valid for the lifetime of the returned Runtime - let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) }; - let last_block_indexer_ffi_res = - unsafe { query_last_block(&raw const runtime, &raw const indexer_ffi) }; - - assert!(last_block_indexer_ffi_res.error.is_ok()); - - let last_block_indexer_ffi = unsafe { *last_block_indexer_ffi_res.value }; - - info!("Last block on indexer FFI now is {last_block_indexer_ffi}"); - - assert!(last_block_indexer_ffi > 0); - - Ok(()) -} - -#[test] -fn indexer_ffi_block_batching() -> Result<()> { - let (ctx, indexer_ffi, _indexer_dir) = setup()?; - - // WAIT - info!("Waiting for indexer to parse blocks"); - std::thread::sleep(L2_TO_L1_TIMEOUT); - - // Safety: ctx runtime is valid for the lifetime of the returned Runtime - let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) }; - let last_block_indexer_ffi_res = - unsafe { query_last_block(&raw const runtime, &raw const indexer_ffi) }; - - assert!(last_block_indexer_ffi_res.error.is_ok()); - - let last_block_indexer = unsafe { *last_block_indexer_ffi_res.value }; - - info!("Last block on indexer FFI now is {last_block_indexer}"); - - assert!(last_block_indexer > 0); - - let before_ffi = FfiOption::::from_none(); - let limit = 100; - - let block_batch_ffi_res = unsafe { - query_block_vec( - &raw const runtime, - &raw const indexer_ffi, - before_ffi, - limit, - ) - }; - - assert!(block_batch_ffi_res.error.is_ok()); - - let block_batch = unsafe { &*block_batch_ffi_res.value }; - - let mut last_block_prev_hash = unsafe { block_batch.get(0) }.header.prev_block_hash.data; - - for i in 1..block_batch.len { - let block = unsafe { block_batch.get(i) }; - - assert_eq!(last_block_prev_hash, block.header.hash.data); - - info!("Block {} chain-consistent", block.header.block_id); - - last_block_prev_hash = block.header.prev_block_hash.data; - } - - Ok(()) -} - -#[test] -fn indexer_ffi_state_consistency() -> Result<()> { - let (mut ctx, indexer_ffi, _indexer_dir) = setup()?; - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: public_mention(ctx.ctx().existing_public_accounts()[0]), - to: Some(public_mention(ctx.ctx().existing_public_accounts()[1])), - to_npk: None, - to_vpk: None, - amount: 100, - to_identifier: Some(0), - }); - - ctx.block_on_mut(|ctx| wallet::cli::execute_subcommand(ctx.wallet_mut(), command))?; - - info!("Waiting for next block creation"); - std::thread::sleep(std::time::Duration::from_secs( - TIME_TO_WAIT_FOR_BLOCK_SECONDS, - )); - - info!("Checking correct balance move"); - let acc_1_balance = ctx.block_on(|ctx| { - sequencer_service_rpc::RpcClient::get_account_balance( - ctx.sequencer_client(), - ctx.existing_public_accounts()[0], - ) - })?; - let acc_2_balance = ctx.block_on(|ctx| { - sequencer_service_rpc::RpcClient::get_account_balance( - ctx.sequencer_client(), - ctx.existing_public_accounts()[1], - ) - })?; - - info!("Balance of sender: {acc_1_balance:#?}"); - info!("Balance of receiver: {acc_2_balance:#?}"); - - assert_eq!(acc_1_balance, 9900); - assert_eq!(acc_2_balance, 20100); - - let from: AccountId = ctx.ctx().existing_private_accounts()[0]; - let to: AccountId = ctx.ctx().existing_private_accounts()[1]; - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: private_mention(from), - to: Some(private_mention(to)), - to_npk: None, - to_vpk: None, - amount: 100, - to_identifier: Some(0), - }); - - ctx.block_on_mut(|ctx| wallet::cli::execute_subcommand(ctx.wallet_mut(), command))?; - - info!("Waiting for next block creation"); - std::thread::sleep(std::time::Duration::from_secs( - TIME_TO_WAIT_FOR_BLOCK_SECONDS, - )); - - let new_commitment1 = ctx - .ctx() - .wallet() - .get_private_account_commitment(from) - .context("Failed to get private account commitment for sender")?; - let commitment_check1 = - ctx.block_on(|ctx| verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client())); - assert!(commitment_check1); - - let new_commitment2 = ctx - .ctx() - .wallet() - .get_private_account_commitment(to) - .context("Failed to get private account commitment for receiver")?; - let commitment_check2 = - ctx.block_on(|ctx| verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client())); - assert!(commitment_check2); - - info!("Successfully transferred privately to owned account"); - - // WAIT - info!("Waiting for indexer to parse blocks"); - std::thread::sleep(L2_TO_L1_TIMEOUT); - - // Safety: ctx runtime is valid for the lifetime of the returned Runtime - let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) }; - let acc1_ind_state_ffi = unsafe { - query_account( - &raw const runtime, - &raw const indexer_ffi, - (&ctx.ctx().existing_public_accounts()[0]).into(), - ) - }; - - assert!(acc1_ind_state_ffi.error.is_ok()); - - let acc1_ind_state_pre = unsafe { &*acc1_ind_state_ffi.value }; - let acc1_ind_state: indexer_service_protocol::Account = acc1_ind_state_pre.into(); - - let acc2_ind_state_ffi = unsafe { - query_account( - &raw const runtime, - &raw const indexer_ffi, - (&ctx.ctx().existing_public_accounts()[1]).into(), - ) - }; - - assert!(acc2_ind_state_ffi.error.is_ok()); - - let acc2_ind_state_pre = unsafe { &*acc2_ind_state_ffi.value }; - let acc2_ind_state: indexer_service_protocol::Account = acc2_ind_state_pre.into(); - - info!("Checking correct state transition"); - let acc1_seq_state = ctx.block_on(|ctx| { - sequencer_service_rpc::RpcClient::get_account( - ctx.sequencer_client(), - ctx.existing_public_accounts()[0], - ) - })?; - let acc2_seq_state = ctx.block_on(|ctx| { - sequencer_service_rpc::RpcClient::get_account( - ctx.sequencer_client(), - ctx.existing_public_accounts()[1], - ) - })?; - - assert_eq!(acc1_ind_state, acc1_seq_state.into()); - assert_eq!(acc2_ind_state, acc2_seq_state.into()); - - // ToDo: Check private state transition - - Ok(()) -} - -#[test] -fn indexer_ffi_state_consistency_with_labels() -> Result<()> { - let (mut ctx, indexer_ffi, _indexer_dir) = setup()?; - - // Assign labels to both accounts - let from_label = Label::new("idx-sender-label"); - let to_label = Label::new("idx-receiver-label"); - - let label_cmd = Command::Account(wallet::cli::account::AccountSubcommand::Label { - account_id: public_mention(ctx.ctx().existing_public_accounts()[0]), - label: from_label.clone(), - }); - ctx.block_on_mut(|ctx| wallet::cli::execute_subcommand(ctx.wallet_mut(), label_cmd))?; - - let label_cmd = Command::Account(wallet::cli::account::AccountSubcommand::Label { - account_id: public_mention(ctx.ctx().existing_public_accounts()[1]), - label: to_label.clone(), - }); - ctx.block_on_mut(|ctx| wallet::cli::execute_subcommand(ctx.wallet_mut(), label_cmd))?; - - // Send using labels instead of account IDs - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: from_label.into(), - to: Some(to_label.into()), - to_npk: None, - to_vpk: None, - amount: 100, - to_identifier: Some(0), - }); - - ctx.block_on_mut(|ctx| wallet::cli::execute_subcommand(ctx.wallet_mut(), command))?; - - info!("Waiting for next block creation"); - std::thread::sleep(std::time::Duration::from_secs( - TIME_TO_WAIT_FOR_BLOCK_SECONDS, - )); - - let acc_1_balance = ctx.block_on(|ctx| { - sequencer_service_rpc::RpcClient::get_account_balance( - ctx.sequencer_client(), - ctx.existing_public_accounts()[0], - ) - })?; - let acc_2_balance = ctx.block_on(|ctx| { - sequencer_service_rpc::RpcClient::get_account_balance( - ctx.sequencer_client(), - ctx.existing_public_accounts()[1], - ) - })?; - - assert_eq!(acc_1_balance, 9900); - assert_eq!(acc_2_balance, 20100); - - info!("Waiting for indexer to parse blocks"); - std::thread::sleep(L2_TO_L1_TIMEOUT); - - // Safety: ctx runtime is valid for the lifetime of the returned Runtime - let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) }; - let acc1_ind_state_ffi = unsafe { - query_account( - &raw const runtime, - &raw const indexer_ffi, - (&ctx.ctx().existing_public_accounts()[0]).into(), - ) - }; - - assert!(acc1_ind_state_ffi.error.is_ok()); - - let acc1_ind_state_pre = unsafe { &*acc1_ind_state_ffi.value }; - let acc1_ind_state: indexer_service_protocol::Account = acc1_ind_state_pre.into(); - - let acc1_seq_state = ctx.block_on(|ctx| { - sequencer_service_rpc::RpcClient::get_account( - ctx.sequencer_client(), - ctx.existing_public_accounts()[0], - ) - })?; - - assert_eq!(acc1_ind_state, acc1_seq_state.into()); - - info!("Indexer state is consistent after label-based transfer"); - - Ok(()) -} diff --git a/integration_tests/tests/indexer_ffi_block_batching.rs b/integration_tests/tests/indexer_ffi_block_batching.rs new file mode 100644 index 00000000..eeef276d --- /dev/null +++ b/integration_tests/tests/indexer_ffi_block_batching.rs @@ -0,0 +1,66 @@ +#![expect( + clippy::tests_outside_test_module, + clippy::undocumented_unsafe_blocks, + reason = "We don't care about these in tests" +)] + +use anyhow::Result; +use indexer_ffi::{Runtime, api::types::FfiOption}; +use integration_tests::L2_TO_L1_TIMEOUT; +use log::info; + +#[path = "indexer_ffi_helpers/mod.rs"] +mod indexer_ffi_helpers; + +#[test] +fn indexer_ffi_block_batching() -> Result<()> { + let (ctx, indexer_ffi, _indexer_dir) = indexer_ffi_helpers::setup()?; + + // WAIT + info!("Waiting for indexer to parse blocks"); + std::thread::sleep(L2_TO_L1_TIMEOUT); + + // Safety: ctx runtime is valid for the lifetime of the returned Runtime + let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) }; + let last_block_indexer_ffi_res = unsafe { + indexer_ffi_helpers::query_last_block(&raw const runtime, &raw const indexer_ffi) + }; + + assert!(last_block_indexer_ffi_res.error.is_ok()); + + let last_block_indexer = unsafe { *last_block_indexer_ffi_res.value }; + + info!("Last block on indexer FFI now is {last_block_indexer}"); + + assert!(last_block_indexer > 0); + + let before_ffi = FfiOption::::from_none(); + let limit = 100; + + let block_batch_ffi_res = unsafe { + indexer_ffi_helpers::query_block_vec( + &raw const runtime, + &raw const indexer_ffi, + before_ffi, + limit, + ) + }; + + assert!(block_batch_ffi_res.error.is_ok()); + + let block_batch = unsafe { &*block_batch_ffi_res.value }; + + let mut last_block_prev_hash = unsafe { block_batch.get(0) }.header.prev_block_hash.data; + + for i in 1..block_batch.len { + let block = unsafe { block_batch.get(i) }; + + assert_eq!(last_block_prev_hash, block.header.hash.data); + + info!("Block {} chain-consistent", block.header.block_id); + + last_block_prev_hash = block.header.prev_block_hash.data; + } + + Ok(()) +} diff --git a/integration_tests/tests/indexer_ffi_helpers/mod.rs b/integration_tests/tests/indexer_ffi_helpers/mod.rs new file mode 100644 index 00000000..c3c3caff --- /dev/null +++ b/integration_tests/tests/indexer_ffi_helpers/mod.rs @@ -0,0 +1,91 @@ +#![allow(dead_code, reason = "helper module used only by FFI test binaries")] + +use std::{ + ffi::{CString, c_char}, + fs::File, + io::Write as _, + net::SocketAddr, +}; + +use anyhow::{Context as _, Result}; +use indexer_ffi::{ + IndexerServiceFFI, OperationStatus, Runtime, + api::{ + PointerResult, + lifecycle::InitializedIndexerServiceFFIResult, + types::{FfiAccountId, FfiOption, FfiVec, account::FfiAccount, block::FfiBlock}, + }, +}; +use integration_tests::{BlockingTestContext, TestContext}; +use tempfile::TempDir; + +unsafe extern "C" { + pub unsafe fn query_last_block( + runtime: *const Runtime, + indexer: *const IndexerServiceFFI, + ) -> PointerResult; + + pub unsafe fn query_block_vec( + runtime: *const Runtime, + indexer: *const IndexerServiceFFI, + before: FfiOption, + limit: u64, + ) -> PointerResult, OperationStatus>; + + pub unsafe fn query_account( + runtime: *const Runtime, + indexer: *const IndexerServiceFFI, + account_id: FfiAccountId, + ) -> PointerResult; + + pub unsafe fn start_indexer( + runtime: *const Runtime, + config_path: *const c_char, + port: u16, + ) -> InitializedIndexerServiceFFIResult; +} + +pub fn setup_indexer_ffi( + runtime: &Runtime, + bedrock_addr: SocketAddr, +) -> Result<(IndexerServiceFFI, TempDir)> { + let temp_indexer_dir = + tempfile::tempdir().context("Failed to create temp dir for indexer home")?; + + log::debug!( + "Using temp indexer home at {}", + temp_indexer_dir.path().display() + ); + + let indexer_config = + integration_tests::config::indexer_config(bedrock_addr, temp_indexer_dir.path().to_owned()) + .context("Failed to create Indexer config")?; + + let config_json = serde_json::to_vec(&indexer_config)?; + let config_path = temp_indexer_dir.path().join("indexer_config.json"); + let mut file = File::create(config_path.as_path())?; + file.write_all(&config_json)?; + file.flush()?; + + let res = + // SAFETY: lib function ensures validity of value. + unsafe { start_indexer(std::ptr::from_ref(runtime), CString::new(config_path.to_str().unwrap())?.as_ptr(), 0) }; + + if res.error.is_error() { + anyhow::bail!("Indexer FFI error {:?}", res.error); + } + + Ok(( + // SAFETY: lib function ensures validity of value. + unsafe { std::ptr::read(res.value) }, + temp_indexer_dir, + )) +} + +pub fn setup() -> Result<(BlockingTestContext, IndexerServiceFFI, TempDir)> { + let ctx = TestContext::builder().disable_indexer().build_blocking()?; + // Safety: ctx runtime is valid for the lifetime of the returned Runtime + let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) }; + let (indexer_ffi, indexer_dir) = setup_indexer_ffi(&runtime, ctx.ctx().bedrock_addr())?; + Ok((ctx, indexer_ffi, indexer_dir)) +} diff --git a/integration_tests/tests/indexer_ffi_state_consistency.rs b/integration_tests/tests/indexer_ffi_state_consistency.rs new file mode 100644 index 00000000..165b332c --- /dev/null +++ b/integration_tests/tests/indexer_ffi_state_consistency.rs @@ -0,0 +1,151 @@ +#![expect( + clippy::shadow_unrelated, + clippy::tests_outside_test_module, + clippy::undocumented_unsafe_blocks, + reason = "We don't care about these in tests" +)] + +use std::time::Duration; + +use anyhow::{Context as _, Result}; +use indexer_ffi::Runtime; +use indexer_service_protocol::Account; +use integration_tests::{ + L2_TO_L1_TIMEOUT, TIME_TO_WAIT_FOR_BLOCK_SECONDS, private_mention, public_mention, + verify_commitment_is_in_state, +}; +use lee::AccountId; +use log::info; +use wallet::cli::{Command, programs::native_token_transfer::AuthTransferSubcommand}; + +#[path = "indexer_ffi_helpers/mod.rs"] +mod indexer_ffi_helpers; + +#[test] +fn indexer_ffi_state_consistency() -> Result<()> { + let (mut ctx, indexer_ffi, _indexer_dir) = indexer_ffi_helpers::setup()?; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: public_mention(ctx.ctx().existing_public_accounts()[0]), + to: Some(public_mention(ctx.ctx().existing_public_accounts()[1])), + to_npk: None, + to_vpk: None, + amount: 100, + to_identifier: Some(0), + }); + + ctx.block_on_mut(|ctx| wallet::cli::execute_subcommand(ctx.wallet_mut(), command))?; + + info!("Waiting for next block creation"); + std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)); + + info!("Checking correct balance move"); + let acc_1_balance = ctx.block_on(|ctx| { + sequencer_service_rpc::RpcClient::get_account_balance( + ctx.sequencer_client(), + ctx.existing_public_accounts()[0], + ) + })?; + let acc_2_balance = ctx.block_on(|ctx| { + sequencer_service_rpc::RpcClient::get_account_balance( + ctx.sequencer_client(), + ctx.existing_public_accounts()[1], + ) + })?; + + info!("Balance of sender: {acc_1_balance:#?}"); + info!("Balance of receiver: {acc_2_balance:#?}"); + + assert_eq!(acc_1_balance, 9900); + assert_eq!(acc_2_balance, 20100); + + let from: AccountId = ctx.ctx().existing_private_accounts()[0]; + let to: AccountId = ctx.ctx().existing_private_accounts()[1]; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: private_mention(from), + to: Some(private_mention(to)), + to_npk: None, + to_vpk: None, + amount: 100, + to_identifier: Some(0), + }); + + ctx.block_on_mut(|ctx| wallet::cli::execute_subcommand(ctx.wallet_mut(), command))?; + + info!("Waiting for next block creation"); + std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)); + + let new_commitment1 = ctx + .ctx() + .wallet() + .get_private_account_commitment(from) + .context("Failed to get private account commitment for sender")?; + let commitment_check1 = + ctx.block_on(|ctx| verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client())); + assert!(commitment_check1); + + let new_commitment2 = ctx + .ctx() + .wallet() + .get_private_account_commitment(to) + .context("Failed to get private account commitment for receiver")?; + let commitment_check2 = + ctx.block_on(|ctx| verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client())); + assert!(commitment_check2); + + info!("Successfully transferred privately to owned account"); + + // WAIT + info!("Waiting for indexer to parse blocks"); + std::thread::sleep(L2_TO_L1_TIMEOUT); + + // Safety: ctx runtime is valid for the lifetime of the returned Runtime + let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) }; + let acc1_ind_state_ffi = unsafe { + indexer_ffi_helpers::query_account( + &raw const runtime, + &raw const indexer_ffi, + (&ctx.ctx().existing_public_accounts()[0]).into(), + ) + }; + + assert!(acc1_ind_state_ffi.error.is_ok()); + + let acc1_ind_state_pre = unsafe { &*acc1_ind_state_ffi.value }; + let acc1_ind_state: Account = acc1_ind_state_pre.into(); + + let acc2_ind_state_ffi = unsafe { + indexer_ffi_helpers::query_account( + &raw const runtime, + &raw const indexer_ffi, + (&ctx.ctx().existing_public_accounts()[1]).into(), + ) + }; + + assert!(acc2_ind_state_ffi.error.is_ok()); + + let acc2_ind_state_pre = unsafe { &*acc2_ind_state_ffi.value }; + let acc2_ind_state: Account = acc2_ind_state_pre.into(); + + info!("Checking correct state transition"); + let acc1_seq_state = ctx.block_on(|ctx| { + sequencer_service_rpc::RpcClient::get_account( + ctx.sequencer_client(), + ctx.existing_public_accounts()[0], + ) + })?; + let acc2_seq_state = ctx.block_on(|ctx| { + sequencer_service_rpc::RpcClient::get_account( + ctx.sequencer_client(), + ctx.existing_public_accounts()[1], + ) + })?; + + assert_eq!(acc1_ind_state, acc1_seq_state.into()); + assert_eq!(acc2_ind_state, acc2_seq_state.into()); + + // ToDo: Check private state transition + + Ok(()) +} diff --git a/integration_tests/tests/indexer_ffi_state_consistency_with_labels.rs b/integration_tests/tests/indexer_ffi_state_consistency_with_labels.rs new file mode 100644 index 00000000..bce90dfb --- /dev/null +++ b/integration_tests/tests/indexer_ffi_state_consistency_with_labels.rs @@ -0,0 +1,104 @@ +#![expect( + clippy::shadow_unrelated, + clippy::tests_outside_test_module, + clippy::undocumented_unsafe_blocks, + reason = "We don't care about these in tests" +)] + +use std::time::Duration; + +use anyhow::Result; +use indexer_ffi::Runtime; +use indexer_service_protocol::Account; +use integration_tests::{L2_TO_L1_TIMEOUT, TIME_TO_WAIT_FOR_BLOCK_SECONDS, public_mention}; +use log::info; +use wallet::{ + account::Label, + cli::{Command, programs::native_token_transfer::AuthTransferSubcommand}, +}; + +#[path = "indexer_ffi_helpers/mod.rs"] +mod indexer_ffi_helpers; + +#[test] +fn indexer_ffi_state_consistency_with_labels() -> Result<()> { + let (mut ctx, indexer_ffi, _indexer_dir) = indexer_ffi_helpers::setup()?; + + // Assign labels to both accounts + let from_label = Label::new("idx-sender-label"); + let to_label = Label::new("idx-receiver-label"); + + let label_cmd = Command::Account(wallet::cli::account::AccountSubcommand::Label { + account_id: public_mention(ctx.ctx().existing_public_accounts()[0]), + label: from_label.clone(), + }); + ctx.block_on_mut(|ctx| wallet::cli::execute_subcommand(ctx.wallet_mut(), label_cmd))?; + + let label_cmd = Command::Account(wallet::cli::account::AccountSubcommand::Label { + account_id: public_mention(ctx.ctx().existing_public_accounts()[1]), + label: to_label.clone(), + }); + ctx.block_on_mut(|ctx| wallet::cli::execute_subcommand(ctx.wallet_mut(), label_cmd))?; + + // Send using labels instead of account IDs + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: from_label.into(), + to: Some(to_label.into()), + to_npk: None, + to_vpk: None, + amount: 100, + to_identifier: Some(0), + }); + + ctx.block_on_mut(|ctx| wallet::cli::execute_subcommand(ctx.wallet_mut(), command))?; + + info!("Waiting for next block creation"); + std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)); + + let acc_1_balance = ctx.block_on(|ctx| { + sequencer_service_rpc::RpcClient::get_account_balance( + ctx.sequencer_client(), + ctx.existing_public_accounts()[0], + ) + })?; + let acc_2_balance = ctx.block_on(|ctx| { + sequencer_service_rpc::RpcClient::get_account_balance( + ctx.sequencer_client(), + ctx.existing_public_accounts()[1], + ) + })?; + + assert_eq!(acc_1_balance, 9900); + assert_eq!(acc_2_balance, 20100); + + info!("Waiting for indexer to parse blocks"); + std::thread::sleep(L2_TO_L1_TIMEOUT); + + // Safety: ctx runtime is valid for the lifetime of the returned Runtime + let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) }; + let acc1_ind_state_ffi = unsafe { + indexer_ffi_helpers::query_account( + &raw const runtime, + &raw const indexer_ffi, + (&ctx.ctx().existing_public_accounts()[0]).into(), + ) + }; + + assert!(acc1_ind_state_ffi.error.is_ok()); + + let acc1_ind_state_pre = unsafe { &*acc1_ind_state_ffi.value }; + let acc1_ind_state: Account = acc1_ind_state_pre.into(); + + let acc1_seq_state = ctx.block_on(|ctx| { + sequencer_service_rpc::RpcClient::get_account( + ctx.sequencer_client(), + ctx.existing_public_accounts()[0], + ) + })?; + + assert_eq!(acc1_ind_state, acc1_seq_state.into()); + + info!("Indexer state is consistent after label-based transfer"); + + Ok(()) +} diff --git a/integration_tests/tests/indexer_state_consistency.rs b/integration_tests/tests/indexer_state_consistency.rs new file mode 100644 index 00000000..7afbf5ae --- /dev/null +++ b/integration_tests/tests/indexer_state_consistency.rs @@ -0,0 +1,118 @@ +#![expect( + clippy::shadow_unrelated, + clippy::tests_outside_test_module, + reason = "We don't care about these in tests" +)] + +use std::time::Duration; + +use anyhow::{Context as _, Result}; +use indexer_service_rpc::RpcClient as _; +use integration_tests::{ + TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, private_mention, public_mention, + verify_commitment_is_in_state, wait_for_indexer_to_catch_up, +}; +use lee::AccountId; +use log::info; +use wallet::cli::{Command, programs::native_token_transfer::AuthTransferSubcommand}; + +#[tokio::test] +async fn indexer_state_consistency() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: public_mention(ctx.existing_public_accounts()[0]), + to: Some(public_mention(ctx.existing_public_accounts()[1])), + to_npk: None, + to_vpk: None, + to_identifier: Some(0), + amount: 100, + }); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Checking correct balance move"); + let acc_1_balance = sequencer_service_rpc::RpcClient::get_account_balance( + ctx.sequencer_client(), + ctx.existing_public_accounts()[0], + ) + .await?; + let acc_2_balance = sequencer_service_rpc::RpcClient::get_account_balance( + ctx.sequencer_client(), + ctx.existing_public_accounts()[1], + ) + .await?; + + info!("Balance of sender: {acc_1_balance:#?}"); + info!("Balance of receiver: {acc_2_balance:#?}"); + + assert_eq!(acc_1_balance, 9900); + assert_eq!(acc_2_balance, 20100); + + let from: AccountId = ctx.existing_private_accounts()[0]; + let to: AccountId = ctx.existing_private_accounts()[1]; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: private_mention(from), + to: Some(private_mention(to)), + to_npk: None, + to_vpk: None, + to_identifier: Some(0), + amount: 100, + }); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let new_commitment1 = ctx + .wallet() + .get_private_account_commitment(from) + .context("Failed to get private account commitment for sender")?; + assert!(verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client()).await); + + let new_commitment2 = ctx + .wallet() + .get_private_account_commitment(to) + .context("Failed to get private account commitment for receiver")?; + assert!(verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()).await); + + info!("Successfully transferred privately to owned account"); + + info!("Waiting for indexer to parse blocks"); + wait_for_indexer_to_catch_up(&ctx).await?; + + let acc1_ind_state = ctx + .indexer_client() + .get_account(ctx.existing_public_accounts()[0].into()) + .await + .unwrap(); + let acc2_ind_state = ctx + .indexer_client() + .get_account(ctx.existing_public_accounts()[1].into()) + .await + .unwrap(); + + info!("Checking correct state transition"); + let acc1_seq_state = sequencer_service_rpc::RpcClient::get_account( + ctx.sequencer_client(), + ctx.existing_public_accounts()[0], + ) + .await?; + let acc2_seq_state = sequencer_service_rpc::RpcClient::get_account( + ctx.sequencer_client(), + ctx.existing_public_accounts()[1], + ) + .await?; + + assert_eq!(acc1_ind_state, acc1_seq_state.into()); + assert_eq!(acc2_ind_state, acc2_seq_state.into()); + + // ToDo: Check private state transition + + Ok(()) +} diff --git a/integration_tests/tests/indexer_state_consistency_with_labels.rs b/integration_tests/tests/indexer_state_consistency_with_labels.rs new file mode 100644 index 00000000..a9817933 --- /dev/null +++ b/integration_tests/tests/indexer_state_consistency_with_labels.rs @@ -0,0 +1,88 @@ +#![expect( + clippy::shadow_unrelated, + clippy::tests_outside_test_module, + reason = "We don't care about these in tests" +)] + +use std::time::Duration; + +use anyhow::Result; +use indexer_service_rpc::RpcClient as _; +use integration_tests::{ + TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, public_mention, wait_for_indexer_to_catch_up, +}; +use log::info; +use wallet::{ + account::Label, + cli::{CliAccountMention, Command, programs::native_token_transfer::AuthTransferSubcommand}, +}; + +#[tokio::test] +async fn indexer_state_consistency_with_labels() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Assign labels to both accounts + let from_label = Label::new("idx-sender-label"); + let to_label = Label::new("idx-receiver-label"); + + let label_cmd = Command::Account(wallet::cli::account::AccountSubcommand::Label { + account_id: public_mention(ctx.existing_public_accounts()[0]), + label: from_label.clone(), + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), label_cmd).await?; + + let label_cmd = Command::Account(wallet::cli::account::AccountSubcommand::Label { + account_id: public_mention(ctx.existing_public_accounts()[1]), + label: to_label.clone(), + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), label_cmd).await?; + + // Send using labels instead of account IDs + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: CliAccountMention::Label(from_label), + to: Some(CliAccountMention::Label(to_label)), + to_npk: None, + to_vpk: None, + to_identifier: Some(0), + amount: 100, + }); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let acc_1_balance = sequencer_service_rpc::RpcClient::get_account_balance( + ctx.sequencer_client(), + ctx.existing_public_accounts()[0], + ) + .await?; + let acc_2_balance = sequencer_service_rpc::RpcClient::get_account_balance( + ctx.sequencer_client(), + ctx.existing_public_accounts()[1], + ) + .await?; + + assert_eq!(acc_1_balance, 9900); + assert_eq!(acc_2_balance, 20100); + + info!("Waiting for indexer to parse blocks"); + wait_for_indexer_to_catch_up(&ctx).await?; + + let acc1_ind_state = ctx + .indexer_client() + .get_account(ctx.existing_public_accounts()[0].into()) + .await + .unwrap(); + let acc1_seq_state = sequencer_service_rpc::RpcClient::get_account( + ctx.sequencer_client(), + ctx.existing_public_accounts()[0], + ) + .await?; + + assert_eq!(acc1_ind_state, acc1_seq_state.into()); + + info!("Indexer state is consistent after label-based transfer"); + + Ok(()) +} diff --git a/integration_tests/tests/indexer_test_run.rs b/integration_tests/tests/indexer_test_run.rs new file mode 100644 index 00000000..b54cdf82 --- /dev/null +++ b/integration_tests/tests/indexer_test_run.rs @@ -0,0 +1,25 @@ +#![expect( + clippy::tests_outside_test_module, + reason = "We don't care about these in tests" +)] + +use anyhow::Result; +use integration_tests::{TestContext, wait_for_indexer_to_catch_up}; +use log::info; + +#[tokio::test] +async fn indexer_test_run() -> Result<()> { + let ctx = TestContext::new().await?; + + let last_block_indexer = wait_for_indexer_to_catch_up(&ctx).await?; + + let last_block_seq = + sequencer_service_rpc::RpcClient::get_last_block_id(ctx.sequencer_client()).await?; + + info!("Last block on seq now is {last_block_seq}"); + info!("Last block on ind now is {last_block_indexer}"); + + assert!(last_block_indexer > 0); + + Ok(()) +} diff --git a/integration_tests/tests/indexer_test_run_ffi.rs b/integration_tests/tests/indexer_test_run_ffi.rs new file mode 100644 index 00000000..2c7c2103 --- /dev/null +++ b/integration_tests/tests/indexer_test_run_ffi.rs @@ -0,0 +1,37 @@ +#![expect( + clippy::tests_outside_test_module, + clippy::undocumented_unsafe_blocks, + reason = "We don't care about these in tests" +)] + +use anyhow::Result; +use indexer_ffi::Runtime; +use integration_tests::L2_TO_L1_TIMEOUT; +use log::info; + +#[path = "indexer_ffi_helpers/mod.rs"] +mod indexer_ffi_helpers; + +#[test] +fn indexer_test_run_ffi() -> Result<()> { + let (ctx, indexer_ffi, _indexer_dir) = indexer_ffi_helpers::setup()?; + + // RUN OBSERVATION + std::thread::sleep(L2_TO_L1_TIMEOUT); + + // Safety: ctx runtime is valid for the lifetime of the returned Runtime + let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) }; + let last_block_indexer_ffi_res = unsafe { + indexer_ffi_helpers::query_last_block(&raw const runtime, &raw const indexer_ffi) + }; + + assert!(last_block_indexer_ffi_res.error.is_ok()); + + let last_block_indexer_ffi = unsafe { *last_block_indexer_ffi_res.value }; + + info!("Last block on indexer FFI now is {last_block_indexer_ffi}"); + + assert!(last_block_indexer_ffi > 0); + + Ok(()) +}