From 8eb9f7ca90103a27afcfbde9d2d1e92960b57fef Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Mon, 27 Jul 2026 12:44:42 +0300 Subject: [PATCH] feat(integration_tests): multi-node config --- integration_tests/tests/block_size_limit.rs | 7 +- .../tests/indexer_ffi_helpers/mod.rs | 5 +- integration_tests/tests/program_deployment.rs | 3 +- integration_tests/tests/tps.rs | 3 +- integration_tests/tests/wallet_ffi.rs | 47 +++--- .../src/bin/regenerate_test_fixture.rs | 2 +- test_fixtures/src/config.rs | 30 +++- test_fixtures/src/lib.rs | 153 ++++++++++++------ test_fixtures/src/setup.rs | 5 +- test_fixtures/tests/prebuilt_fixture.rs | 10 +- 10 files changed, 175 insertions(+), 90 deletions(-) diff --git a/integration_tests/tests/block_size_limit.rs b/integration_tests/tests/block_size_limit.rs index d97b695d8..3f936d658 100644 --- a/integration_tests/tests/block_size_limit.rs +++ b/integration_tests/tests/block_size_limit.rs @@ -14,11 +14,12 @@ use integration_tests::{ }; use lee::program::Program; use sequencer_service_rpc::RpcClient as _; +use test_fixtures::config::MultiNodeTestContextConfig; use tokio::test; #[test] async fn reject_oversized_transaction() -> Result<()> { - let ctx = TestContext::builder() + let ctx = TestContext::builder(MultiNodeTestContextConfig::default()) .with_sequencer_partial_config(SequencerPartialConfig { max_num_tx_in_block: 100, max_block_size: ByteSize::mib(1), @@ -61,7 +62,7 @@ async fn reject_oversized_transaction() -> Result<()> { #[test] async fn accept_transaction_within_limit() -> Result<()> { - let ctx = TestContext::builder() + let ctx = TestContext::builder(MultiNodeTestContextConfig::default()) .with_sequencer_partial_config(SequencerPartialConfig { max_num_tx_in_block: 100, max_block_size: ByteSize::mib(1), @@ -102,7 +103,7 @@ async fn transaction_deferred_to_next_block_when_current_full() -> Result<()> { let max_program_size = claimer.elf().len().max(chain_caller.elf().len()); let block_size = ByteSize::b((max_program_size + 10 * 1024) as u64); - let ctx = TestContext::builder() + let ctx = TestContext::builder(MultiNodeTestContextConfig::default()) .with_sequencer_partial_config(SequencerPartialConfig { max_num_tx_in_block: 100, max_block_size: block_size, diff --git a/integration_tests/tests/indexer_ffi_helpers/mod.rs b/integration_tests/tests/indexer_ffi_helpers/mod.rs index 09e0a9271..e8e51ef4a 100644 --- a/integration_tests/tests/indexer_ffi_helpers/mod.rs +++ b/integration_tests/tests/indexer_ffi_helpers/mod.rs @@ -19,6 +19,7 @@ use indexer_ffi::{ }; use integration_tests::{BlockingTestContext, TestContext}; use tempfile::TempDir; +use test_fixtures::config::MultiNodeTestContextConfig; unsafe extern "C" { pub unsafe fn query_last_block(indexer: *const IndexerServiceFFI) -> LastBlockIdResult; @@ -83,7 +84,9 @@ pub fn setup_indexer_ffi(bedrock_addr: SocketAddr) -> Result<(IndexerServiceFFI, } pub fn setup() -> Result<(BlockingTestContext, IndexerServiceFFI, TempDir)> { - let ctx = TestContext::builder().disable_indexer().build_blocking()?; + let ctx = TestContext::builder(MultiNodeTestContextConfig::default()) + .disable_indexer() + .build_blocking()?; // Don't borrow `ctx.runtime()`: `ctx` (and its by-value tokio runtime) is // moved into the returned tuple, which would leave any pointer into it // dangling. Pass a null runtime so the FFI owns its own — the same path the diff --git a/integration_tests/tests/program_deployment.rs b/integration_tests/tests/program_deployment.rs index 3c620168e..db60325c5 100644 --- a/integration_tests/tests/program_deployment.rs +++ b/integration_tests/tests/program_deployment.rs @@ -10,6 +10,7 @@ use common::transaction::LeeTransaction; use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, get_account, new_account}; use log::info; use sequencer_service_rpc::RpcClient as _; +use test_fixtures::config::MultiNodeTestContextConfig; use tokio::test; use wallet::{cli::Command, config::WalletConfigOverrides}; @@ -68,7 +69,7 @@ async fn deploy_invalid_program_fails() -> Result<()> { // An invalid program bytecode is rejected by the sequencer during block production, so the // deployment transaction is never included in a block. Shrink the wallet's polling window so // the command gives up quickly instead of waiting for the full default timeout. - let mut ctx = TestContext::builder() + let mut ctx = TestContext::builder(MultiNodeTestContextConfig::default()) .with_wallet_config_overrides(WalletConfigOverrides { seq_poll_timeout: Some(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)), seq_tx_poll_max_blocks: Some(5), diff --git a/integration_tests/tests/tps.rs b/integration_tests/tests/tps.rs index bfcd4c64e..1f59d264b 100644 --- a/integration_tests/tests/tps.rs +++ b/integration_tests/tests/tps.rs @@ -29,6 +29,7 @@ use lee_core::{ use log::info; use sequencer_core::config::GenesisAction; use sequencer_service_rpc::RpcClient as _; +use test_fixtures::config::MultiNodeTestContextConfig; use tokio::test; pub(crate) struct TpsTestManager { @@ -177,7 +178,7 @@ pub async fn tps_test() -> Result<()> { let target_tps = 8; let tps_test = TpsTestManager::new(target_tps, num_transactions); - let ctx = TestContext::builder() + let ctx = TestContext::builder(MultiNodeTestContextConfig::default()) .with_sequencer_partial_config(TpsTestManager::generate_sequencer_partial_config()) .with_genesis(tps_test.generate_genesis()) .build() diff --git a/integration_tests/tests/wallet_ffi.rs b/integration_tests/tests/wallet_ffi.rs index a36eb0694..57dfd34d8 100644 --- a/integration_tests/tests/wallet_ffi.rs +++ b/integration_tests/tests/wallet_ffi.rs @@ -28,6 +28,7 @@ use lee::{ }; use lee_core::program::DEFAULT_PROGRAM_ID; use log::info; +use test_fixtures::config::MultiNodeTestContextConfig; use wallet::{account::HumanReadableAccount, program_facades::vault::Vault}; use wallet_ffi::{ FfiAccount, FfiAccountIdWithPrivacy, FfiAccountIdentity, FfiAccountList, FfiBytes32, @@ -389,7 +390,7 @@ fn load_existing_ffi_wallet(home: &Path) -> Result<*mut WalletHandle> { #[test] fn wallet_ffi_create_public_accounts() -> Result<()> { - let ctx = BlockingTestContext::new()?; + let ctx = BlockingTestContext::new(MultiNodeTestContextConfig::default())?; let n_accounts = 10; // Create `n_accounts` public accounts with wallet FFI @@ -430,7 +431,7 @@ fn wallet_ffi_create_public_accounts() -> Result<()> { #[test] fn wallet_ffi_create_private_accounts() -> Result<()> { - let ctx = BlockingTestContext::new()?; + let ctx = BlockingTestContext::new(MultiNodeTestContextConfig::default())?; let n_accounts = 10; // Create `n_accounts` receiving keys with wallet FFI let new_npks_ffi = unsafe { @@ -465,7 +466,7 @@ fn wallet_ffi_create_private_accounts() -> Result<()> { #[test] fn wallet_ffi_save_and_load_persistent_storage() -> Result<()> { - let ctx = BlockingTestContext::new()?; + let ctx = BlockingTestContext::new(MultiNodeTestContextConfig::default())?; let home = tempfile::tempdir()?; // Create a receiving key and save let first_npk = unsafe { @@ -505,7 +506,7 @@ fn wallet_ffi_save_and_load_persistent_storage() -> Result<()> { #[test] fn test_wallet_ffi_list_accounts() -> Result<()> { - let ctx = BlockingTestContext::new()?; + let ctx = BlockingTestContext::new(MultiNodeTestContextConfig::default())?; // Create the wallet FFI and track which account IDs were created as public/private let (wallet_ffi_handle, created_public_ids) = unsafe { let home = tempfile::tempdir()?; @@ -574,7 +575,7 @@ fn test_wallet_ffi_list_accounts() -> Result<()> { #[test] fn test_wallet_ffi_get_balance_public() -> Result<()> { - let ctx = BlockingTestContext::new()?; + let ctx = BlockingTestContext::new(MultiNodeTestContextConfig::default())?; let account_id: AccountId = ctx.ctx().existing_public_accounts()[0]; let home = tempfile::tempdir()?; let FfiCreateWalletOutput { @@ -607,7 +608,7 @@ fn test_wallet_ffi_get_balance_public() -> Result<()> { #[test] fn test_wallet_ffi_get_account_public() -> Result<()> { - let ctx = BlockingTestContext::new()?; + let ctx = BlockingTestContext::new(MultiNodeTestContextConfig::default())?; let account_id: AccountId = ctx.ctx().existing_public_accounts()[0]; let home = tempfile::tempdir()?; let FfiCreateWalletOutput { @@ -647,7 +648,7 @@ fn test_wallet_ffi_get_account_public() -> Result<()> { #[test] fn test_wallet_ffi_get_account_private() -> Result<()> { - let ctx = BlockingTestContext::new()?; + let ctx = BlockingTestContext::new(MultiNodeTestContextConfig::default())?; let account_id: AccountId = ctx.ctx().existing_private_accounts()[0]; let home = tempfile::tempdir()?; let FfiCreateWalletOutput { @@ -686,7 +687,7 @@ fn test_wallet_ffi_get_account_private() -> Result<()> { #[test] fn test_wallet_ffi_get_public_account_keys() -> Result<()> { - let ctx = BlockingTestContext::new()?; + let ctx = BlockingTestContext::new(MultiNodeTestContextConfig::default())?; let account_id: AccountId = ctx.ctx().existing_public_accounts()[0]; let home = tempfile::tempdir()?; let FfiCreateWalletOutput { @@ -728,7 +729,7 @@ fn test_wallet_ffi_get_public_account_keys() -> Result<()> { #[test] fn test_wallet_ffi_get_private_account_keys() -> Result<()> { - let ctx = BlockingTestContext::new()?; + let ctx = BlockingTestContext::new(MultiNodeTestContextConfig::default())?; let account_id: AccountId = ctx.ctx().existing_private_accounts()[0]; let home = tempfile::tempdir()?; let FfiCreateWalletOutput { @@ -814,7 +815,7 @@ fn wallet_ffi_base58_to_account_id() -> Result<()> { #[test] fn wallet_ffi_init_public_account_auth_transfer() -> Result<()> { - let ctx = BlockingTestContext::new()?; + let ctx = BlockingTestContext::new(MultiNodeTestContextConfig::default())?; let home = tempfile::tempdir()?; let FfiCreateWalletOutput { wallet: wallet_ffi_handle, @@ -880,7 +881,7 @@ fn wallet_ffi_init_public_account_auth_transfer() -> Result<()> { #[test] fn wallet_ffi_init_private_account_auth_transfer() -> Result<()> { - let ctx = BlockingTestContext::new()?; + let ctx = BlockingTestContext::new(MultiNodeTestContextConfig::default())?; let home = tempfile::tempdir()?; let FfiCreateWalletOutput { wallet: wallet_ffi_handle, @@ -940,7 +941,7 @@ fn wallet_ffi_init_private_account_auth_transfer() -> Result<()> { #[test] fn test_wallet_ffi_transfer_public() -> Result<()> { - let ctx = BlockingTestContext::new()?; + let ctx = BlockingTestContext::new(MultiNodeTestContextConfig::default())?; let home = tempfile::tempdir()?; let FfiCreateWalletOutput { wallet: wallet_ffi_handle, @@ -997,7 +998,7 @@ fn test_wallet_ffi_transfer_public() -> Result<()> { #[test] fn test_wallet_ffi_transfer_shielded() -> Result<()> { - let ctx = BlockingTestContext::new()?; + let ctx = BlockingTestContext::new(MultiNodeTestContextConfig::default())?; let home = tempfile::tempdir()?; let FfiCreateWalletOutput { wallet: wallet_ffi_handle, @@ -1080,7 +1081,7 @@ fn test_wallet_ffi_transfer_shielded() -> Result<()> { #[test] fn test_wallet_ffi_transfer_deshielded() -> Result<()> { - let ctx = BlockingTestContext::new()?; + let ctx = BlockingTestContext::new(MultiNodeTestContextConfig::default())?; let home = tempfile::tempdir()?; let FfiCreateWalletOutput { wallet: wallet_ffi_handle, @@ -1143,7 +1144,7 @@ fn test_wallet_ffi_transfer_deshielded() -> Result<()> { #[test] fn test_wallet_ffi_transfer_private() -> Result<()> { - let ctx = BlockingTestContext::new()?; + let ctx = BlockingTestContext::new(MultiNodeTestContextConfig::default())?; let home = tempfile::tempdir()?; let FfiCreateWalletOutput { wallet: wallet_ffi_handle, @@ -1226,7 +1227,7 @@ fn test_wallet_ffi_transfer_private() -> Result<()> { #[test] fn restore_keys_from_seed_ffi() -> Result<()> { - let ctx = BlockingTestContext::new()?; + let ctx = BlockingTestContext::new(MultiNodeTestContextConfig::default())?; let home = tempfile::tempdir()?; let FfiCreateWalletOutput { wallet: wallet_ffi_handle, @@ -1523,7 +1524,7 @@ fn restore_keys_from_seed_ffi() -> Result<()> { #[test] fn test_wallet_ffi_bridge_withdraw() -> Result<()> { - let ctx = BlockingTestContext::new()?; + let ctx = BlockingTestContext::new(MultiNodeTestContextConfig::default())?; let home = tempfile::tempdir()?; let FfiCreateWalletOutput { wallet: wallet_ffi_handle, @@ -1586,7 +1587,7 @@ fn test_wallet_ffi_bridge_withdraw() -> Result<()> { #[test] fn test_wallet_ffi_transfer_generic_public() -> Result<()> { - let ctx = BlockingTestContext::new()?; + let ctx = BlockingTestContext::new(MultiNodeTestContextConfig::default())?; let home = tempfile::tempdir()?; let FfiCreateWalletOutput { wallet: wallet_ffi_handle, @@ -1680,7 +1681,7 @@ fn test_wallet_ffi_transfer_generic_public() -> Result<()> { #[test] fn test_wallet_ffi_transfer_generic_private() -> Result<()> { - let ctx = BlockingTestContext::new()?; + let ctx = BlockingTestContext::new(MultiNodeTestContextConfig::default())?; let home = tempfile::tempdir()?; let FfiCreateWalletOutput { wallet: wallet_ffi_handle, @@ -1789,7 +1790,7 @@ fn test_wallet_ffi_transfer_generic_private() -> Result<()> { #[test] fn test_wallet_ffi_vault_balance_and_claim_public() -> Result<()> { - let ctx = BlockingTestContext::new()?; + let ctx = BlockingTestContext::new(MultiNodeTestContextConfig::default())?; let home = tempfile::tempdir()?; let FfiCreateWalletOutput { wallet: wallet_ffi_handle, @@ -1874,7 +1875,7 @@ fn test_wallet_ffi_vault_balance_and_claim_public() -> Result<()> { #[test] fn test_wallet_ffi_vault_balance_and_claim_private() -> Result<()> { - let ctx = BlockingTestContext::new()?; + let ctx = BlockingTestContext::new(MultiNodeTestContextConfig::default())?; let home = tempfile::tempdir()?; let FfiCreateWalletOutput { wallet: wallet_ffi_handle, @@ -1966,7 +1967,7 @@ fn test_wallet_ffi_vault_balance_and_claim_private() -> Result<()> { #[test] fn test_wallet_ffi_single_label() -> Result<()> { - let ctx = BlockingTestContext::new()?; + let ctx = BlockingTestContext::new(MultiNodeTestContextConfig::default())?; let home = tempfile::tempdir()?; let FfiCreateWalletOutput { wallet: wallet_ffi_handle, @@ -2015,7 +2016,7 @@ fn test_wallet_ffi_single_label() -> Result<()> { #[test] fn test_wallet_ffi_more_labels() -> Result<()> { - let ctx = BlockingTestContext::new()?; + let ctx = BlockingTestContext::new(MultiNodeTestContextConfig::default())?; let home = tempfile::tempdir()?; let FfiCreateWalletOutput { wallet: wallet_ffi_handle, diff --git a/test_fixtures/src/bin/regenerate_test_fixture.rs b/test_fixtures/src/bin/regenerate_test_fixture.rs index 038d4b0cd..a04a49a25 100644 --- a/test_fixtures/src/bin/regenerate_test_fixture.rs +++ b/test_fixtures/src/bin/regenerate_test_fixture.rs @@ -57,7 +57,7 @@ async fn generate_prebuilt_fixture(dest: &Path) -> Result<()> { .context("Failed to setup Sequencer for fixture generation")?; let (mut wallet, _temp_wallet_dir, _wallet_password) = setup_wallet( - sequencer_handle.addr(), + &[sequencer_handle.addr()], &initial_public_accounts, &initial_private_accounts, WalletConfigOverrides::default(), diff --git a/test_fixtures/src/config.rs b/test_fixtures/src/config.rs index 9a7df9db6..afb0f88b2 100644 --- a/test_fixtures/src/config.rs +++ b/test_fixtures/src/config.rs @@ -75,6 +75,20 @@ impl std::fmt::Display for UrlProtocol { } } +#[derive(Debug, Clone, Copy)] +/// Config for test context in multi-node case. +/// +/// For now have only one field: `num_nodes`. +pub struct MultiNodeTestContextConfig { + pub num_nodes: usize, +} + +impl Default for MultiNodeTestContextConfig { + fn default() -> Self { + Self { num_nodes: 1 } + } +} + pub fn sequencer_config( partial: SequencerPartialConfig, home: PathBuf, @@ -211,13 +225,19 @@ pub fn genesis_from_accounts( .collect() } -pub fn wallet_config(sequencer_addr: SocketAddr) -> Result { - Ok(WalletConfig { - sequencers: vec![SequencerConnectionData { - sequencer_addr: addr_to_url(UrlProtocol::Http, sequencer_addr) +pub fn wallet_config(sequencer_addrs: &[SocketAddr]) -> Result { + let mut sequencers = vec![]; + + for addr in sequencer_addrs { + sequencers.push(SequencerConnectionData { + sequencer_addr: addr_to_url(UrlProtocol::Http, *addr) .context("Failed to convert sequencer addr to URL")?, basic_auth: None, - }], + }); + } + + Ok(WalletConfig { + sequencers, seq_poll_timeout: Duration::from_secs(30), seq_tx_poll_max_blocks: 15, seq_poll_max_retries: 10, diff --git a/test_fixtures/src/lib.rs b/test_fixtures/src/lib.rs index 596354ab8..390c7b7f3 100644 --- a/test_fixtures/src/lib.rs +++ b/test_fixtures/src/lib.rs @@ -1,7 +1,7 @@ //! Shared test/bench fixtures: spins up bedrock + sequencer + indexer + wallet //! end-to-end against docker-compose, exposes a `TestContext` callers can drive. -use std::{net::SocketAddr, path::Path, sync::LazyLock}; +use std::{collections::HashMap, net::SocketAddr, path::Path, sync::LazyLock}; use anyhow::{Context as _, Result}; use common::{HashType, transaction::LeeTransaction}; @@ -22,6 +22,7 @@ use wallet::{ }; use crate::{ + config::MultiNodeTestContextConfig, indexer_client::IndexerClient, setup::{ SequencerSetup, setup_bedrock_node, setup_indexer, @@ -76,28 +77,36 @@ pub struct DiskSizes { /// as each instance uses its own temporary directories for sequencer and wallet data. // NOTE: Order of fields is important for proper drop order. pub struct TestContext { - sequencer_client: SequencerClient, + sequencer_clients: HashMap, wallet: WalletCore, wallet_password: String, /// Optional to move out value in Drop. - sequencer_handle: Option, + sequencer_handles: Option>, indexer_components: Option, bedrock_compose: DockerCompose, bedrock_addr: SocketAddr, - temp_sequencer_dir: TempDir, + temp_sequencer_dirs: Vec, temp_wallet_dir: TempDir, + config: MultiNodeTestContextConfig, } impl TestContext { /// Create new test context. + pub async fn new_custom(config: MultiNodeTestContextConfig) -> Result { + Self::builder(config).build().await + } + + /// Create new test context with default config. pub async fn new() -> Result { - Self::builder().build().await + Self::builder(MultiNodeTestContextConfig::default()) + .build() + .await } /// Get a builder for the test context to customize its configuration. #[must_use] - pub fn builder() -> TestContextBuilder { - TestContextBuilder::new() + pub fn builder(config: MultiNodeTestContextConfig) -> TestContextBuilder { + TestContextBuilder::new(config) } /// Get reference to the wallet. @@ -116,10 +125,19 @@ impl TestContext { &mut self.wallet } + /// Get reference to the sequencer client in default case (1 sequencer). + #[must_use] + pub fn sequencer_client(&self) -> &SequencerClient { + self.sequencer_clients + .values() + .next() + .expect("Must be at least one sequencer client") + } + /// Get reference to the sequencer client. #[must_use] - pub const fn sequencer_client(&self) -> &SequencerClient { - &self.sequencer_client + pub fn sequencer_client_getter(&self, addr: &SocketAddr) -> Option<&SequencerClient> { + self.sequencer_clients.get(addr) } /// Get the Bedrock Node address. @@ -166,12 +184,21 @@ impl TestContext { .expect("Called `TestContext::indexer_client()` on context with disabled indexer") } + #[must_use] + /// Get the multi-node config. + pub const fn config(&self) -> &MultiNodeTestContextConfig { + &self.config + } + /// Recursively-sized bytes on disk for sequencer + indexer + wallet tempdirs. /// Indexer bytes are zero if the indexer is disabled. #[must_use] pub fn disk_sizes(&self) -> DiskSizes { DiskSizes { - sequencer_bytes: dir_size_bytes(self.temp_sequencer_dir.path()), + sequencer_bytes: self + .temp_sequencer_dirs + .iter() + .fold(0, |acc, dir| acc.saturating_add(dir_size_bytes(dir.path()))), indexer_bytes: self .indexer_components .as_ref() @@ -206,28 +233,32 @@ impl TestContext { impl Drop for TestContext { fn drop(&mut self) { let Self { - sequencer_handle, + sequencer_handles, bedrock_compose, bedrock_addr: _, indexer_components: _, - sequencer_client: _, + sequencer_clients: _, wallet: _, wallet_password: _, - temp_sequencer_dir: _, + temp_sequencer_dirs: _, temp_wallet_dir: _, + config: _, } = self; - let sequencer_handle = sequencer_handle + let sequencer_handles = sequencer_handles .take() .expect("Sequencer handle should be present in TestContext drop"); - if !sequencer_handle.is_healthy() { - let Err(err) = sequencer_handle - .failed() - .now_or_never() - .expect("Sequencer handle should not be running"); - error!( - "Sequencer handle has unexpectedly stopped before TestContext drop with error: {err:#}" - ); + + for sequencer_handle in sequencer_handles { + if !sequencer_handle.is_healthy() { + let Err(err) = sequencer_handle + .failed() + .now_or_never() + .expect("Sequencer handle should not be running"); + error!( + "Sequencer handle has unexpectedly stopped before TestContext drop with error: {err:#}" + ); + } } let container = bedrock_compose @@ -256,16 +287,18 @@ pub struct TestContextBuilder { enable_indexer: bool, wallet_config_overrides: WalletConfigOverrides, from_scratch: bool, + config: MultiNodeTestContextConfig, } impl TestContextBuilder { - fn new() -> Self { + fn new(config: MultiNodeTestContextConfig) -> Self { Self { genesis_transactions: None, sequencer_partial_config: None, enable_indexer: true, wallet_config_overrides: WalletConfigOverrides::default(), from_scratch: false, + config, } } @@ -320,6 +353,7 @@ impl TestContextBuilder { enable_indexer, wallet_config_overrides, from_scratch, + config, } = self; // Ensure logger is initialized only once @@ -357,29 +391,48 @@ impl TestContextBuilder { let partial_config = sequencer_partial_config.unwrap_or_default(); - let mut sequencer_setup = SequencerSetup::new(partial_config, bedrock_addr); - if !use_prebuilt { - // Wallet genesis must always be present so that - // setup_public/private_accounts_with_initial_supply can claim from the vault PDAs. - // When a test supplies custom genesis, merge rather than replace. - let wallet_genesis = - config::genesis_from_accounts(&initial_public_accounts, &initial_private_accounts); - let genesis = match genesis_transactions { - Some(mut custom) => { - custom.extend(wallet_genesis); - custom - } - None => wallet_genesis, - }; - sequencer_setup = sequencer_setup.with_genesis(genesis); + let mut sequencer_handles = vec![]; + let mut temp_sequencer_dirs = vec![]; + let mut sequencer_clients = HashMap::new(); + + for _ in 0..config.num_nodes { + let mut sequencer_setup = SequencerSetup::new(partial_config, bedrock_addr); + if !use_prebuilt { + // Wallet genesis must always be present so that + // setup_public/private_accounts_with_initial_supply can claim from the vault PDAs. + // When a test supplies custom genesis, merge rather than replace. + let wallet_genesis = config::genesis_from_accounts( + &initial_public_accounts, + &initial_private_accounts, + ); + let genesis = match genesis_transactions.clone() { + Some(mut custom) => { + custom.extend(wallet_genesis); + custom + } + None => wallet_genesis, + }; + sequencer_setup = sequencer_setup.with_genesis(genesis); + } + let (sequencer_handle, temp_sequencer_dir) = sequencer_setup + .setup() + .await + .context("Failed to setup Sequencer")?; + + let sequencer_client = setup::sequencer_client(sequencer_handle.addr()) + .context("Failed to create sequencer client")?; + + sequencer_clients.insert(sequencer_handle.addr(), sequencer_client); + + sequencer_handles.push(sequencer_handle); + temp_sequencer_dirs.push(temp_sequencer_dir); } - let (sequencer_handle, temp_sequencer_dir) = sequencer_setup - .setup() - .await - .context("Failed to setup Sequencer")?; let (mut wallet, temp_wallet_dir, wallet_password) = setup_wallet( - sequencer_handle.addr(), + &sequencer_handles + .iter() + .map(sequencer_service::SequencerHandle::addr) + .collect::>(), &initial_public_accounts, &initial_private_accounts, wallet_config_overrides, @@ -402,19 +455,17 @@ impl TestContextBuilder { .context("Failed to initialize private accounts in wallet")?; } - let sequencer_client = setup::sequencer_client(sequencer_handle.addr()) - .context("Failed to create sequencer client")?; - Ok(TestContext { - sequencer_client, + sequencer_clients, wallet, wallet_password, bedrock_compose, bedrock_addr, - sequencer_handle: Some(sequencer_handle), + sequencer_handles: Some(sequencer_handles), indexer_components, - temp_sequencer_dir, + temp_sequencer_dirs, temp_wallet_dir, + config, }) } @@ -437,8 +488,8 @@ pub struct BlockingTestContext { } impl BlockingTestContext { - pub fn new() -> Result { - TestContext::builder().build_blocking() + pub fn new(config: MultiNodeTestContextConfig) -> Result { + TestContext::builder(config).build_blocking() } pub const fn ctx(&self) -> &TestContext { diff --git a/test_fixtures/src/setup.rs b/test_fixtures/src/setup.rs index 6c60a7e80..124c13bc0 100644 --- a/test_fixtures/src/setup.rs +++ b/test_fixtures/src/setup.rs @@ -277,12 +277,13 @@ pub async fn setup_indexer( } pub async fn setup_wallet( - sequencer_addr: SocketAddr, + sequencer_addrs: &[SocketAddr], initial_public_accounts: &[(PrivateKey, u128)], initial_private_accounts: &[InitialPrivateAccountForWallet], config_overrides: WalletConfigOverrides, ) -> Result<(WalletCore, TempDir, String)> { - let config = config::wallet_config(sequencer_addr).context("Failed to create Wallet config")?; + let config = + config::wallet_config(sequencer_addrs).context("Failed to create Wallet config")?; let config_serialized = serde_json::to_string_pretty(&config).context("Failed to serialize Wallet config")?; diff --git a/test_fixtures/tests/prebuilt_fixture.rs b/test_fixtures/tests/prebuilt_fixture.rs index 4960af812..27a12364a 100644 --- a/test_fixtures/tests/prebuilt_fixture.rs +++ b/test_fixtures/tests/prebuilt_fixture.rs @@ -7,14 +7,20 @@ use lee::{AccountId, PublicKey}; use sequencer_service_rpc::RpcClient as _; use test_fixtures::{ TestContext, - config::{default_private_accounts_for_wallet, default_public_accounts_for_wallet}, + config::{ + MultiNodeTestContextConfig, default_private_accounts_for_wallet, + default_public_accounts_for_wallet, + }, verify_commitment_is_in_state, }; /// Builds from genesis (no prebuilt database) and checks the on-chain state follows the config. #[tokio::test] async fn genesis_from_scratch_follows_config() -> Result<()> { - let ctx = TestContext::builder().from_scratch().build().await?; + let ctx = TestContext::builder(MultiNodeTestContextConfig::default()) + .from_scratch() + .build() + .await?; assert_context_follows_config(&ctx).await }