mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-06-02 07:09:29 +00:00
split indexer integration tests
This commit is contained in:
parent
4bcffafe27
commit
9f1dc1d24b
@ -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<u64> {
|
||||
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}"
|
||||
)
|
||||
})?
|
||||
}
|
||||
|
||||
@ -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<u64> {
|
||||
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(())
|
||||
}
|
||||
40
integration_tests/tests/indexer_block_batching.rs
Normal file
40
integration_tests/tests/indexer_block_batching.rs
Normal file
@ -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(())
|
||||
}
|
||||
@ -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<u64, OperationStatus>;
|
||||
|
||||
unsafe fn query_block_vec(
|
||||
runtime: *const Runtime,
|
||||
indexer: *const IndexerServiceFFI,
|
||||
before: FfiOption<u64>,
|
||||
limit: u64,
|
||||
) -> PointerResult<FfiVec<FfiBlock>, OperationStatus>;
|
||||
|
||||
unsafe fn query_account(
|
||||
runtime: *const Runtime,
|
||||
indexer: *const IndexerServiceFFI,
|
||||
account_id: FfiAccountId,
|
||||
) -> PointerResult<FfiAccount, OperationStatus>;
|
||||
|
||||
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::<u64>::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(())
|
||||
}
|
||||
66
integration_tests/tests/indexer_ffi_block_batching.rs
Normal file
66
integration_tests/tests/indexer_ffi_block_batching.rs
Normal file
@ -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::<u64>::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(())
|
||||
}
|
||||
91
integration_tests/tests/indexer_ffi_helpers/mod.rs
Normal file
91
integration_tests/tests/indexer_ffi_helpers/mod.rs
Normal file
@ -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<u64, OperationStatus>;
|
||||
|
||||
pub unsafe fn query_block_vec(
|
||||
runtime: *const Runtime,
|
||||
indexer: *const IndexerServiceFFI,
|
||||
before: FfiOption<u64>,
|
||||
limit: u64,
|
||||
) -> PointerResult<FfiVec<FfiBlock>, OperationStatus>;
|
||||
|
||||
pub unsafe fn query_account(
|
||||
runtime: *const Runtime,
|
||||
indexer: *const IndexerServiceFFI,
|
||||
account_id: FfiAccountId,
|
||||
) -> PointerResult<FfiAccount, OperationStatus>;
|
||||
|
||||
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))
|
||||
}
|
||||
151
integration_tests/tests/indexer_ffi_state_consistency.rs
Normal file
151
integration_tests/tests/indexer_ffi_state_consistency.rs
Normal file
@ -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(())
|
||||
}
|
||||
@ -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(())
|
||||
}
|
||||
118
integration_tests/tests/indexer_state_consistency.rs
Normal file
118
integration_tests/tests/indexer_state_consistency.rs
Normal file
@ -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(())
|
||||
}
|
||||
@ -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(())
|
||||
}
|
||||
25
integration_tests/tests/indexer_test_run.rs
Normal file
25
integration_tests/tests/indexer_test_run.rs
Normal file
@ -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(())
|
||||
}
|
||||
37
integration_tests/tests/indexer_test_run_ffi.rs
Normal file
37
integration_tests/tests/indexer_test_run_ffi.rs
Normal file
@ -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(())
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user