From 64f85bf37dc3617b4ff52cb38ecb72ba98d83ae0 Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Mon, 29 Dec 2025 10:31:18 -0500 Subject: [PATCH 01/25] chore: use local svm crate --- Cargo.lock | 1 - Cargo.toml | 8 +++++--- test-integration/Cargo.lock | 14 +++++++------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f424f4dca..7b6eba2e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7070,7 +7070,6 @@ dependencies = [ [[package]] name = "solana-svm" version = "2.2.1" -source = "git+https://github.com/magicblock-labs/magicblock-svm.git?rev=3e9456ec4#3e9456ec4d5798ad8281537501c1e777d6888ba3" dependencies = [ "ahash 0.8.12", "log", diff --git a/Cargo.toml b/Cargo.toml index 5759aa2de..751e25303 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -203,8 +203,9 @@ tonic-build = "0.9.2" url = "2.5.0" [workspace.dependencies.solana-svm] -git = "https://github.com/magicblock-labs/magicblock-svm.git" -rev = "3e9456ec4" +path = "../magicblock-svm" +# git = "https://github.com/magicblock-labs/magicblock-svm.git" +# rev = "3e9456ec4" features = ["dev-context-only-utils"] [workspace.dependencies.rocksdb] @@ -220,7 +221,8 @@ version = "0.22.0" # and we use protobuf-src v2.1.1. Otherwise compilation fails solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "57158728" } solana-storage-proto = { path = "./storage-proto" } -solana-svm = { git = "https://github.com/magicblock-labs/magicblock-svm.git", rev = "3e9456ec4" } +# solana-svm = { git = "https://github.com/magicblock-labs/magicblock-svm.git", rev = "3e9456ec4" } +solana-svm = { path = "../magicblock-svm" } # Fork is used to enable `disable_manual_compaction` usage # Fork is based on commit d4e9e16 of rocksdb (parent commit of 0.23.0 release) # without patching update isn't possible due to conflict with solana deps diff --git a/test-integration/Cargo.lock b/test-integration/Cargo.lock index 8ceb846cb..6c8ff2af9 100644 --- a/test-integration/Cargo.lock +++ b/test-integration/Cargo.lock @@ -3643,13 +3643,14 @@ dependencies = [ "solana-pubkey", "solana-rent-collector", "solana-sdk-ids", - "solana-svm 2.2.1 (git+https://github.com/magicblock-labs/magicblock-svm.git?rev=3e9456ec4)", + "solana-svm 2.2.1", "solana-svm-transaction", "solana-system-program", "solana-transaction", "solana-transaction-error", "solana-transaction-status", "tokio", + "tokio-util 0.7.15", ] [[package]] @@ -8509,13 +8510,11 @@ dependencies = [ [[package]] name = "solana-svm" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850baf834aba4a94a7558fa6cf6ca93fad284abf0363dec5fb9cab173a11fc4" dependencies = [ "ahash 0.8.12", - "itertools 0.12.1", "log", "percentage", + "qualifier_attr", "serde", "serde_derive", "solana-account", @@ -8540,6 +8539,7 @@ dependencies = [ "solana-pubkey", "solana-rent", "solana-rent-debits", + "solana-reserved-account-keys", "solana-sdk", "solana-sdk-ids", "solana-svm-rent-collector", @@ -8554,12 +8554,13 @@ dependencies = [ [[package]] name = "solana-svm" version = "2.2.1" -source = "git+https://github.com/magicblock-labs/magicblock-svm.git?rev=3e9456ec4#3e9456ec4d5798ad8281537501c1e777d6888ba3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850baf834aba4a94a7558fa6cf6ca93fad284abf0363dec5fb9cab173a11fc4" dependencies = [ "ahash 0.8.12", + "itertools 0.12.1", "log", "percentage", - "qualifier_attr", "serde", "serde_derive", "solana-account", @@ -8584,7 +8585,6 @@ dependencies = [ "solana-pubkey", "solana-rent", "solana-rent-debits", - "solana-reserved-account-keys", "solana-sdk", "solana-sdk-ids", "solana-svm-rent-collector", From 1d1617a6ddba9bd6043b4d356ac59deec5ee77c6 Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Mon, 29 Dec 2025 10:35:33 -0500 Subject: [PATCH 02/25] feat: configuring enforce access permissions --- magicblock-api/src/magic_validator.rs | 3 +++ magicblock-config/src/config/lifecycle.rs | 6 ++++++ magicblock-processor/src/executor/mod.rs | 2 ++ magicblock-processor/src/scheduler/mod.rs | 7 ++++++- test-kit/src/lib.rs | 4 +++- 5 files changed, 20 insertions(+), 2 deletions(-) diff --git a/magicblock-api/src/magic_validator.rs b/magicblock-api/src/magic_validator.rs index 62515468e..376e26710 100644 --- a/magicblock-api/src/magic_validator.rs +++ b/magicblock-api/src/magic_validator.rs @@ -252,9 +252,12 @@ impl MagicValidator { // runtime, -1 is taken up by the transaction scheduler itself let transaction_executors = (num_cpus::get() / 2).saturating_sub(1).max(1) as u32; + let enforce_access_permissions = + config.lifecycle.enforce_access_permissions(); let transaction_scheduler = TransactionScheduler::new( transaction_executors, txn_scheduler_state, + enforce_access_permissions, ); info!("Running execution backend with {transaction_executors} threads"); let transaction_execution = transaction_scheduler.spawn(); diff --git a/magicblock-config/src/config/lifecycle.rs b/magicblock-config/src/config/lifecycle.rs index dd4359fe9..30a10d115 100644 --- a/magicblock-config/src/config/lifecycle.rs +++ b/magicblock-config/src/config/lifecycle.rs @@ -28,4 +28,10 @@ impl LifecycleMode { pub fn needs_remote_account_provider(&self) -> bool { !matches!(self, LifecycleMode::Offline) } + + /// Check whether the validator lifecycle enforces access permissions + /// which is only the case currently in [LifecycleMode::Ephemeral] mode + pub fn enforce_access_permissions(&self) -> bool { + matches!(self, LifecycleMode::Ephemeral) + } } diff --git a/magicblock-processor/src/executor/mod.rs b/magicblock-processor/src/executor/mod.rs index dbd97d7a1..92b9e3a2a 100644 --- a/magicblock-processor/src/executor/mod.rs +++ b/magicblock-processor/src/executor/mod.rs @@ -76,11 +76,13 @@ impl TransactionExecutor { rx: TransactionToProcessRx, ready_tx: Sender, programs_cache: Arc>>, + enforce_access_permissions: bool, ) -> Self { let slot = state.accountsdb.slot(); let mut processor = TransactionBatchProcessor::new_uninitialized( slot, Default::default(), + enforce_access_permissions, ); // Override the default program cache with a globally shared one. diff --git a/magicblock-processor/src/scheduler/mod.rs b/magicblock-processor/src/scheduler/mod.rs index 92fe2ea24..6230d8dc3 100644 --- a/magicblock-processor/src/scheduler/mod.rs +++ b/magicblock-processor/src/scheduler/mod.rs @@ -61,7 +61,11 @@ impl TransactionScheduler { /// 1. Prepares the shared program cache and ensures necessary sysvars are in the `AccountsDb`. /// 2. Creates a pool of `TransactionExecutor` workers, each with its own dedicated channel. /// 3. Spawns each worker in its own OS thread for maximum isolation and performance. - pub fn new(executors: u32, state: TransactionSchedulerState) -> Self { + pub fn new( + executors: u32, + state: TransactionSchedulerState, + enforce_access_permissions: bool, + ) -> Self { let count = executors.clamp(1, MAX_SVM_EXECUTORS) as usize; let mut executors = Vec::with_capacity(count); @@ -80,6 +84,7 @@ impl TransactionScheduler { transactions_rx, ready_tx.clone(), program_cache.clone(), + enforce_access_permissions, ); executor.populate_builtins(); executor.spawn(); diff --git a/test-kit/src/lib.rs b/test-kit/src/lib.rs index a7f5bc469..c9bc4f206 100644 --- a/test-kit/src/lib.rs +++ b/test-kit/src/lib.rs @@ -149,7 +149,9 @@ impl ExecutionTestEnv { .expect("failed to load test programs into test env"); // Start/Defer the transaction processing backend. - let scheduler = TransactionScheduler::new(executors, scheduler_state); + // NOTE: we run in default (Ephemeral) mode which enforces access permissions. + let scheduler = + TransactionScheduler::new(executors, scheduler_state, true); if defer_startup { this.scheduler.replace(scheduler); } else { From 3b4e1211adf44eeca4ea0904948e10f455452351 Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Mon, 29 Dec 2025 11:24:07 -0500 Subject: [PATCH 03/25] chore: iniitial lifecycle mode test --- .../test-config/tests/allowed_programs.rs | 3 +- .../test-config/tests/lifecycle_modes.rs | 154 ++++++++++++++++++ 2 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 test-integration/test-config/tests/lifecycle_modes.rs diff --git a/test-integration/test-config/tests/allowed_programs.rs b/test-integration/test-config/tests/allowed_programs.rs index 493c1c6e2..7213053a4 100644 --- a/test-integration/test-config/tests/allowed_programs.rs +++ b/test-integration/test-config/tests/allowed_programs.rs @@ -10,6 +10,7 @@ use log::*; use magicblock_config::{ config::{ accounts::AccountsDbConfig, chain::ChainLinkConfig, AllowedProgram, + LifecycleMode, }, types::network::Remote, ValidatorParams, @@ -49,7 +50,7 @@ fn run_allowed_programs(allow_committor_program: bool) { let config = ValidatorParams { programs: vec![], - lifecycle: magicblock_config::config::LifecycleMode::Ephemeral, + lifecycle: LifecycleMode::Ephemeral, remotes: vec![ Remote::from_str(IntegrationTestContext::url_chain()).unwrap(), Remote::from_str(IntegrationTestContext::ws_url_chain()).unwrap(), diff --git a/test-integration/test-config/tests/lifecycle_modes.rs b/test-integration/test-config/tests/lifecycle_modes.rs new file mode 100644 index 000000000..2a486c120 --- /dev/null +++ b/test-integration/test-config/tests/lifecycle_modes.rs @@ -0,0 +1,154 @@ +use std::str::FromStr; + +use cleanass::assert_eq; +use integration_test_tools::{ + expect, + loaded_accounts::LoadedAccounts, + validator::{cleanup, start_magicblock_validator_with_config_struct}, + IntegrationTestContext, +}; +use log::*; +use magicblock_config::{ + config::{ + accounts::AccountsDbConfig, chain::ChainLinkConfig, + ledger::LedgerConfig, LifecycleMode, + }, + types::network::Remote, + ValidatorParams, +}; +use serial_test::file_serial; +use solana_sdk::{ + native_token::LAMPORTS_PER_SOL, signature::Keypair, signer::Signer, + system_instruction, +}; +use test_kit::init_logger; + +#[test] +#[file_serial] +fn test_lifecycle_ephemeral_blocks_system_transfer_to_non_delegated() { + run_lifecycle_transfer_test(LifecycleMode::Ephemeral, false); +} + +#[test] +#[file_serial] +fn test_lifecycle_offline_allows_system_transfer_to_non_delegated() { + run_lifecycle_transfer_test(LifecycleMode::Offline, true); +} + +fn run_lifecycle_transfer_test( + lifecycle_mode: LifecycleMode, + expect_success: bool, +) { + init_logger!(); + + let config = ValidatorParams { + lifecycle: lifecycle_mode.clone(), + remotes: vec![ + Remote::from_str(IntegrationTestContext::url_chain()).unwrap(), + Remote::from_str(IntegrationTestContext::ws_url_chain()).unwrap(), + ], + chainlink: ChainLinkConfig { + auto_airdrop_lamports: 0, + ..Default::default() + }, + accountsdb: AccountsDbConfig { + reset: true, + ..Default::default() + }, + ledger: LedgerConfig { + reset: true, + ..Default::default() + }, + ..Default::default() + }; + + let (_default_tmpdir, Some(mut validator), port) = + start_magicblock_validator_with_config_struct( + config, + &LoadedAccounts::with_delegation_program_test_authority(), + ) + else { + panic!("validator should set up correctly"); + }; + + let ctx = expect!( + IntegrationTestContext::try_new_with_ephem_port(port), + validator + ); + + // Create payer accounts + let payer_chain = Keypair::new(); + let payer = Keypair::new(); + let non_delegated_recipient = Keypair::new(); + + // Setup payer based on lifecycle mode + match lifecycle_mode { + LifecycleMode::Ephemeral => { + expect!( + ctx.airdrop_chain(&payer_chain.pubkey(), LAMPORTS_PER_SOL), + validator + ); + expect!( + ctx.airdrop_chain_and_delegate( + &payer_chain, + &payer, + LAMPORTS_PER_SOL, + ), + validator + ); + debug!( + "✅ Airdropped 1 SOL to payer account via chain: {}", + payer.pubkey() + ); + } + LifecycleMode::Offline => { + // For offline mode, airdrop directly to the validator without + // using chain + expect!( + ctx.airdrop_ephem(&payer.pubkey(), LAMPORTS_PER_SOL), + validator + ); + debug!( + "✅ Airdropped 1 SOL to payer account directly in offline \ + validator: {}", + payer.pubkey() + ); + } + _ => { + panic!( + "Test only supports Ephemeral and Offline lifecycle modes, \ + got {:?}", + lifecycle_mode + ); + } + } + + // 4. Send a transfer to a non-delegated account that doesn't yet exist + // This tests the lifecycle mode behavior + let ix = system_instruction::transfer( + &payer.pubkey(), + &non_delegated_recipient.pubkey(), + 1000, + ); + + let (sig, confirmed) = expect!( + ctx.send_and_confirm_instructions_with_payer_ephem(&[ix], &payer), + validator + ); + + debug!( + "Transfer to non-delegated {sig} recipient result confirmed: {}", + confirmed + ); + + assert_eq!( + confirmed, + expect_success, + cleanup(&mut validator), + "Transfer to non-delegated account expected success: {}, got: {}", + expect_success, + confirmed + ); + + cleanup(&mut validator); +} From 8f20d36f06f98c08e5d4438b18c481edf0fb5dc5 Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Mon, 29 Dec 2025 16:20:18 -0500 Subject: [PATCH 04/25] fix: forwarding lifecycle mode on chainlink init --- magicblock-api/src/magic_validator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/magicblock-api/src/magic_validator.rs b/magicblock-api/src/magic_validator.rs index 376e26710..ca2ddda5a 100644 --- a/magicblock-api/src/magic_validator.rs +++ b/magicblock-api/src/magic_validator.rs @@ -393,7 +393,7 @@ impl MagicValidator { let cloner = Arc::new(cloner); let accounts_bank = accountsdb.clone(); let mut chainlink_config = ChainlinkConfig::default_with_lifecycle_mode( - LifecycleMode::Ephemeral, + config.lifecycle.clone(), ); chainlink_config.remove_confined_accounts = config.chainlink.remove_confined_accounts; From 95de7d9b39f94ba60e87215284cc1886067addc2 Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Mon, 29 Dec 2025 17:47:18 -0500 Subject: [PATCH 05/25] feat: disable account state verification when not enforcing access permissions --- magicblock-processor/src/executor/mod.rs | 3 +++ magicblock-processor/src/executor/processing.rs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/magicblock-processor/src/executor/mod.rs b/magicblock-processor/src/executor/mod.rs index 92b9e3a2a..4ccbc7a84 100644 --- a/magicblock-processor/src/executor/mod.rs +++ b/magicblock-processor/src/executor/mod.rs @@ -62,6 +62,8 @@ pub(super) struct TransactionExecutor { /// is tightly contolled and will be removed in the nearest future /// True when auto airdrop for fee payers is enabled (auto_airdrop_lamports > 0). is_auto_airdrop_lamports_enabled: bool, + /// Whether to enforce access permissions during transaction execution. + is_enforcing_access_permissions: bool, } impl TransactionExecutor { @@ -113,6 +115,7 @@ impl TransactionExecutor { tasks_tx: state.tasks_tx.clone(), is_auto_airdrop_lamports_enabled: state .is_auto_airdrop_lamports_enabled, + is_enforcing_access_permissions: enforce_access_permissions, }; this.processor.fill_missing_sysvar_cache_entries(&this); diff --git a/magicblock-processor/src/executor/processing.rs b/magicblock-processor/src/executor/processing.rs index c0c8afcfe..5270aa182 100644 --- a/magicblock-processor/src/executor/processing.rs +++ b/magicblock-processor/src/executor/processing.rs @@ -343,6 +343,9 @@ impl super::TransactionExecutor { /// 1. No modification of the non-delegated feepayer in gasless mode /// 2. No lamport modification of confined accounts fn verify_account_states(&self, processed: &mut ProcessedTransaction) { + if !self.is_enforcing_access_permissions { + return; + } let ProcessedTransaction::Executed(executed) = processed else { return; }; From 7f9b0332270f0520a15a0300fed4d7b043f602e7 Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Mon, 29 Dec 2025 18:00:47 -0500 Subject: [PATCH 06/25] chore: dump logs on airdrop failure in integration tests --- .../src/integration_test_context.rs | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/test-integration/test-tools/src/integration_test_context.rs b/test-integration/test-tools/src/integration_test_context.rs index b0f51c17e..bb39fd5f0 100644 --- a/test-integration/test-tools/src/integration_test_context.rs +++ b/test-integration/test-tools/src/integration_test_context.rs @@ -497,11 +497,12 @@ impl IntegrationTestContext { pubkey: &Pubkey, lamports: u64, ) -> anyhow::Result { - Self::airdrop( + Self::airdrop_impl( self.try_chain_client()?, pubkey, lamports, self.commitment, + Some(&(|sig| self.dump_chain_logs(sig))), ) } @@ -511,7 +512,13 @@ impl IntegrationTestContext { lamports: u64, ) -> anyhow::Result { self.try_ephem_client().and_then(|ephem_client| { - Self::airdrop(ephem_client, pubkey, lamports, self.commitment) + Self::airdrop_impl( + ephem_client, + pubkey, + lamports, + self.commitment, + Some(&(|sig| self.dump_chain_logs(sig))), + ) }) } /// Airdrop lamports to the payer on-chain account and @@ -636,6 +643,22 @@ impl IntegrationTestContext { pubkey: &Pubkey, lamports: u64, commitment_config: CommitmentConfig, + ) -> anyhow::Result { + Self::airdrop_impl( + rpc_client, + pubkey, + lamports, + commitment_config, + None, + ) + } + + fn airdrop_impl( + rpc_client: &RpcClient, + pubkey: &Pubkey, + lamports: u64, + commitment_config: CommitmentConfig, + on_error: Option<&dyn Fn(Signature)>, ) -> anyhow::Result { let sig = rpc_client.request_airdrop(pubkey, lamports).with_context( || format!("Failed to airdrop chain account '{:?}'", pubkey), @@ -650,6 +673,9 @@ impl IntegrationTestContext { ) })?; if !succeeded { + if let Some(on_error) = on_error { + on_error(sig); + } return Err(anyhow::anyhow!( "Failed to airdrop chain account '{:?}'", pubkey From dac2d4a206524150797c62c8f150db04f0317cb3 Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Mon, 29 Dec 2025 18:04:59 -0500 Subject: [PATCH 07/25] chore: remove obsolete method --- magicblock-accounts/src/config.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/magicblock-accounts/src/config.rs b/magicblock-accounts/src/config.rs index 70e3995d2..55d9c5bb6 100644 --- a/magicblock-accounts/src/config.rs +++ b/magicblock-accounts/src/config.rs @@ -5,14 +5,3 @@ pub enum LifecycleMode { Ephemeral, Offline, } - -impl LifecycleMode { - pub fn requires_ephemeral_validation(&self) -> bool { - match self { - LifecycleMode::Replica => false, - LifecycleMode::ProgramsReplica => false, - LifecycleMode::Ephemeral => true, - LifecycleMode::Offline => false, - } - } -} From 3345c747f747c721af64e6ad6b8e8e107ecfacfd Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Mon, 29 Dec 2025 18:05:17 -0500 Subject: [PATCH 08/25] chore: passing test verifying we can write to any account in non-ephemeral mode --- .../test-config/tests/lifecycle_modes.rs | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/test-integration/test-config/tests/lifecycle_modes.rs b/test-integration/test-config/tests/lifecycle_modes.rs index 2a486c120..973d4298f 100644 --- a/test-integration/test-config/tests/lifecycle_modes.rs +++ b/test-integration/test-config/tests/lifecycle_modes.rs @@ -35,6 +35,18 @@ fn test_lifecycle_offline_allows_system_transfer_to_non_delegated() { run_lifecycle_transfer_test(LifecycleMode::Offline, true); } +#[test] +#[file_serial] +fn test_lifecycle_replica_allows_system_transfer_to_non_delegated() { + run_lifecycle_transfer_test(LifecycleMode::Replica, true); +} + +#[test] +#[file_serial] +fn test_lifecycle_programs_replica_allows_system_transfer_to_non_delegated() { + run_lifecycle_transfer_test(LifecycleMode::ProgramsReplica, true); +} + fn run_lifecycle_transfer_test( lifecycle_mode: LifecycleMode, expect_success: bool, @@ -101,9 +113,8 @@ fn run_lifecycle_transfer_test( payer.pubkey() ); } - LifecycleMode::Offline => { - // For offline mode, airdrop directly to the validator without - // using chain + // All other modes support direct airdrop + _ => { expect!( ctx.airdrop_ephem(&payer.pubkey(), LAMPORTS_PER_SOL), validator @@ -114,13 +125,6 @@ fn run_lifecycle_transfer_test( payer.pubkey() ); } - _ => { - panic!( - "Test only supports Ephemeral and Offline lifecycle modes, \ - got {:?}", - lifecycle_mode - ); - } } // 4. Send a transfer to a non-delegated account that doesn't yet exist @@ -128,7 +132,7 @@ fn run_lifecycle_transfer_test( let ix = system_instruction::transfer( &payer.pubkey(), &non_delegated_recipient.pubkey(), - 1000, + 1_000_000, ); let (sig, confirmed) = expect!( From 822f355f2acab928522ff41b98b63b7ad2c4e31c Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Mon, 29 Dec 2025 18:08:02 -0500 Subject: [PATCH 09/25] chore: run all config tests in series for more stability --- test-integration/test-config/tests/auto_airdrop_feepayer.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test-integration/test-config/tests/auto_airdrop_feepayer.rs b/test-integration/test-config/tests/auto_airdrop_feepayer.rs index 7971ae4e5..54c46240f 100644 --- a/test-integration/test-config/tests/auto_airdrop_feepayer.rs +++ b/test-integration/test-config/tests/auto_airdrop_feepayer.rs @@ -15,10 +15,12 @@ use magicblock_config::{ types::network::Remote, ValidatorParams, }; +use serial_test::file_serial; use solana_sdk::{signature::Keypair, signer::Signer, system_instruction}; use test_kit::init_logger; #[test] +#[file_serial] fn test_auto_airdrop_feepayer_balance_after_tx() { init_logger!(); From 94cf735d733fdd5b64650f81de3a346d52eb1837 Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Mon, 29 Dec 2025 18:11:38 -0500 Subject: [PATCH 10/25] chore: better name for test --- .../tests/{lifecycle_modes.rs => lifecycle_modes_access.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test-integration/test-config/tests/{lifecycle_modes.rs => lifecycle_modes_access.rs} (100%) diff --git a/test-integration/test-config/tests/lifecycle_modes.rs b/test-integration/test-config/tests/lifecycle_modes_access.rs similarity index 100% rename from test-integration/test-config/tests/lifecycle_modes.rs rename to test-integration/test-config/tests/lifecycle_modes_access.rs From c0d5cb2c896ae81594079988a99153fc52c5e365 Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Mon, 2 Feb 2026 17:01:24 +0800 Subject: [PATCH 11/25] chore: update cargo lock --- test-integration/Cargo.lock | 124 +++++++++++------------------------- 1 file changed, 36 insertions(+), 88 deletions(-) diff --git a/test-integration/Cargo.lock b/test-integration/Cargo.lock index bde7d704c..b73e95d1e 100644 --- a/test-integration/Cargo.lock +++ b/test-integration/Cargo.lock @@ -1700,7 +1700,7 @@ dependencies = [ "ephemeral-rollups-sdk-attribute-ephemeral", "getrandom 0.2.16", "magicblock-delegation-program 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "magicblock-magic-program-api 0.6.1 (git+https://github.com/magicblock-labs/magicblock-validator.git?rev=4d8cded9d)", + "magicblock-magic-program-api 0.6.1", "solana-account", "solana-account-info", "solana-cpi", @@ -2241,10 +2241,10 @@ dependencies = [ [[package]] name = "guinea" -version = "0.6.1" +version = "0.6.2" dependencies = [ "bincode", - "magicblock-magic-program-api 0.6.1", + "magicblock-magic-program-api 0.6.2", "serde", "solana-program", ] @@ -3257,7 +3257,7 @@ dependencies = [ [[package]] name = "magicblock-account-cloner" -version = "0.6.1" +version = "0.6.2" dependencies = [ "async-trait", "bincode", @@ -3267,7 +3267,7 @@ dependencies = [ "magicblock-config", "magicblock-core", "magicblock-ledger", - "magicblock-magic-program-api 0.6.1", + "magicblock-magic-program-api 0.6.2", "magicblock-program", "magicblock-rpc-client", "rand 0.9.2", @@ -3289,7 +3289,7 @@ dependencies = [ [[package]] name = "magicblock-accounts" -version = "0.6.1" +version = "0.6.2" dependencies = [ "async-trait", "magicblock-account-cloner", @@ -3311,7 +3311,7 @@ dependencies = [ [[package]] name = "magicblock-accounts-db" -version = "0.6.1" +version = "0.6.2" dependencies = [ "lmdb-rkv", "magicblock-config", @@ -3326,7 +3326,7 @@ dependencies = [ [[package]] name = "magicblock-aperture" -version = "0.6.1" +version = "0.6.2" dependencies = [ "agave-geyser-plugin-interface", "arc-swap", @@ -3374,7 +3374,7 @@ dependencies = [ [[package]] name = "magicblock-api" -version = "0.6.1" +version = "0.6.2" dependencies = [ "anyhow", "borsh 1.6.0", @@ -3389,7 +3389,7 @@ dependencies = [ "magicblock-config", "magicblock-core", "magicblock-ledger", - "magicblock-magic-program-api 0.6.1", + "magicblock-magic-program-api 0.6.2", "magicblock-metrics", "magicblock-processor", "magicblock-program", @@ -3429,7 +3429,7 @@ dependencies = [ [[package]] name = "magicblock-chainlink" -version = "0.6.1" +version = "0.6.2" dependencies = [ "arc-swap", "async-trait", @@ -3441,7 +3441,7 @@ dependencies = [ "magicblock-config", "magicblock-core", "magicblock-delegation-program 1.1.3 (git+https://github.com/magicblock-labs/delegation-program.git?rev=1874b4f5f5f55cb9ab54b64de2cc0d41107d1435)", - "magicblock-magic-program-api 0.6.1", + "magicblock-magic-program-api 0.6.2", "magicblock-metrics", "parking_lot", "scc", @@ -3483,7 +3483,7 @@ dependencies = [ [[package]] name = "magicblock-committor-program" -version = "0.6.1" +version = "0.6.2" dependencies = [ "borsh 1.6.0", "paste", @@ -3495,7 +3495,7 @@ dependencies = [ [[package]] name = "magicblock-committor-service" -version = "0.6.1" +version = "0.6.2" dependencies = [ "async-trait", "base64 0.21.7", @@ -3539,7 +3539,7 @@ dependencies = [ [[package]] name = "magicblock-config" -version = "0.6.1" +version = "0.6.2" dependencies = [ "clap", "derive_more", @@ -3557,10 +3557,10 @@ dependencies = [ [[package]] name = "magicblock-core" -version = "0.6.1" +version = "0.6.2" dependencies = [ "flume", - "magicblock-magic-program-api 0.6.1", + "magicblock-magic-program-api 0.6.2", "solana-account", "solana-account-decoder", "solana-hash", @@ -3616,7 +3616,7 @@ dependencies = [ [[package]] name = "magicblock-ledger" -version = "0.6.1" +version = "0.6.2" dependencies = [ "arc-swap", "bincode", @@ -3657,6 +3657,7 @@ dependencies = [ [[package]] name = "magicblock-magic-program-api" version = "0.6.1" +source = "git+https://github.com/magicblock-labs/magicblock-validator.git?rev=4d8cded9d#4d8cded9d77772baf05462741dee59421e2e413a" dependencies = [ "bincode", "serde", @@ -3665,8 +3666,7 @@ dependencies = [ [[package]] name = "magicblock-magic-program-api" -version = "0.6.1" -source = "git+https://github.com/magicblock-labs/magicblock-validator.git?rev=4d8cded9d#4d8cded9d77772baf05462741dee59421e2e413a" +version = "0.6.2" dependencies = [ "bincode", "serde", @@ -3675,7 +3675,7 @@ dependencies = [ [[package]] name = "magicblock-metrics" -version = "0.6.1" +version = "0.6.2" dependencies = [ "http-body-util", "hyper 1.8.1", @@ -3689,7 +3689,7 @@ dependencies = [ [[package]] name = "magicblock-processor" -version = "0.6.1" +version = "0.6.2" dependencies = [ "bincode", "magicblock-accounts-db", @@ -3719,22 +3719,18 @@ dependencies = [ "solana-transaction-error", "solana-transaction-status", "tokio", -<<<<<<< HEAD - "tokio-util 0.7.15", -======= "tokio-util 0.7.17", "tracing", ->>>>>>> master ] [[package]] name = "magicblock-program" -version = "0.6.1" +version = "0.6.2" dependencies = [ "bincode", "lazy_static", "magicblock-core", - "magicblock-magic-program-api 0.6.1", + "magicblock-magic-program-api 0.6.2", "num-derive", "num-traits", "parking_lot", @@ -3761,7 +3757,7 @@ dependencies = [ [[package]] name = "magicblock-rpc-client" -version = "0.6.1" +version = "0.6.2" dependencies = [ "solana-account", "solana-account-decoder-client-types", @@ -3783,7 +3779,7 @@ dependencies = [ [[package]] name = "magicblock-table-mania" -version = "0.6.1" +version = "0.6.2" dependencies = [ "ed25519-dalek", "magicblock-metrics", @@ -3809,7 +3805,7 @@ dependencies = [ [[package]] name = "magicblock-task-scheduler" -version = "0.6.1" +version = "0.6.2" dependencies = [ "bincode", "chrono", @@ -3835,7 +3831,7 @@ dependencies = [ [[package]] name = "magicblock-validator-admin" -version = "0.6.1" +version = "0.6.2" dependencies = [ "magicblock-delegation-program 1.1.3 (git+https://github.com/magicblock-labs/delegation-program.git?rev=1874b4f5f5f55cb9ab54b64de2cc0d41107d1435)", "magicblock-program", @@ -3852,7 +3848,7 @@ dependencies = [ [[package]] name = "magicblock-version" -version = "0.6.1" +version = "0.6.2" dependencies = [ "git-version", "rustc_version", @@ -4752,7 +4748,7 @@ dependencies = [ "bincode", "borsh 1.6.0", "ephemeral-rollups-sdk", - "magicblock-magic-program-api 0.6.1", + "magicblock-magic-program-api 0.6.2", "serde", "solana-program", ] @@ -4776,7 +4772,7 @@ dependencies = [ "borsh 1.6.0", "ephemeral-rollups-sdk", "magicblock-delegation-program 1.1.3 (git+https://github.com/magicblock-labs/delegation-program.git?rev=1874b4f5f5f55cb9ab54b64de2cc0d41107d1435)", - "magicblock-magic-program-api 0.6.1", + "magicblock-magic-program-api 0.6.2", "rkyv 0.7.45", "solana-program", "static_assertions", @@ -5810,7 +5806,7 @@ dependencies = [ "ephemeral-rollups-sdk", "integration-test-tools", "magicblock-core", - "magicblock-magic-program-api 0.6.1", + "magicblock-magic-program-api 0.6.2", "program-schedulecommit", "rand 0.8.5", "schedulecommit-client", @@ -5828,7 +5824,7 @@ version = "0.0.0" dependencies = [ "integration-test-tools", "magicblock-core", - "magicblock-magic-program-api 0.6.1", + "magicblock-magic-program-api 0.6.2", "program-schedulecommit", "program-schedulecommit-security", "schedulecommit-client", @@ -8547,7 +8543,7 @@ dependencies = [ [[package]] name = "solana-storage-proto" -version = "0.6.1" +version = "0.6.2" dependencies = [ "bincode", "bs58", @@ -8655,7 +8651,7 @@ dependencies = [ "solana-transaction-context", "solana-transaction-error", "solana-type-overrides", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -8704,54 +8700,6 @@ dependencies = [ ] [[package]] -<<<<<<< HEAD -======= -name = "solana-svm" -version = "2.2.1" -source = "git+https://github.com/magicblock-labs/magicblock-svm.git?rev=3e9456ec4#3e9456ec4d5798ad8281537501c1e777d6888ba3" -dependencies = [ - "ahash 0.8.12", - "log", - "percentage", - "qualifier_attr", - "serde", - "serde_derive", - "solana-account", - "solana-bpf-loader-program", - "solana-clock", - "solana-compute-budget", - "solana-compute-budget-instruction", - "solana-feature-set", - "solana-fee-structure", - "solana-hash", - "solana-instruction", - "solana-instructions-sysvar", - "solana-loader-v4-program", - "solana-log-collector", - "solana-measure", - "solana-message", - "solana-nonce", - "solana-nonce-account", - "solana-precompiles", - "solana-program", - "solana-program-runtime", - "solana-pubkey", - "solana-rent", - "solana-rent-debits", - "solana-reserved-account-keys", - "solana-sdk", - "solana-sdk-ids", - "solana-svm-rent-collector", - "solana-svm-transaction", - "solana-timings", - "solana-transaction-context", - "solana-transaction-error", - "solana-type-overrides", - "thiserror 2.0.17", -] - -[[package]] ->>>>>>> master name = "solana-svm-rent-collector" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -10095,7 +10043,7 @@ dependencies = [ [[package]] name = "test-kit" -version = "0.6.1" +version = "0.6.2" dependencies = [ "guinea", "magicblock-accounts-db", From 248df6ecd3f95f3cf8a376b20a8f7bf6e18a9574 Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Mon, 2 Feb 2026 17:01:41 +0800 Subject: [PATCH 12/25] chore: use tracing log --- test-integration/test-config/tests/lifecycle_modes_access.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-integration/test-config/tests/lifecycle_modes_access.rs b/test-integration/test-config/tests/lifecycle_modes_access.rs index 973d4298f..7c35de5a2 100644 --- a/test-integration/test-config/tests/lifecycle_modes_access.rs +++ b/test-integration/test-config/tests/lifecycle_modes_access.rs @@ -7,7 +7,7 @@ use integration_test_tools::{ validator::{cleanup, start_magicblock_validator_with_config_struct}, IntegrationTestContext, }; -use log::*; +use tracing::*; use magicblock_config::{ config::{ accounts::AccountsDbConfig, chain::ChainLinkConfig, From 0006b161bdf80abef117d58688cd106e673dd38b Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Tue, 3 Feb 2026 14:47:51 +0800 Subject: [PATCH 13/25] test: add lifecycle modes cloning tests Amp-Thread-ID: https://ampcode.com/threads/T-019c2241-6f4f-71b6-aae5-e16e069ae4fc Co-authored-by: Amp --- .../tests/lifecycle_modes_cloning.rs | 194 ++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 test-integration/test-config/tests/lifecycle_modes_cloning.rs diff --git a/test-integration/test-config/tests/lifecycle_modes_cloning.rs b/test-integration/test-config/tests/lifecycle_modes_cloning.rs new file mode 100644 index 000000000..cbcb7e923 --- /dev/null +++ b/test-integration/test-config/tests/lifecycle_modes_cloning.rs @@ -0,0 +1,194 @@ +use std::str::FromStr; + +use cleanass::assert_eq; +use integration_test_tools::{ + expect, + loaded_accounts::LoadedAccounts, + validator::{cleanup, start_magicblock_validator_with_config_struct}, + IntegrationTestContext, +}; +use magicblock_config::{ + config::{ + accounts::AccountsDbConfig, chain::ChainLinkConfig, + ledger::LedgerConfig, LifecycleMode, + }, + types::network::Remote, + ValidatorParams, +}; +use serial_test::file_serial; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::{ + native_token::LAMPORTS_PER_SOL, signature::Keypair, signer::Signer, +}; +use test_kit::init_logger; +use tracing::*; + +fn random_pubkey() -> Pubkey { + Keypair::new().pubkey() +} + +// Ephemeral mode: all accounts should be cloned +#[test] +#[file_serial] +fn test_lifecycle_ephemeral_clones_non_program_account() { + run_lifecycle_cloning_test( + LifecycleMode::Ephemeral, + random_pubkey(), + true, + true, + ); +} + +#[test] +#[file_serial] +fn test_lifecycle_ephemeral_clones_program_account() { + run_lifecycle_cloning_test( + LifecycleMode::Ephemeral, + program_flexi_counter::id(), + false, + true, + ); +} + +// Offline mode: no accounts should be cloned +#[test] +#[file_serial] +fn test_lifecycle_offline_does_not_clone_non_program_account() { + run_lifecycle_cloning_test( + LifecycleMode::Offline, + random_pubkey(), + true, + false, + ); +} + +#[test] +#[file_serial] +fn test_lifecycle_offline_does_not_clone_program_account() { + run_lifecycle_cloning_test( + LifecycleMode::Offline, + program_flexi_counter::id(), + false, + false, + ); +} + +// Replica mode: all accounts should be cloned +#[test] +#[file_serial] +fn test_lifecycle_replica_clones_non_program_account() { + run_lifecycle_cloning_test( + LifecycleMode::Replica, + random_pubkey(), + true, + true, + ); +} + +#[test] +#[file_serial] +fn test_lifecycle_replica_clones_program_account() { + run_lifecycle_cloning_test( + LifecycleMode::Replica, + program_flexi_counter::id(), + true, + true, + ); +} + +// Programs replica mode: only program accounts should be cloned +#[test] +#[file_serial] +fn test_lifecycle_programs_replica_does_not_clone_non_program_account() { + run_lifecycle_cloning_test( + LifecycleMode::ProgramsReplica, + random_pubkey(), + true, + false, + ); +} + +#[test] +#[file_serial] +fn test_lifecycle_programs_replica_clones_program_account() { + run_lifecycle_cloning_test( + LifecycleMode::ProgramsReplica, + program_flexi_counter::id(), + true, + true, + ); +} + +fn run_lifecycle_cloning_test( + lifecycle_mode: LifecycleMode, + pubkey: Pubkey, + airdrop: bool, + expect_clone: bool, +) { + init_logger!(); + + let config = ValidatorParams { + lifecycle: lifecycle_mode.clone(), + remotes: vec![ + Remote::from_str(IntegrationTestContext::url_chain()).unwrap(), + Remote::from_str(IntegrationTestContext::ws_url_chain()).unwrap(), + ], + chainlink: ChainLinkConfig { + auto_airdrop_lamports: 0, + ..Default::default() + }, + accountsdb: AccountsDbConfig { + reset: true, + ..Default::default() + }, + ledger: LedgerConfig { + reset: true, + ..Default::default() + }, + ..Default::default() + }; + + let (_default_tmpdir, Some(mut validator), port) = + start_magicblock_validator_with_config_struct( + config, + &LoadedAccounts::with_delegation_program_test_authority(), + ) + else { + panic!("validator should set up correctly"); + }; + + let ctx = expect!( + IntegrationTestContext::try_new_with_ephem_port(port), + validator + ); + + if airdrop { + // Airdrop to test account on chain + expect!(ctx.airdrop_chain(&pubkey, LAMPORTS_PER_SOL), validator); + debug!("✅ Airdropped 1 SOL to test account on chain: {}", pubkey); + } + + // Attempt to fetch the account from ephemeral validator + std::thread::sleep(std::time::Duration::from_millis(500)); + let cloned_account = ctx.fetch_ephem_account(pubkey); + + if expect_clone { + assert_eq!( + cloned_account.is_ok(), + true, + cleanup(&mut validator), + "Account should have been cloned in {:?} mode for", + lifecycle_mode, + ); + } else { + assert_eq!( + cloned_account.is_err(), + true, + cleanup(&mut validator), + "Account should NOT have been cloned in {:?} mode", + lifecycle_mode, + ); + } + + cleanup(&mut validator); +} From 1dc6a8a559f6e6ab9c9577a2b491ac0c3bdba074 Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Tue, 3 Feb 2026 15:21:31 +0800 Subject: [PATCH 14/25] refactor: convert clone_accounts_and_programs to instance method Amp-Thread-ID: https://ampcode.com/threads/T-019c2260-634f-714d-8c42-1de9f9cb810f Co-authored-by: Amp --- .../src/chainlink/fetch_cloner/mod.rs | 8 +- .../src/chainlink/fetch_cloner/pipeline.rs | 78 ++++++++++--------- .../src/remote_account_provider/mod.rs | 2 + 3 files changed, 47 insertions(+), 41 deletions(-) diff --git a/magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs b/magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs index f20c45015..9d6e6c0b9 100644 --- a/magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs +++ b/magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs @@ -795,12 +795,8 @@ where cancel_subs(&self.remote_account_provider, cancel_strategy).await; - pipeline::clone_accounts_and_programs( - self, - accounts_to_clone, - loaded_programs, - ) - .await?; + self.clone_accounts_and_programs(accounts_to_clone, loaded_programs) + .await?; Ok(FetchAndCloneResult { not_found_on_chain: not_found, diff --git a/magicblock-chainlink/src/chainlink/fetch_cloner/pipeline.rs b/magicblock-chainlink/src/chainlink/fetch_cloner/pipeline.rs index 2fd9c59ba..74d3f2080 100644 --- a/magicblock-chainlink/src/chainlink/fetch_cloner/pipeline.rs +++ b/magicblock-chainlink/src/chainlink/fetch_cloner/pipeline.rs @@ -638,50 +638,58 @@ pub(crate) fn compute_cancel_strategy( } } -/// Clones accounts and programs into the bank -#[instrument(skip(this, accounts_to_clone, loaded_programs))] -pub(crate) async fn clone_accounts_and_programs( - this: &FetchCloner, - accounts_to_clone: Vec, - loaded_programs: Vec< - crate::remote_account_provider::program_account::LoadedProgram, - >, -) -> ClonerResult<()> +impl FetchCloner where T: ChainRpcClient, U: ChainPubsubClient, V: AccountsBank, C: Cloner, { - let mut join_set = JoinSet::new(); - for request in accounts_to_clone { - if tracing::enabled!(tracing::Level::TRACE) { - trace!( - pubkey = %request.pubkey, - slot = request.account.remote_slot(), - owner = %request.account.owner(), - "Cloning account" - ); - }; + /// Clones accounts and programs into the bank + #[instrument(skip(self, accounts_to_clone, loaded_programs))] + pub(crate) async fn clone_accounts_and_programs( + &self, + accounts_to_clone: Vec, + loaded_programs: Vec< + crate::remote_account_provider::program_account::LoadedProgram, + >, + ) -> ClonerResult<()> + where + T: ChainRpcClient, + U: ChainPubsubClient, + V: AccountsBank, + C: Cloner, + { + let mut join_set = JoinSet::new(); + for request in accounts_to_clone { + if tracing::enabled!(tracing::Level::TRACE) { + trace!( + pubkey = %request.pubkey, + slot = request.account.remote_slot(), + owner = %request.account.owner(), + "Cloning account" + ); + }; - let cloner = this.cloner.clone(); - join_set.spawn(async move { cloner.clone_account(request).await }); - } + let cloner = self.cloner.clone(); + join_set.spawn(async move { cloner.clone_account(request).await }); + } - for acc in loaded_programs { - if !this.is_program_allowed(&acc.program_id) { - debug!(program_id = %acc.program_id, "Skipping clone of program"); - continue; + for acc in loaded_programs { + if !self.is_program_allowed(&acc.program_id) { + debug!(program_id = %acc.program_id, "Skipping clone of program"); + continue; + } + let cloner = self.cloner.clone(); + join_set.spawn(async move { cloner.clone_program(acc).await }); } - let cloner = this.cloner.clone(); - join_set.spawn(async move { cloner.clone_program(acc).await }); - } - join_set - .join_all() - .await - .into_iter() - .collect::>>()?; + join_set + .join_all() + .await + .into_iter() + .collect::>>()?; - Ok(()) + Ok(()) + } } diff --git a/magicblock-chainlink/src/remote_account_provider/mod.rs b/magicblock-chainlink/src/remote_account_provider/mod.rs index 38c62ae22..2d8657eaf 100644 --- a/magicblock-chainlink/src/remote_account_provider/mod.rs +++ b/magicblock-chainlink/src/remote_account_provider/mod.rs @@ -164,6 +164,8 @@ impl Default for MatchSlotsConfig { impl RemoteAccountProvider> { + /// Creates a RemoteAccountProvider from the given endpoints and config if needed. + /// NOTE: for offline [RemoteAccountProviderConfig::lifecycle_mode] we return None pub async fn try_from_urls_and_config( endpoints: &Endpoints, commitment: CommitmentConfig, From acd9b8962b72ee06dc133bbe84beb78a28ee7ef0 Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Tue, 3 Feb 2026 16:21:31 +0800 Subject: [PATCH 15/25] chore: provide lifecycle mode down to fetch/clone decision point --- magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs | 8 +++++++- magicblock-chainlink/src/chainlink/fetch_cloner/tests.rs | 5 +++++ magicblock-chainlink/src/chainlink/mod.rs | 3 +++ magicblock-chainlink/tests/utils/test_context.rs | 3 ++- magicblock-config/src/config/lifecycle.rs | 6 ++++++ test-integration/test-chainlink/src/ixtest_context.rs | 1 + test-integration/test-chainlink/src/test_context.rs | 1 + 7 files changed, 25 insertions(+), 2 deletions(-) diff --git a/magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs b/magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs index 9d6e6c0b9..f12b6687b 100644 --- a/magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs +++ b/magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs @@ -11,7 +11,7 @@ use dlp::{ pda::delegation_record_pda_from_delegated_account, state::DelegationRecord, }; use magicblock_accounts_db::traits::AccountsBank; -use magicblock_config::config::AllowedProgram; +use magicblock_config::config::{AllowedProgram, LifecycleMode}; use magicblock_core::token_programs::{ is_ata, try_derive_eata_address_and_bump, MaybeIntoAta, }; @@ -87,6 +87,9 @@ where /// If specified, only these programs will be cloned. If None or empty, /// all programs are allowed. allowed_programs: Option>, + + /// The lifecycle mode of the validator, used to determine cloning behavior + lifecycle_mode: LifecycleMode, } impl FetchCloner @@ -97,6 +100,7 @@ where C: Cloner, { /// Create FetchCloner with subscription updates properly connected + #[allow(clippy::too_many_arguments)] pub fn new( remote_account_provider: &Arc>, accounts_bank: &Arc, @@ -105,6 +109,7 @@ where faucet_pubkey: Pubkey, subscription_updates_rx: mpsc::Receiver, allowed_programs: Option>, + lifecycle_mode: LifecycleMode, ) -> Arc { let blacklisted_accounts = blacklisted_accounts(&validator_pubkey, &faucet_pubkey); @@ -120,6 +125,7 @@ where fetch_count: Arc::new(AtomicU64::new(0)), blacklisted_accounts, allowed_programs, + lifecycle_mode, }); me.clone() diff --git a/magicblock-chainlink/src/chainlink/fetch_cloner/tests.rs b/magicblock-chainlink/src/chainlink/fetch_cloner/tests.rs index 588f24210..0dedcca9f 100644 --- a/magicblock-chainlink/src/chainlink/fetch_cloner/tests.rs +++ b/magicblock-chainlink/src/chainlink/fetch_cloner/tests.rs @@ -1,5 +1,6 @@ use std::{collections::HashMap, sync::Arc}; +use magicblock_config::config::LifecycleMode; use solana_account::{Account, AccountSharedData, WritableAccount}; use solana_sdk_ids::system_program; use tokio::sync::mpsc; @@ -184,6 +185,7 @@ fn init_fetch_cloner( faucet_pubkey, subscription_rx, None, + LifecycleMode::Ephemeral, ); (fetch_cloner, subscription_tx) } @@ -1499,6 +1501,7 @@ async fn test_allowed_programs_filters_programs() { random_pubkey(), subscription_rx, allowed_programs, + LifecycleMode::Ephemeral, ); // Fetch and clone both programs @@ -1567,6 +1570,7 @@ async fn test_allowed_programs_none_allows_all() { random_pubkey(), subscription_rx, None, // No restriction + LifecycleMode::Ephemeral, ); // Fetch and clone both programs @@ -1634,6 +1638,7 @@ async fn test_allowed_programs_empty_allows_all() { random_pubkey(), subscription_rx, allowed_programs, + LifecycleMode::Ephemeral, ); // Fetch and clone both programs diff --git a/magicblock-chainlink/src/chainlink/mod.rs b/magicblock-chainlink/src/chainlink/mod.rs index e1d453e45..1e0fcce2b 100644 --- a/magicblock-chainlink/src/chainlink/mod.rs +++ b/magicblock-chainlink/src/chainlink/mod.rs @@ -131,6 +131,8 @@ impl .await?; let fetch_cloner = if let Some(provider) = account_provider { let provider = Arc::new(provider); + let lifecycle_mode = + config.remote_account_provider.lifecycle_mode().clone(); let fetch_cloner = FetchCloner::new( &provider, accounts_bank, @@ -139,6 +141,7 @@ impl faucet_pubkey, rx, chainlink_config.allowed_programs.clone(), + lifecycle_mode, ); Some(fetch_cloner) } else { diff --git a/magicblock-chainlink/tests/utils/test_context.rs b/magicblock-chainlink/tests/utils/test_context.rs index 206d9efe9..775eaac15 100644 --- a/magicblock-chainlink/tests/utils/test_context.rs +++ b/magicblock-chainlink/tests/utils/test_context.rs @@ -70,7 +70,7 @@ impl TestContext { let (tx, rx) = tokio::sync::mpsc::channel(100); let config = RemoteAccountProviderConfig::default_with_lifecycle_mode( - lifecycle_mode, + lifecycle_mode.clone(), ); let subscribed_accounts = create_test_lru_cache_with_config(&config); @@ -99,6 +99,7 @@ impl TestContext { faucet_pubkey, rx, None, + lifecycle_mode.clone(), )), Some(provider), ) diff --git a/magicblock-config/src/config/lifecycle.rs b/magicblock-config/src/config/lifecycle.rs index 30a10d115..b7ea8b915 100644 --- a/magicblock-config/src/config/lifecycle.rs +++ b/magicblock-config/src/config/lifecycle.rs @@ -29,6 +29,12 @@ impl LifecycleMode { !matches!(self, LifecycleMode::Offline) } + /// Check whether the validator lifecycle dictates to + /// clone program accounts only + pub fn clones_programs_only(&self) -> bool { + matches!(self, LifecycleMode::ProgramsReplica) + } + /// Check whether the validator lifecycle enforces access permissions /// which is only the case currently in [LifecycleMode::Ephemeral] mode pub fn enforce_access_permissions(&self) -> bool { diff --git a/test-integration/test-chainlink/src/ixtest_context.rs b/test-integration/test-chainlink/src/ixtest_context.rs index 914b4479a..9cb24564c 100644 --- a/test-integration/test-chainlink/src/ixtest_context.rs +++ b/test-integration/test-chainlink/src/ixtest_context.rs @@ -128,6 +128,7 @@ impl IxtestContext { faucet_kp.pubkey(), rx, None, + config.remote_account_provider.lifecycle_mode().clone(), )), Some(provider), ) diff --git a/test-integration/test-chainlink/src/test_context.rs b/test-integration/test-chainlink/src/test_context.rs index af839f405..58f32c76e 100644 --- a/test-integration/test-chainlink/src/test_context.rs +++ b/test-integration/test-chainlink/src/test_context.rs @@ -102,6 +102,7 @@ impl TestContext { faucet_pubkey, rx, None, + lifecycle_mode, )), Some(provider), ) From 58a6b7f7a111da8f9e23e3061645ad82ef1e8442 Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Tue, 3 Feb 2026 16:23:08 +0800 Subject: [PATCH 16/25] chore: respect programs replica cloning restriction --- magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs b/magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs index f12b6687b..b0827510c 100644 --- a/magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs +++ b/magicblock-chainlink/src/chainlink/fetch_cloner/mod.rs @@ -788,6 +788,11 @@ where .await; accounts_to_clone.extend(ata_accounts); + // If lifecycle mode requires programs-only cloning, filter out non-program accounts + if self.lifecycle_mode.clones_programs_only() { + accounts_to_clone.retain(|request| request.account.executable()); + } + // Compute sub cancellations now since we may potentially fail during a cloning step let cancel_strategy = pipeline::compute_cancel_strategy( pubkeys, From 006fee4f8cbd840f3dd08b5117e83363f38072dd Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Wed, 4 Feb 2026 10:45:56 +0800 Subject: [PATCH 17/25] chore: update svm dep + fix compile issue --- Cargo.lock | 5 +++++ Cargo.toml | 3 +-- test-integration/test-chainlink/src/test_context.rs | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bf8c8ac73..a6372deaa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9599,3 +9599,8 @@ dependencies = [ "cc", "pkg-config", ] + +[[patch.unused]] +name = "solana-svm" +version = "2.2.1" +source = "git+https://github.com/magicblock-labs/magicblock-svm.git?rev=569cb82#569cb82aa0081cd8433147bd54d942f08be57715" diff --git a/Cargo.toml b/Cargo.toml index 2323c837e..b5cad1ebc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -232,8 +232,7 @@ version = "0.22.0" # and we use protobuf-src v2.1.1. Otherwise compilation fails solana-account = { git = "https://github.com/magicblock-labs/solana-account.git", rev = "2246929" } solana-storage-proto = { path = "./storage-proto" } -# solana-svm = { git = "https://github.com/magicblock-labs/magicblock-svm.git", rev = "3e9456ec4" } -solana-svm = { path = "../magicblock-svm" } +solana-svm = { git = "https://github.com/magicblock-labs/magicblock-svm.git", rev = "569cb82" } # Fork is used to enable `disable_manual_compaction` usage # Fork is based on commit d4e9e16 of rocksdb (parent commit of 0.23.0 release) # without patching update isn't possible due to conflict with solana deps diff --git a/test-integration/test-chainlink/src/test_context.rs b/test-integration/test-chainlink/src/test_context.rs index 58f32c76e..5ea43553f 100644 --- a/test-integration/test-chainlink/src/test_context.rs +++ b/test-integration/test-chainlink/src/test_context.rs @@ -71,7 +71,7 @@ impl TestContext { let (tx, rx) = tokio::sync::mpsc::channel(100); let config = RemoteAccountProviderConfig::try_new_with_metrics( 1000, // subscribed_accounts_lru_capacity - lifecycle_mode, + lifecycle_mode.clone(), false, // disable subscription metrics ) .unwrap(); From c22898808ce7127a307114c34012a682af5970d7 Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Wed, 4 Feb 2026 13:02:51 +0800 Subject: [PATCH 18/25] chore: move setup methods to test utils for reuse --- test-integration/Cargo.lock | 2 + .../test-ledger-restore/src/lib.rs | 258 +----------------- .../tests/01_single_transfer.rs | 9 +- .../tests/02_two_transfers.rs | 9 +- .../tests/03_single_block_tx_order.rs | 8 +- .../tests/04_flexi_counter.rs | 11 +- .../tests/05_program_deploy.rs | 9 +- .../tests/06_delegated_account.rs | 13 +- .../tests/07_commit_delegated_account.rs | 16 +- .../tests/08_commit_update.rs | 16 +- ...store_different_accounts_multiple_times.rs | 16 +- .../tests/10_readonly_update_after.rs | 36 ++- .../tests/11_undelegate_before_restart.rs | 15 +- ...12_two_airdrops_one_after_account_flush.rs | 10 +- .../13_timestamps_match_during_replay.rs | 8 +- .../tests/15_resume_strategies.rs | 9 +- test-integration/test-tools/Cargo.toml | 2 + test-integration/test-tools/src/lib.rs | 6 +- .../test-tools/src/scenario_setup.rs | 245 +++++++++++++++++ 19 files changed, 389 insertions(+), 309 deletions(-) create mode 100644 test-integration/test-tools/src/scenario_setup.rs diff --git a/test-integration/Cargo.lock b/test-integration/Cargo.lock index b73e95d1e..6bbcc9dc0 100644 --- a/test-integration/Cargo.lock +++ b/test-integration/Cargo.lock @@ -2881,10 +2881,12 @@ version = "0.0.0" dependencies = [ "anyhow", "borsh 1.6.0", + "cleanass", "color-backtrace", "magicblock-config", "magicblock-core", "magicblock-delegation-program 1.1.3 (git+https://github.com/magicblock-labs/delegation-program.git?rev=1874b4f5f5f55cb9ab54b64de2cc0d41107d1435)", + "program-flexi-counter", "random-port", "rayon", "serde", diff --git a/test-integration/test-ledger-restore/src/lib.rs b/test-integration/test-ledger-restore/src/lib.rs index a32ade83b..6da61a337 100644 --- a/test-integration/test-ledger-restore/src/lib.rs +++ b/test-integration/test-ledger-restore/src/lib.rs @@ -2,7 +2,7 @@ use std::{ path::Path, process::Child, str::FromStr, thread::sleep, time::Duration, }; -use cleanass::{assert, assert_eq}; +use cleanass::assert_eq; use integration_test_tools::{ expect, loaded_accounts::LoadedAccounts, @@ -22,22 +22,9 @@ use magicblock_config::{ types::{crypto::SerdePubkey, network::Remote, StorageDirectory}, ValidatorParams, }; -use program_flexi_counter::{ - instruction::{create_delegate_ix, create_init_ix}, - state::FlexiCounter, -}; -use solana_rpc_client::rpc_client::RpcClient; -use solana_sdk::{ - clock::Slot, - instruction::Instruction, - native_token::LAMPORTS_PER_SOL, - pubkey::Pubkey, - signature::{Keypair, Signature}, - signer::Signer, - transaction::Transaction, -}; +use program_flexi_counter::state::FlexiCounter; +use solana_sdk::{clock::Slot, native_token::LAMPORTS_PER_SOL, pubkey::Pubkey}; use tempfile::TempDir; -use tracing::*; pub const TMP_DIR_LEDGER: &str = "TMP_DIR_LEDGER"; pub const SNAPSHOT_FREQUENCY: u64 = 2; @@ -182,234 +169,6 @@ pub fn setup_validator_with_local_remote_and_resume_strategy( (default_tmpdir_config, validator, ctx) } -// ----------------- -// Transactions and Account Updates -// ----------------- -pub fn init_and_delegate_counter_and_payer( - ctx: &IntegrationTestContext, - validator: &mut Child, - label: &str, -) -> (Keypair, Pubkey) { - // 1. Airdrop to payer on chain - let mut keypairs = - airdrop_accounts_on_chain(ctx, validator, &[2 * LAMPORTS_PER_SOL]); - let payer = keypairs.drain(0..1).next().unwrap(); - - // 2. Init counter instruction on chain - let ix = create_init_ix(payer.pubkey(), label.to_string()); - confirm_tx_with_payer_chain(ix, &payer, validator); - - // 3 Delegate counter PDA - let ix = create_delegate_ix(payer.pubkey()); - confirm_tx_with_payer_chain(ix, &payer, validator); - - // 4. Now we can delegate the payer to use for counter instructions - // in the ephemeral - delegate_accounts(ctx, validator, &[&payer]); - - // 5. Verify all accounts are initialized correctly - let (counter_pda, _) = FlexiCounter::pda(&payer.pubkey()); - let counter = fetch_counter_chain(&payer.pubkey(), validator); - assert_eq!( - counter, - FlexiCounter { - count: 0, - updates: 0, - label: label.to_string() - }, - cleanup(validator) - ); - let owner = fetch_counter_owner_chain(&payer.pubkey(), validator); - assert_eq!(owner, dlp::id(), cleanup(validator)); - - let payer_chain = - expect!(ctx.fetch_chain_account(payer.pubkey()), validator); - assert_eq!(payer_chain.owner, dlp::id(), cleanup(validator)); - assert!(payer_chain.lamports > LAMPORTS_PER_SOL, cleanup(validator)); - - debug!( - "✅ Initialized and delegated counter {counter_pda} and payer {}", - payer.pubkey() - ); - - (payer, counter_pda) -} - -pub fn airdrop_accounts_on_chain( - ctx: &IntegrationTestContext, - validator: &mut Child, - lamports: &[u64], -) -> Vec { - let mut payers = vec![]; - for l in lamports.iter() { - let payer_chain = Keypair::new(); - expect!(ctx.airdrop_chain(&payer_chain.pubkey(), *l), validator); - payers.push(payer_chain); - } - payers -} - -pub fn delegate_accounts( - ctx: &IntegrationTestContext, - validator: &mut Child, - keypairs: &[&Keypair], -) { - let payer_chain = Keypair::new(); - expect!( - ctx.airdrop_chain(&payer_chain.pubkey(), LAMPORTS_PER_SOL), - validator - ); - for keypair in keypairs.iter() { - expect!( - ctx.delegate_account(&payer_chain, keypair), - format!("Failed to delegate keypair {}", keypair.pubkey()), - validator - ); - } -} - -pub fn airdrop_and_delegate_accounts( - ctx: &IntegrationTestContext, - validator: &mut Child, - lamports: &[u64], -) -> Vec { - let payer_chain = Keypair::new(); - - let total_lamports: u64 = lamports.iter().sum(); - let payer_lamports = LAMPORTS_PER_SOL + total_lamports; - // 1. Airdrop to payer on chain - expect!( - ctx.airdrop_chain(&payer_chain.pubkey(), payer_lamports), - validator - ); - // 2. Airdrop to ephem payers and delegate them - let keypairs_lamports = lamports - .iter() - .map(|&l| (Keypair::new(), l)) - .collect::>(); - - for (keypair, l) in keypairs_lamports.iter() { - expect!( - ctx.airdrop_chain_and_delegate(&payer_chain, keypair, *l), - format!("Failed to airdrop {l} and delegate keypair"), - validator - ); - } - keypairs_lamports - .into_iter() - .map(|(k, _)| k) - .collect::>() -} - -pub fn transfer_lamports( - ctx: &IntegrationTestContext, - validator: &mut Child, - from: &Keypair, - to: &Pubkey, - lamports: u64, -) -> Signature { - let transfer_ix = - solana_sdk::system_instruction::transfer(&from.pubkey(), to, lamports); - let (sig, confirmed) = expect!( - ctx.send_and_confirm_instructions_with_payer_ephem( - &[transfer_ix], - from - ), - "Failed to send transfer", - validator - ); - - assert!(confirmed, cleanup(validator)); - sig -} - -pub fn send_tx_with_payer_chain( - ix: Instruction, - payer: &Keypair, - validator: &mut Child, -) -> Signature { - let ctx = expect!(IntegrationTestContext::try_new(), validator); - let mut tx = Transaction::new_with_payer(&[ix], Some(&payer.pubkey())); - let signers = &[payer]; - - let sig = expect!(ctx.send_transaction_chain(&mut tx, signers), validator); - sig -} - -pub fn confirm_tx_with_payer_ephem( - ix: Instruction, - payer: &Keypair, - ctx: &IntegrationTestContext, - validator: &mut Child, -) -> Signature { - let mut tx = Transaction::new_with_payer(&[ix], Some(&payer.pubkey())); - let signers = &[payer]; - - let (sig, confirmed) = expect!( - ctx.send_and_confirm_transaction_ephem(&mut tx, signers), - validator - ); - assert!(confirmed, cleanup(validator), "Should confirm transaction",); - sig -} - -pub fn confirm_tx_with_payer_chain( - ix: Instruction, - payer: &Keypair, - validator: &mut Child, -) -> Signature { - let ctx = expect!(IntegrationTestContext::try_new_chain_only(), validator); - - let mut tx = Transaction::new_with_payer(&[ix], Some(&payer.pubkey())); - let signers = &[payer]; - - let (sig, confirmed) = expect!( - ctx.send_and_confirm_transaction_chain(&mut tx, signers), - validator - ); - assert!(confirmed, cleanup(validator), "Should confirm transaction"); - sig -} - -pub fn fetch_counter_ephem( - ctx: &IntegrationTestContext, - payer: &Pubkey, - validator: &mut Child, -) -> FlexiCounter { - let ephem_client = expect!(ctx.try_ephem_client(), validator); - fetch_counter(payer, ephem_client, validator, "ephem") -} - -pub fn fetch_counter_chain( - payer: &Pubkey, - validator: &mut Child, -) -> FlexiCounter { - let ctx = expect!(IntegrationTestContext::try_new_chain_only(), validator); - let chain_client = expect!(ctx.try_chain_client(), validator); - fetch_counter(payer, chain_client, validator, "chain") -} - -fn fetch_counter( - payer: &Pubkey, - rpc_client: &RpcClient, - validator: &mut Child, - source: &str, -) -> FlexiCounter { - let (counter, _) = FlexiCounter::pda(payer); - debug!("Fetching counter {counter} for payer {payer} from {source}"); - let counter_acc = expect!(rpc_client.get_account(&counter), validator); - expect!(FlexiCounter::try_decode(&counter_acc.data), validator) -} - -pub fn fetch_counter_owner_chain( - payer: &Pubkey, - validator: &mut Child, -) -> Pubkey { - let ctx = expect!(IntegrationTestContext::try_new_chain_only(), validator); - let (counter, _) = FlexiCounter::pda(payer); - expect!(ctx.fetch_chain_account_owner(counter), validator) -} - // ----------------- // Slot Advances // ----------------- @@ -498,7 +257,10 @@ pub struct Counter<'a> { macro_rules! assert_counter_state { ($ctx:expr, $validator:expr, $expected:expr, $label:ident) => { let counter_chain = - $crate::fetch_counter_chain($expected.payer, $validator); + ::integration_test_tools::scenario_setup::fetch_counter_chain( + $expected.payer, + $validator, + ); ::cleanass::assert_eq!( counter_chain, ::program_flexi_counter::state::FlexiCounter { @@ -510,7 +272,11 @@ macro_rules! assert_counter_state { ); let counter_ephem = - $crate::fetch_counter_ephem($ctx, $expected.payer, $validator); + ::integration_test_tools::scenario_setup::fetch_counter_ephem( + $ctx, + $expected.payer, + $validator, + ); ::cleanass::assert_eq!( counter_ephem, ::program_flexi_counter::state::FlexiCounter { diff --git a/test-integration/test-ledger-restore/tests/01_single_transfer.rs b/test-integration/test-ledger-restore/tests/01_single_transfer.rs index 25f3de384..92cee5c0e 100644 --- a/test-integration/test-ledger-restore/tests/01_single_transfer.rs +++ b/test-integration/test-ledger-restore/tests/01_single_transfer.rs @@ -2,7 +2,11 @@ use std::{path::Path, process::Child}; use cleanass::{assert, assert_eq}; use integration_test_tools::{ - expect, tmpdir::resolve_tmp_dir, unwrap, validator::cleanup, + expect, + scenario_setup::{airdrop_and_delegate_accounts, transfer_lamports}, + tmpdir::resolve_tmp_dir, + unwrap, + validator::cleanup, }; use solana_sdk::{ commitment_config::CommitmentConfig, @@ -12,8 +16,7 @@ use solana_sdk::{ }; use test_kit::init_logger; use test_ledger_restore::{ - airdrop_and_delegate_accounts, setup_offline_validator, - setup_validator_with_local_remote, transfer_lamports, + setup_offline_validator, setup_validator_with_local_remote, wait_for_ledger_persist, TMP_DIR_LEDGER, }; use tracing::*; diff --git a/test-integration/test-ledger-restore/tests/02_two_transfers.rs b/test-integration/test-ledger-restore/tests/02_two_transfers.rs index b2ea819eb..a2649ab47 100644 --- a/test-integration/test-ledger-restore/tests/02_two_transfers.rs +++ b/test-integration/test-ledger-restore/tests/02_two_transfers.rs @@ -2,7 +2,11 @@ use std::{path::Path, process::Child}; use cleanass::{assert, assert_eq}; use integration_test_tools::{ - expect, tmpdir::resolve_tmp_dir, unwrap, validator::cleanup, + expect, + scenario_setup::{airdrop_and_delegate_accounts, transfer_lamports}, + tmpdir::resolve_tmp_dir, + unwrap, + validator::cleanup, }; use solana_sdk::{ commitment_config::CommitmentConfig, @@ -11,8 +15,7 @@ use solana_sdk::{ }; use test_kit::Signer; use test_ledger_restore::{ - airdrop_and_delegate_accounts, setup_offline_validator, - setup_validator_with_local_remote, transfer_lamports, + setup_offline_validator, setup_validator_with_local_remote, wait_for_ledger_persist, TMP_DIR_LEDGER, }; diff --git a/test-integration/test-ledger-restore/tests/03_single_block_tx_order.rs b/test-integration/test-ledger-restore/tests/03_single_block_tx_order.rs index d1fa24e37..d70dace5e 100644 --- a/test-integration/test-ledger-restore/tests/03_single_block_tx_order.rs +++ b/test-integration/test-ledger-restore/tests/03_single_block_tx_order.rs @@ -2,7 +2,10 @@ use std::{path::Path, process::Child}; use cleanass::assert_eq; use integration_test_tools::{ - expect, tmpdir::resolve_tmp_dir, validator::cleanup, + expect, + scenario_setup::{airdrop_and_delegate_accounts, transfer_lamports}, + tmpdir::resolve_tmp_dir, + validator::cleanup, }; use solana_sdk::{ native_token::LAMPORTS_PER_SOL, @@ -10,8 +13,7 @@ use solana_sdk::{ signature::{Keypair, Signer}, }; use test_ledger_restore::{ - airdrop_and_delegate_accounts, setup_offline_validator, - setup_validator_with_local_remote, transfer_lamports, + setup_offline_validator, setup_validator_with_local_remote, wait_for_ledger_persist, TMP_DIR_LEDGER, }; diff --git a/test-integration/test-ledger-restore/tests/04_flexi_counter.rs b/test-integration/test-ledger-restore/tests/04_flexi_counter.rs index 775337bcb..6ffe0d2bd 100644 --- a/test-integration/test-ledger-restore/tests/04_flexi_counter.rs +++ b/test-integration/test-ledger-restore/tests/04_flexi_counter.rs @@ -2,7 +2,13 @@ use std::{path::Path, process::Child}; use cleanass::assert_eq; use integration_test_tools::{ - expect, loaded_accounts::LoadedAccounts, tmpdir::resolve_tmp_dir, + expect, + loaded_accounts::LoadedAccounts, + scenario_setup::{ + confirm_tx_with_payer_ephem, fetch_counter_ephem, + init_and_delegate_counter_and_payer, + }, + tmpdir::resolve_tmp_dir, validator::cleanup, }; use program_flexi_counter::{ @@ -12,8 +18,7 @@ use program_flexi_counter::{ use solana_sdk::{pubkey::Pubkey, signer::Signer}; use test_kit::init_logger; use test_ledger_restore::{ - confirm_tx_with_payer_ephem, fetch_counter_ephem, - init_and_delegate_counter_and_payer, setup_offline_validator, + setup_offline_validator, setup_validator_with_local_remote_and_resume_strategy, wait_for_ledger_persist, TMP_DIR_LEDGER, }; diff --git a/test-integration/test-ledger-restore/tests/05_program_deploy.rs b/test-integration/test-ledger-restore/tests/05_program_deploy.rs index e9e98537a..1d7dc8e4d 100644 --- a/test-integration/test-ledger-restore/tests/05_program_deploy.rs +++ b/test-integration/test-ledger-restore/tests/05_program_deploy.rs @@ -7,7 +7,10 @@ use std::{ use cleanass::assert_eq; use integration_test_tools::{ - expect, tmpdir::resolve_tmp_dir, validator::cleanup, + expect, + scenario_setup::{confirm_tx_with_payer_ephem, fetch_counter_ephem}, + tmpdir::resolve_tmp_dir, + validator::cleanup, workspace_paths::TestProgramPaths, }; use program_flexi_counter::{ @@ -21,8 +24,8 @@ use solana_sdk::{ signer::{EncodableKey, Signer}, }; use test_ledger_restore::{ - confirm_tx_with_payer_ephem, fetch_counter_ephem, setup_offline_validator, - wait_for_ledger_persist, FLEXI_COUNTER_ID, TMP_DIR_LEDGER, + setup_offline_validator, wait_for_ledger_persist, FLEXI_COUNTER_ID, + TMP_DIR_LEDGER, }; fn read_authority_pubkey(paths: &TestProgramPaths) -> Pubkey { diff --git a/test-integration/test-ledger-restore/tests/06_delegated_account.rs b/test-integration/test-ledger-restore/tests/06_delegated_account.rs index 0aca0e1e5..4cf93534d 100644 --- a/test-integration/test-ledger-restore/tests/06_delegated_account.rs +++ b/test-integration/test-ledger-restore/tests/06_delegated_account.rs @@ -2,17 +2,20 @@ use std::{path::Path, process::Child}; use cleanass::assert_eq; use integration_test_tools::{ - loaded_accounts::LoadedAccounts, tmpdir::resolve_tmp_dir, + loaded_accounts::LoadedAccounts, + scenario_setup::{ + confirm_tx_with_payer_ephem, fetch_counter_ephem, + init_and_delegate_counter_and_payer, + }, + tmpdir::resolve_tmp_dir, validator::cleanup, }; use program_flexi_counter::{instruction::create_add_ix, state::FlexiCounter}; use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer}; use test_kit::init_logger; use test_ledger_restore::{ - confirm_tx_with_payer_ephem, fetch_counter_ephem, - init_and_delegate_counter_and_payer, setup_validator_with_local_remote, - wait_for_cloned_accounts_hydration, wait_for_ledger_persist, - TMP_DIR_LEDGER, + setup_validator_with_local_remote, wait_for_cloned_accounts_hydration, + wait_for_ledger_persist, TMP_DIR_LEDGER, }; use tracing::*; diff --git a/test-integration/test-ledger-restore/tests/07_commit_delegated_account.rs b/test-integration/test-ledger-restore/tests/07_commit_delegated_account.rs index 7b62ccc79..0508f3011 100644 --- a/test-integration/test-ledger-restore/tests/07_commit_delegated_account.rs +++ b/test-integration/test-ledger-restore/tests/07_commit_delegated_account.rs @@ -2,7 +2,13 @@ use std::{path::Path, process::Child}; use cleanass::assert_eq; use integration_test_tools::{ - expect, loaded_accounts::LoadedAccounts, tmpdir::resolve_tmp_dir, + expect, + loaded_accounts::LoadedAccounts, + scenario_setup::{ + confirm_tx_with_payer_ephem, fetch_counter_chain, fetch_counter_ephem, + init_and_delegate_counter_and_payer, + }, + tmpdir::resolve_tmp_dir, validator::cleanup, }; use program_flexi_counter::{ @@ -14,11 +20,9 @@ use program_flexi_counter::{ use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer}; use test_kit::init_logger; use test_ledger_restore::{ - assert_counter_commits_on_chain, confirm_tx_with_payer_ephem, - fetch_counter_chain, fetch_counter_ephem, get_programs_with_flexi_counter, - init_and_delegate_counter_and_payer, setup_validator_with_local_remote, - wait_for_cloned_accounts_hydration, wait_for_ledger_persist, - TMP_DIR_LEDGER, + assert_counter_commits_on_chain, get_programs_with_flexi_counter, + setup_validator_with_local_remote, wait_for_cloned_accounts_hydration, + wait_for_ledger_persist, TMP_DIR_LEDGER, }; use tracing::*; diff --git a/test-integration/test-ledger-restore/tests/08_commit_update.rs b/test-integration/test-ledger-restore/tests/08_commit_update.rs index da24d2af2..c923f2324 100644 --- a/test-integration/test-ledger-restore/tests/08_commit_update.rs +++ b/test-integration/test-ledger-restore/tests/08_commit_update.rs @@ -2,7 +2,13 @@ use std::{path::Path, process::Child}; use cleanass::assert_eq; use integration_test_tools::{ - expect, loaded_accounts::LoadedAccounts, tmpdir::resolve_tmp_dir, + expect, + loaded_accounts::LoadedAccounts, + scenario_setup::{ + confirm_tx_with_payer_chain, confirm_tx_with_payer_ephem, + fetch_counter_chain, fetch_counter_ephem, + }, + tmpdir::resolve_tmp_dir, validator::cleanup, }; use program_flexi_counter::{ @@ -16,11 +22,9 @@ use solana_sdk::{ native_token::LAMPORTS_PER_SOL, signature::Keypair, signer::Signer, }; use test_ledger_restore::{ - assert_counter_commits_on_chain, confirm_tx_with_payer_chain, - confirm_tx_with_payer_ephem, fetch_counter_chain, fetch_counter_ephem, - get_programs_with_flexi_counter, setup_validator_with_local_remote, - wait_for_cloned_accounts_hydration, wait_for_ledger_persist, - TMP_DIR_LEDGER, + assert_counter_commits_on_chain, get_programs_with_flexi_counter, + setup_validator_with_local_remote, wait_for_cloned_accounts_hydration, + wait_for_ledger_persist, TMP_DIR_LEDGER, }; const COUNTER: &str = "Counter of Payer"; fn payer_keypair() -> Keypair { diff --git a/test-integration/test-ledger-restore/tests/09_restore_different_accounts_multiple_times.rs b/test-integration/test-ledger-restore/tests/09_restore_different_accounts_multiple_times.rs index 786ae2dec..c0af60918 100644 --- a/test-integration/test-ledger-restore/tests/09_restore_different_accounts_multiple_times.rs +++ b/test-integration/test-ledger-restore/tests/09_restore_different_accounts_multiple_times.rs @@ -2,7 +2,14 @@ use std::{path::Path, process::Child}; use cleanass::assert_eq; use integration_test_tools::{ - expect, loaded_accounts::LoadedAccounts, tmpdir::resolve_tmp_dir, + expect, + loaded_accounts::LoadedAccounts, + scenario_setup::{ + confirm_tx_with_payer_chain, confirm_tx_with_payer_ephem, + fetch_counter_chain, fetch_counter_ephem, + init_and_delegate_counter_and_payer, + }, + tmpdir::resolve_tmp_dir, validator::cleanup, }; use program_flexi_counter::{ @@ -14,11 +21,8 @@ use solana_sdk::{ }; use test_kit::init_logger; use test_ledger_restore::{ - confirm_tx_with_payer_chain, confirm_tx_with_payer_ephem, - fetch_counter_chain, fetch_counter_ephem, - init_and_delegate_counter_and_payer, setup_validator_with_local_remote, - wait_for_cloned_accounts_hydration, wait_for_ledger_persist, - TMP_DIR_LEDGER, + setup_validator_with_local_remote, wait_for_cloned_accounts_hydration, + wait_for_ledger_persist, TMP_DIR_LEDGER, }; use tracing::*; const COUNTER_MAIN: &str = "Main Counter"; diff --git a/test-integration/test-ledger-restore/tests/10_readonly_update_after.rs b/test-integration/test-ledger-restore/tests/10_readonly_update_after.rs index 3faa5e33e..f228eadfe 100644 --- a/test-integration/test-ledger-restore/tests/10_readonly_update_after.rs +++ b/test-integration/test-ledger-restore/tests/10_readonly_update_after.rs @@ -2,7 +2,13 @@ use std::{path::Path, process::Child}; use cleanass::assert_eq; use integration_test_tools::{ - expect, loaded_accounts::LoadedAccounts, tmpdir::resolve_tmp_dir, + expect, + loaded_accounts::LoadedAccounts, + scenario_setup::{ + confirm_tx_with_payer_chain, confirm_tx_with_payer_ephem, + delegate_accounts, + }, + tmpdir::resolve_tmp_dir, validator::cleanup, }; use program_flexi_counter::{ @@ -17,9 +23,7 @@ use solana_sdk::{ }; use test_kit::init_logger; use test_ledger_restore::{ - assert_counter_state, confirm_tx_with_payer_chain, - confirm_tx_with_payer_ephem, delegate_accounts, fetch_counter_chain, - fetch_counter_ephem, get_programs_with_flexi_counter, + assert_counter_state, get_programs_with_flexi_counter, setup_validator_with_local_remote, wait_for_cloned_accounts_hydration, wait_for_ledger_persist, Counter, State, TMP_DIR_LEDGER, }; @@ -84,10 +88,17 @@ struct ExpectedCounterStates<'a> { macro_rules! add_to_readonly { ($validator:expr, $payer_readonly:expr, $count:expr, $expected:expr) => { let ix = create_add_ix($payer_readonly.pubkey(), $count); - confirm_tx_with_payer_chain(ix, $payer_readonly, $validator); + ::integration_test_tools::scenario_setup::confirm_tx_with_payer_chain( + ix, + $payer_readonly, + $validator, + ); let counter_readonly_chain = - fetch_counter_chain(&$payer_readonly.pubkey(), $validator); + ::integration_test_tools::scenario_setup::fetch_counter_chain( + &$payer_readonly.pubkey(), + $validator, + ); assert_eq!(counter_readonly_chain, $expected, cleanup($validator)); }; } @@ -98,10 +109,19 @@ macro_rules! add_readonly_to_main { $payer_main.pubkey(), $payer_readonly.pubkey(), ); - confirm_tx_with_payer_ephem(ix, $payer_main, $ctx, $validator); + ::integration_test_tools::scenario_setup::confirm_tx_with_payer_ephem( + ix, + $payer_main, + $ctx, + $validator, + ); let counter_main_ephem = - fetch_counter_ephem($ctx, &$payer_main.pubkey(), $validator); + ::integration_test_tools::scenario_setup::fetch_counter_ephem( + $ctx, + &$payer_main.pubkey(), + $validator, + ); assert_eq!(counter_main_ephem, $expected, cleanup($validator)); }; } diff --git a/test-integration/test-ledger-restore/tests/11_undelegate_before_restart.rs b/test-integration/test-ledger-restore/tests/11_undelegate_before_restart.rs index 5c0046a3c..2f896b3f9 100644 --- a/test-integration/test-ledger-restore/tests/11_undelegate_before_restart.rs +++ b/test-integration/test-ledger-restore/tests/11_undelegate_before_restart.rs @@ -2,8 +2,15 @@ use std::{path::Path, process::Child}; use cleanass::assert; use integration_test_tools::{ - conversions::get_rpc_transwise_error_msg, expect, expect_err, - loaded_accounts::LoadedAccounts, tmpdir::resolve_tmp_dir, unwrap, + conversions::get_rpc_transwise_error_msg, + expect, expect_err, + loaded_accounts::LoadedAccounts, + scenario_setup::{ + airdrop_accounts_on_chain, confirm_tx_with_payer_chain, + confirm_tx_with_payer_ephem, delegate_accounts, + }, + tmpdir::resolve_tmp_dir, + unwrap, validator::cleanup, }; use program_flexi_counter::{ @@ -19,9 +26,7 @@ use solana_sdk::{ }; use test_kit::init_logger; use test_ledger_restore::{ - airdrop_accounts_on_chain, assert_counter_state, - confirm_tx_with_payer_chain, confirm_tx_with_payer_ephem, - delegate_accounts, get_programs_with_flexi_counter, + assert_counter_state, get_programs_with_flexi_counter, setup_validator_with_local_remote, wait_for_ledger_persist, Counter, State, TMP_DIR_LEDGER, }; diff --git a/test-integration/test-ledger-restore/tests/12_two_airdrops_one_after_account_flush.rs b/test-integration/test-ledger-restore/tests/12_two_airdrops_one_after_account_flush.rs index 363b8e4cf..1403bd1f0 100644 --- a/test-integration/test-ledger-restore/tests/12_two_airdrops_one_after_account_flush.rs +++ b/test-integration/test-ledger-restore/tests/12_two_airdrops_one_after_account_flush.rs @@ -2,15 +2,17 @@ use std::{path::Path, process::Child}; use cleanass::assert_eq; use integration_test_tools::{ - expect, loaded_accounts::LoadedAccounts, tmpdir::resolve_tmp_dir, + expect, + loaded_accounts::LoadedAccounts, + scenario_setup::{airdrop_and_delegate_accounts, transfer_lamports}, + tmpdir::resolve_tmp_dir, validator::cleanup, }; use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer}; use test_kit::init_logger; use test_ledger_restore::{ - airdrop_and_delegate_accounts, setup_validator_with_local_remote, - transfer_lamports, wait_for_ledger_persist, SNAPSHOT_FREQUENCY, - TMP_DIR_LEDGER, + setup_validator_with_local_remote, wait_for_ledger_persist, + SNAPSHOT_FREQUENCY, TMP_DIR_LEDGER, }; use tracing::*; diff --git a/test-integration/test-ledger-restore/tests/13_timestamps_match_during_replay.rs b/test-integration/test-ledger-restore/tests/13_timestamps_match_during_replay.rs index 050eea256..b8faceee1 100644 --- a/test-integration/test-ledger-restore/tests/13_timestamps_match_during_replay.rs +++ b/test-integration/test-ledger-restore/tests/13_timestamps_match_during_replay.rs @@ -2,7 +2,10 @@ use std::{path::Path, process::Child}; use cleanass::assert_eq; use integration_test_tools::{ - expect, loaded_accounts::LoadedAccounts, tmpdir::resolve_tmp_dir, + expect, + loaded_accounts::LoadedAccounts, + scenario_setup::{airdrop_and_delegate_accounts, transfer_lamports}, + tmpdir::resolve_tmp_dir, validator::cleanup, }; use solana_sdk::{ @@ -12,8 +15,7 @@ use solana_sdk::{ use solana_transaction_status::UiTransactionEncoding; use test_kit::init_logger; use test_ledger_restore::{ - airdrop_and_delegate_accounts, setup_offline_validator, - setup_validator_with_local_remote, transfer_lamports, + setup_offline_validator, setup_validator_with_local_remote, wait_for_ledger_persist, SNAPSHOT_FREQUENCY, TMP_DIR_LEDGER, }; use tracing::*; diff --git a/test-integration/test-ledger-restore/tests/15_resume_strategies.rs b/test-integration/test-ledger-restore/tests/15_resume_strategies.rs index 71745dfa7..aada94d2c 100644 --- a/test-integration/test-ledger-restore/tests/15_resume_strategies.rs +++ b/test-integration/test-ledger-restore/tests/15_resume_strategies.rs @@ -2,7 +2,10 @@ use std::{path::Path, process::Child}; use cleanass::{assert, assert_eq}; use integration_test_tools::{ - expect, tmpdir::resolve_tmp_dir, validator::cleanup, + expect, + scenario_setup::{airdrop_and_delegate_accounts, transfer_lamports}, + tmpdir::resolve_tmp_dir, + validator::cleanup, }; use solana_sdk::{ signature::{Keypair, Signature}, @@ -10,8 +13,8 @@ use solana_sdk::{ }; use test_kit::init_logger; use test_ledger_restore::{ - airdrop_and_delegate_accounts, setup_validator_with_local_remote, - setup_validator_with_local_remote_and_resume_strategy, transfer_lamports, + setup_validator_with_local_remote, + setup_validator_with_local_remote_and_resume_strategy, wait_for_ledger_persist, wait_for_next_slot_after_account_snapshot, SNAPSHOT_FREQUENCY, TMP_DIR_LEDGER, }; diff --git a/test-integration/test-tools/Cargo.toml b/test-integration/test-tools/Cargo.toml index 3bccbbe57..a84cffd7a 100644 --- a/test-integration/test-tools/Cargo.toml +++ b/test-integration/test-tools/Cargo.toml @@ -7,6 +7,7 @@ edition.workspace = true anyhow = { workspace = true } borsh = { workspace = true } color-backtrace = { workspace = true } +cleanass = { workspace = true } tracing = { workspace = true } random-port = { workspace = true } rayon = { workspace = true } @@ -18,6 +19,7 @@ magicblock-config = { workspace = true } magicblock-delegation-program = { workspace = true, features = [ "no-entrypoint", ] } +program-flexi-counter = { workspace = true, features = ["no-entrypoint"] } solana-pubkey = { workspace = true } solana-rpc-client = { workspace = true } solana-rpc-client-api = { workspace = true } diff --git a/test-integration/test-tools/src/lib.rs b/test-integration/test-tools/src/lib.rs index c7518eb41..63fdbddb5 100644 --- a/test-integration/test-tools/src/lib.rs +++ b/test-integration/test-tools/src/lib.rs @@ -3,13 +3,15 @@ pub mod dlp_interface; mod integration_test_context; pub mod loaded_accounts; mod run_test; +pub mod toml_to_args; +pub mod validator; +pub mod scenario_setup; pub mod scheduled_commits; pub mod tmpdir; pub mod transactions; pub mod workspace_paths; -pub mod toml_to_args; -pub mod validator; pub use color_backtrace; pub use integration_test_context::IntegrationTestContext; pub use run_test::*; +pub use validator::cleanup; diff --git a/test-integration/test-tools/src/scenario_setup.rs b/test-integration/test-tools/src/scenario_setup.rs new file mode 100644 index 000000000..afcc0c378 --- /dev/null +++ b/test-integration/test-tools/src/scenario_setup.rs @@ -0,0 +1,245 @@ +use crate::{expect, validator::cleanup, IntegrationTestContext}; +use cleanass::{assert, assert_eq}; +use dlp; +use program_flexi_counter::{ + instruction::{create_delegate_ix, create_init_ix}, + state::FlexiCounter, +}; +use solana_rpc_client::rpc_client::RpcClient; +use solana_sdk::{ + instruction::Instruction, + native_token::LAMPORTS_PER_SOL, + pubkey::Pubkey, + signature::{Keypair, Signature, Signer}, + transaction::Transaction, +}; +use std::process::Child; +use tracing::debug; + +// ----------------- +// Transactions and Account Updates +// ----------------- +pub fn init_and_delegate_counter_and_payer( + ctx: &IntegrationTestContext, + validator: &mut Child, + label: &str, +) -> (Keypair, Pubkey) { + // 1. Airdrop to payer on chain + let mut keypairs = + airdrop_accounts_on_chain(ctx, validator, &[2 * LAMPORTS_PER_SOL]); + let payer = keypairs.drain(0..1).next().unwrap(); + + // 2. Init counter instruction on chain + let ix = create_init_ix(payer.pubkey(), label.to_string()); + confirm_tx_with_payer_chain(ix, &payer, validator); + + // 3 Delegate counter PDA + let ix = create_delegate_ix(payer.pubkey()); + confirm_tx_with_payer_chain(ix, &payer, validator); + + // 4. Now we can delegate the payer to use for counter instructions + // in the ephemeral + delegate_accounts(ctx, validator, &[&payer]); + + // 5. Verify all accounts are initialized correctly + let (counter_pda, _) = FlexiCounter::pda(&payer.pubkey()); + let counter = fetch_counter_chain(&payer.pubkey(), validator); + assert_eq!( + counter, + FlexiCounter { + count: 0, + updates: 0, + label: label.to_string() + }, + cleanup(validator) + ); + let owner = fetch_counter_owner_chain(&payer.pubkey(), validator); + assert_eq!(owner, dlp::id(), cleanup(validator)); + + let payer_chain = + expect!(ctx.fetch_chain_account(payer.pubkey()), validator); + assert_eq!(payer_chain.owner, dlp::id(), cleanup(validator)); + assert!(payer_chain.lamports > LAMPORTS_PER_SOL, cleanup(validator)); + + debug!( + "✅ Initialized and delegated counter {counter_pda} and payer {}", + payer.pubkey() + ); + + (payer, counter_pda) +} + +pub fn airdrop_accounts_on_chain( + ctx: &IntegrationTestContext, + validator: &mut Child, + lamports: &[u64], +) -> Vec { + let mut payers = vec![]; + for l in lamports.iter() { + let payer_chain = Keypair::new(); + expect!(ctx.airdrop_chain(&payer_chain.pubkey(), *l), validator); + payers.push(payer_chain); + } + payers +} + +pub fn delegate_accounts( + ctx: &IntegrationTestContext, + validator: &mut Child, + keypairs: &[&Keypair], +) { + let payer_chain = Keypair::new(); + expect!( + ctx.airdrop_chain(&payer_chain.pubkey(), LAMPORTS_PER_SOL), + validator + ); + for keypair in keypairs.iter() { + expect!( + ctx.delegate_account(&payer_chain, keypair), + format!("Failed to delegate keypair {}", keypair.pubkey()), + validator + ); + } +} + +pub fn airdrop_and_delegate_accounts( + ctx: &IntegrationTestContext, + validator: &mut Child, + lamports: &[u64], +) -> Vec { + let payer_chain = Keypair::new(); + + let total_lamports: u64 = lamports.iter().sum(); + let payer_lamports = LAMPORTS_PER_SOL + total_lamports; + // 1. Airdrop to payer on chain + expect!( + ctx.airdrop_chain(&payer_chain.pubkey(), payer_lamports), + validator + ); + // 2. Airdrop to ephem payers and delegate them + let keypairs_lamports = lamports + .iter() + .map(|&l| (Keypair::new(), l)) + .collect::>(); + + for (keypair, l) in keypairs_lamports.iter() { + expect!( + ctx.airdrop_chain_and_delegate(&payer_chain, keypair, *l), + format!("Failed to airdrop {l} and delegate keypair"), + validator + ); + } + keypairs_lamports + .into_iter() + .map(|(k, _)| k) + .collect::>() +} + +pub fn transfer_lamports( + ctx: &IntegrationTestContext, + validator: &mut Child, + from: &Keypair, + to: &Pubkey, + lamports: u64, +) -> Signature { + let transfer_ix = + solana_sdk::system_instruction::transfer(&from.pubkey(), to, lamports); + let (sig, confirmed) = expect!( + ctx.send_and_confirm_instructions_with_payer_ephem( + &[transfer_ix], + from + ), + "Failed to send transfer", + validator + ); + + assert!(confirmed, cleanup(validator)); + sig +} + +pub fn send_tx_with_payer_chain( + ix: Instruction, + payer: &Keypair, + validator: &mut Child, +) -> Signature { + let ctx = expect!(IntegrationTestContext::try_new(), validator); + let mut tx = Transaction::new_with_payer(&[ix], Some(&payer.pubkey())); + let signers = &[payer]; + + let sig = expect!(ctx.send_transaction_chain(&mut tx, signers), validator); + sig +} + +pub fn confirm_tx_with_payer_ephem( + ix: Instruction, + payer: &Keypair, + ctx: &IntegrationTestContext, + validator: &mut Child, +) -> Signature { + let mut tx = Transaction::new_with_payer(&[ix], Some(&payer.pubkey())); + let signers = &[payer]; + + let (sig, confirmed) = expect!( + ctx.send_and_confirm_transaction_ephem(&mut tx, signers), + validator + ); + assert!(confirmed, cleanup(validator), "Should confirm transaction",); + sig +} + +pub fn confirm_tx_with_payer_chain( + ix: Instruction, + payer: &Keypair, + validator: &mut Child, +) -> Signature { + let ctx = expect!(IntegrationTestContext::try_new_chain_only(), validator); + + let mut tx = Transaction::new_with_payer(&[ix], Some(&payer.pubkey())); + let signers = &[payer]; + + let (sig, confirmed) = expect!( + ctx.send_and_confirm_transaction_chain(&mut tx, signers), + validator + ); + assert!(confirmed, cleanup(validator), "Should confirm transaction"); + sig +} + +pub fn fetch_counter_ephem( + ctx: &IntegrationTestContext, + payer: &Pubkey, + validator: &mut Child, +) -> FlexiCounter { + let ephem_client = expect!(ctx.try_ephem_client(), validator); + fetch_counter(payer, ephem_client, validator, "ephem") +} + +pub fn fetch_counter_chain( + payer: &Pubkey, + validator: &mut Child, +) -> FlexiCounter { + let ctx = expect!(IntegrationTestContext::try_new_chain_only(), validator); + let chain_client = expect!(ctx.try_chain_client(), validator); + fetch_counter(payer, chain_client, validator, "chain") +} + +fn fetch_counter( + payer: &Pubkey, + rpc_client: &RpcClient, + validator: &mut Child, + source: &str, +) -> FlexiCounter { + let (counter, _) = FlexiCounter::pda(payer); + debug!("Fetching counter {counter} for payer {payer} from {source}"); + let counter_acc = expect!(rpc_client.get_account(&counter), validator); + expect!(FlexiCounter::try_decode(&counter_acc.data), validator) +} + +pub fn fetch_counter_owner_chain( + payer: &Pubkey, + validator: &mut Child, +) -> Pubkey { + let ctx = expect!(IntegrationTestContext::try_new_chain_only(), validator); + let (counter, _) = FlexiCounter::pda(payer); + expect!(ctx.fetch_chain_account_owner(counter), validator) +} From c2217e23991a43b3f181d10a81316d26ba835ef6 Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Thu, 5 Feb 2026 11:31:27 +0800 Subject: [PATCH 19/25] chore: verify that program replica clones programs fully --- .../tests/lifecycle_modes_cloning.rs | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/test-integration/test-config/tests/lifecycle_modes_cloning.rs b/test-integration/test-config/tests/lifecycle_modes_cloning.rs index cbcb7e923..451ba6343 100644 --- a/test-integration/test-config/tests/lifecycle_modes_cloning.rs +++ b/test-integration/test-config/tests/lifecycle_modes_cloning.rs @@ -1,9 +1,10 @@ -use std::str::FromStr; +use std::{process::Child, str::FromStr}; use cleanass::assert_eq; use integration_test_tools::{ expect, loaded_accounts::LoadedAccounts, + scenario_setup::confirm_tx_with_payer_ephem, validator::{cleanup, start_magicblock_validator_with_config_struct}, IntegrationTestContext, }; @@ -15,6 +16,7 @@ use magicblock_config::{ types::network::Remote, ValidatorParams, }; +use program_flexi_counter::instruction::create_init_ix; use serial_test::file_serial; use solana_sdk::pubkey::Pubkey; use solana_sdk::{ @@ -111,12 +113,24 @@ fn test_lifecycle_programs_replica_does_not_clone_non_program_account() { #[test] #[file_serial] fn test_lifecycle_programs_replica_clones_program_account() { - run_lifecycle_cloning_test( + let (ctx, mut validator) = run_lifecycle_cloning_test_no_cleanup( LifecycleMode::ProgramsReplica, program_flexi_counter::id(), true, true, ); + + // Verify that the program got cloned correctly and can run instructions + let payer = Keypair::new(); + expect!( + ctx.airdrop_ephem(&payer.pubkey(), LAMPORTS_PER_SOL), + validator + ); + let ix = create_init_ix(payer.pubkey(), "Counter".to_string()); + let sig = confirm_tx_with_payer_ephem(ix, &payer, &ctx, &mut validator); + debug!("✅ Initialized flexi counter in ephemeral validator: {sig}"); + + cleanup(&mut validator); } fn run_lifecycle_cloning_test( @@ -125,6 +139,21 @@ fn run_lifecycle_cloning_test( airdrop: bool, expect_clone: bool, ) { + let (_ctx, mut validator) = run_lifecycle_cloning_test_no_cleanup( + lifecycle_mode, + pubkey, + airdrop, + expect_clone, + ); + cleanup(&mut validator); +} + +fn run_lifecycle_cloning_test_no_cleanup( + lifecycle_mode: LifecycleMode, + pubkey: Pubkey, + airdrop: bool, + expect_clone: bool, +) -> (IntegrationTestContext, Child) { init_logger!(); let config = ValidatorParams { @@ -189,6 +218,5 @@ fn run_lifecycle_cloning_test( lifecycle_mode, ); } - - cleanup(&mut validator); + (ctx, validator) } From 88ba1d608d79290ffe86ef524ea7a21788aa4a14 Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Thu, 5 Feb 2026 11:34:41 +0800 Subject: [PATCH 20/25] chore: fmt --- test-integration/test-chainlink/src/ixtest_context.rs | 5 ++++- .../test-config/tests/lifecycle_modes_access.rs | 2 +- .../test-config/tests/lifecycle_modes_cloning.rs | 4 ++-- test-integration/test-tools/src/lib.rs | 4 ++-- test-integration/test-tools/src/scenario_setup.rs | 6 ++++-- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/test-integration/test-chainlink/src/ixtest_context.rs b/test-integration/test-chainlink/src/ixtest_context.rs index 9cb24564c..7384360e8 100644 --- a/test-integration/test-chainlink/src/ixtest_context.rs +++ b/test-integration/test-chainlink/src/ixtest_context.rs @@ -128,7 +128,10 @@ impl IxtestContext { faucet_kp.pubkey(), rx, None, - config.remote_account_provider.lifecycle_mode().clone(), + config + .remote_account_provider + .lifecycle_mode() + .clone(), )), Some(provider), ) diff --git a/test-integration/test-config/tests/lifecycle_modes_access.rs b/test-integration/test-config/tests/lifecycle_modes_access.rs index 7c35de5a2..d1189e397 100644 --- a/test-integration/test-config/tests/lifecycle_modes_access.rs +++ b/test-integration/test-config/tests/lifecycle_modes_access.rs @@ -7,7 +7,6 @@ use integration_test_tools::{ validator::{cleanup, start_magicblock_validator_with_config_struct}, IntegrationTestContext, }; -use tracing::*; use magicblock_config::{ config::{ accounts::AccountsDbConfig, chain::ChainLinkConfig, @@ -22,6 +21,7 @@ use solana_sdk::{ system_instruction, }; use test_kit::init_logger; +use tracing::*; #[test] #[file_serial] diff --git a/test-integration/test-config/tests/lifecycle_modes_cloning.rs b/test-integration/test-config/tests/lifecycle_modes_cloning.rs index 451ba6343..eafc2ad40 100644 --- a/test-integration/test-config/tests/lifecycle_modes_cloning.rs +++ b/test-integration/test-config/tests/lifecycle_modes_cloning.rs @@ -18,9 +18,9 @@ use magicblock_config::{ }; use program_flexi_counter::instruction::create_init_ix; use serial_test::file_serial; -use solana_sdk::pubkey::Pubkey; use solana_sdk::{ - native_token::LAMPORTS_PER_SOL, signature::Keypair, signer::Signer, + native_token::LAMPORTS_PER_SOL, pubkey::Pubkey, signature::Keypair, + signer::Signer, }; use test_kit::init_logger; use tracing::*; diff --git a/test-integration/test-tools/src/lib.rs b/test-integration/test-tools/src/lib.rs index 63fdbddb5..6e8612547 100644 --- a/test-integration/test-tools/src/lib.rs +++ b/test-integration/test-tools/src/lib.rs @@ -3,12 +3,12 @@ pub mod dlp_interface; mod integration_test_context; pub mod loaded_accounts; mod run_test; -pub mod toml_to_args; -pub mod validator; pub mod scenario_setup; pub mod scheduled_commits; pub mod tmpdir; +pub mod toml_to_args; pub mod transactions; +pub mod validator; pub mod workspace_paths; pub use color_backtrace; diff --git a/test-integration/test-tools/src/scenario_setup.rs b/test-integration/test-tools/src/scenario_setup.rs index afcc0c378..4b07beb77 100644 --- a/test-integration/test-tools/src/scenario_setup.rs +++ b/test-integration/test-tools/src/scenario_setup.rs @@ -1,4 +1,5 @@ -use crate::{expect, validator::cleanup, IntegrationTestContext}; +use std::process::Child; + use cleanass::{assert, assert_eq}; use dlp; use program_flexi_counter::{ @@ -13,9 +14,10 @@ use solana_sdk::{ signature::{Keypair, Signature, Signer}, transaction::Transaction, }; -use std::process::Child; use tracing::debug; +use crate::{expect, validator::cleanup, IntegrationTestContext}; + // ----------------- // Transactions and Account Updates // ----------------- From a8833d66c3a01c1fb6a8bec1c2cfc509899c18bc Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Thu, 5 Feb 2026 11:43:33 +0800 Subject: [PATCH 21/25] chore: fix svm reference --- Cargo.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b5cad1ebc..2804d9a2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -216,9 +216,8 @@ spl-token = "7.0" spl-token-2022 = "7.0" [workspace.dependencies.solana-svm] -path = "../magicblock-svm" -# git = "https://github.com/magicblock-labs/magicblock-svm.git" -# rev = "3e9456ec4" +git = "https://github.com/magicblock-labs/magicblock-svm.git" +rev = "569cb82" features = ["dev-context-only-utils"] [workspace.dependencies.rocksdb] From c2ce3a3dafc71500a5b689ed2dad5cb75173bb95 Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Thu, 5 Feb 2026 12:18:50 +0800 Subject: [PATCH 22/25] chore: update cargo locks --- Cargo.lock | 6 +----- test-integration/Cargo.lock | 13 +++++++------ 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a6372deaa..c1295b181 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7094,6 +7094,7 @@ dependencies = [ [[package]] name = "solana-svm" version = "2.2.1" +source = "git+https://github.com/magicblock-labs/magicblock-svm.git?rev=569cb82#569cb82aa0081cd8433147bd54d942f08be57715" dependencies = [ "ahash 0.8.12", "log", @@ -9599,8 +9600,3 @@ dependencies = [ "cc", "pkg-config", ] - -[[patch.unused]] -name = "solana-svm" -version = "2.2.1" -source = "git+https://github.com/magicblock-labs/magicblock-svm.git?rev=569cb82#569cb82aa0081cd8433147bd54d942f08be57715" diff --git a/test-integration/Cargo.lock b/test-integration/Cargo.lock index 6bbcc9dc0..3fb68bba3 100644 --- a/test-integration/Cargo.lock +++ b/test-integration/Cargo.lock @@ -3714,7 +3714,7 @@ dependencies = [ "solana-pubkey", "solana-rent-collector", "solana-sdk-ids", - "solana-svm 2.2.1", + "solana-svm 2.2.1 (git+https://github.com/magicblock-labs/magicblock-svm.git?rev=569cb82)", "solana-svm-transaction", "solana-system-program", "solana-transaction", @@ -8615,11 +8615,13 @@ dependencies = [ [[package]] name = "solana-svm" version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850baf834aba4a94a7558fa6cf6ca93fad284abf0363dec5fb9cab173a11fc4" dependencies = [ "ahash 0.8.12", + "itertools 0.12.1", "log", "percentage", - "qualifier_attr", "serde", "serde_derive", "solana-account", @@ -8644,7 +8646,6 @@ dependencies = [ "solana-pubkey", "solana-rent", "solana-rent-debits", - "solana-reserved-account-keys", "solana-sdk", "solana-sdk-ids", "solana-svm-rent-collector", @@ -8659,13 +8660,12 @@ dependencies = [ [[package]] name = "solana-svm" version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850baf834aba4a94a7558fa6cf6ca93fad284abf0363dec5fb9cab173a11fc4" +source = "git+https://github.com/magicblock-labs/magicblock-svm.git?rev=569cb82#569cb82aa0081cd8433147bd54d942f08be57715" dependencies = [ "ahash 0.8.12", - "itertools 0.12.1", "log", "percentage", + "qualifier_attr", "serde", "serde_derive", "solana-account", @@ -8690,6 +8690,7 @@ dependencies = [ "solana-pubkey", "solana-rent", "solana-rent-debits", + "solana-reserved-account-keys", "solana-sdk", "solana-sdk-ids", "solana-svm-rent-collector", From af2c55feccc0ccfaf0cd215a722e09276d9ed509 Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Thu, 5 Feb 2026 15:06:27 +0800 Subject: [PATCH 23/25] test: Replace fixed sleep with polling loop in lifecycle_modes_cloning Amp-Thread-ID: https://ampcode.com/threads/T-019c2c9e-49ac-73f9-9fed-f92965513f3e Co-authored-by: Amp --- .../test-config/tests/lifecycle_modes_cloning.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/test-integration/test-config/tests/lifecycle_modes_cloning.rs b/test-integration/test-config/tests/lifecycle_modes_cloning.rs index eafc2ad40..c8e492f55 100644 --- a/test-integration/test-config/tests/lifecycle_modes_cloning.rs +++ b/test-integration/test-config/tests/lifecycle_modes_cloning.rs @@ -197,9 +197,16 @@ fn run_lifecycle_cloning_test_no_cleanup( debug!("✅ Airdropped 1 SOL to test account on chain: {}", pubkey); } - // Attempt to fetch the account from ephemeral validator - std::thread::sleep(std::time::Duration::from_millis(500)); - let cloned_account = ctx.fetch_ephem_account(pubkey); + // Poll for the account from ephemeral validator with timeout + let max_wait = std::time::Duration::from_secs(5); + let poll_interval = std::time::Duration::from_millis(50); + let start = std::time::Instant::now(); + let mut cloned_account = ctx.fetch_ephem_account(pubkey); + + while cloned_account.is_err() && start.elapsed() < max_wait { + std::thread::sleep(poll_interval); + cloned_account = ctx.fetch_ephem_account(pubkey); + } if expect_clone { assert_eq!( From c3e660c05b05094aa669345431d487b6b8f2774d Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Thu, 5 Feb 2026 15:07:36 +0800 Subject: [PATCH 24/25] chore: fix out of diff issue --- test-integration/test-tools/src/integration_test_context.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-integration/test-tools/src/integration_test_context.rs b/test-integration/test-tools/src/integration_test_context.rs index 115b5c0d0..14cee6dac 100644 --- a/test-integration/test-tools/src/integration_test_context.rs +++ b/test-integration/test-tools/src/integration_test_context.rs @@ -517,7 +517,7 @@ impl IntegrationTestContext { pubkey, lamports, self.commitment, - Some(&(|sig| self.dump_chain_logs(sig))), + Some(&(|sig| self.dump_ephemeral_logs(sig))), ) }) } From 61b6cffea232d97c6c93b0b6a8e33524ad1e5901 Mon Sep 17 00:00:00 2001 From: Thorsten Lorenz Date: Thu, 5 Feb 2026 18:31:41 +0800 Subject: [PATCH 25/25] chore: dump logs on tx failure --- test-integration/test-tools/src/scenario_setup.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test-integration/test-tools/src/scenario_setup.rs b/test-integration/test-tools/src/scenario_setup.rs index 4b07beb77..b81832724 100644 --- a/test-integration/test-tools/src/scenario_setup.rs +++ b/test-integration/test-tools/src/scenario_setup.rs @@ -185,6 +185,9 @@ pub fn confirm_tx_with_payer_ephem( ctx.send_and_confirm_transaction_ephem(&mut tx, signers), validator ); + if !confirmed { + ctx.dump_ephemeral_logs(sig) + } assert!(confirmed, cleanup(validator), "Should confirm transaction",); sig } @@ -203,6 +206,9 @@ pub fn confirm_tx_with_payer_chain( ctx.send_and_confirm_transaction_chain(&mut tx, signers), validator ); + if !confirmed { + ctx.dump_chain_logs(sig) + } assert!(confirmed, cleanup(validator), "Should confirm transaction"); sig }