From acd16219119169e71f72e0c70f5a56d9d92e0f44 Mon Sep 17 00:00:00 2001 From: taco-paco Date: Mon, 19 Jan 2026 18:48:57 +0700 Subject: [PATCH 01/36] wip: simplified version of v2 intents reusing old types --- magicblock-magic-program-api/src/args.rs | 7 + .../src/instruction.rs | 13 +- .../src/magic_scheduled_base_intent.rs | 119 +++++++++- .../magicblock/src/magicblock_processor.rs | 6 +- .../src/schedule_transactions/mod.rs | 2 + .../process_schedule_intent_bundle.rs | 211 ++++++++++++++++++ 6 files changed, 352 insertions(+), 6 deletions(-) create mode 100644 programs/magicblock/src/schedule_transactions/process_schedule_intent_bundle.rs diff --git a/magicblock-magic-program-api/src/args.rs b/magicblock-magic-program-api/src/args.rs index a8b9c1988..97a8e8e7d 100644 --- a/magicblock-magic-program-api/src/args.rs +++ b/magicblock-magic-program-api/src/args.rs @@ -87,6 +87,13 @@ pub enum MagicBaseIntentArgs { CommitAndUndelegate(CommitAndUndelegateArgs), } +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] +pub struct MagicIntentBundleArgs { + pub commit: Option, + pub commit_and_undelegate: Option, + pub standalone_actions: Vec, +} + /// A compact account meta used for base-layer actions. /// /// Unlike `solana_instruction::AccountMeta`, this type **does not** carry an diff --git a/magicblock-magic-program-api/src/instruction.rs b/magicblock-magic-program-api/src/instruction.rs index a4050e4e7..8b4976ed6 100644 --- a/magicblock-magic-program-api/src/instruction.rs +++ b/magicblock-magic-program-api/src/instruction.rs @@ -3,7 +3,9 @@ use std::collections::HashMap; use serde::{Deserialize, Serialize}; use solana_program::pubkey::Pubkey; -use crate::args::{MagicBaseIntentArgs, ScheduleTaskArgs}; +use crate::args::{ + MagicBaseIntentArgs, MagicIntentBundleArgs, ScheduleTaskArgs, +}; #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] pub enum MagicBlockInstruction { @@ -71,8 +73,13 @@ pub enum MagicBlockInstruction { /// as part of the [MagicBlockInstruction::ScheduleCommit] instruction. /// Args: (intent_id, bump) - bump is needed in order to guarantee unique transactions ScheduledCommitSent((u64, u64)), + + /// TODO(edwin): ScheduleBaseIntent(MagicBaseIntentArgs), + /// TODO(edwin): + ScheduleIntentBundle(MagicIntentBundleArgs), + /// Schedule a new task for execution /// /// # Account references @@ -86,9 +93,7 @@ pub enum MagicBlockInstruction { /// # Account references /// - **0.** `[WRITE, SIGNER]` Task authority /// - **1.** `[WRITE]` Task context account - CancelTask { - task_id: i64, - }, + CancelTask { task_id: i64 }, /// Disables the executable check, needed to modify the data of a program /// in preparation to deploying it via LoaderV4 and to modify its authority. diff --git a/programs/magicblock/src/magic_scheduled_base_intent.rs b/programs/magicblock/src/magic_scheduled_base_intent.rs index 4f2b8cc18..4a219abd0 100644 --- a/programs/magicblock/src/magic_scheduled_base_intent.rs +++ b/programs/magicblock/src/magic_scheduled_base_intent.rs @@ -8,7 +8,8 @@ use magicblock_core::{ }; use magicblock_magic_program_api::args::{ ActionArgs, BaseActionArgs, CommitAndUndelegateArgs, CommitTypeArgs, - MagicBaseIntentArgs, ShortAccountMeta, UndelegateTypeArgs, + MagicBaseIntentArgs, MagicIntentBundleArgs, ShortAccountMeta, + UndelegateTypeArgs, }; use serde::{Deserialize, Serialize}; use solana_account::{Account, AccountSharedData, ReadableAccount}; @@ -121,6 +122,122 @@ pub enum MagicBaseIntent { CommitAndUndelegate(CommitAndUndelegate), } +// Bundle of BaseIntents +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct MagicIntentBundle { + pub commit: Option, + pub commit_and_undelegate: Option, + pub standalone_actions: Vec, +} + +impl MagicIntentBundle { + pub fn try_from_args( + args: MagicIntentBundleArgs, + context: &ConstructionContext<'_, '_>, + ) -> Result { + Self::validate(&args)?; + + let commit = args + .commit + .map(|value| CommitType::try_from_args(value, context)) + .transpose()?; + let commit_and_undelegate = args + .commit_and_undelegate + .map(|value| CommitAndUndelegate::try_from_args(value, context)) + .transpose()?; + let actions = args + .standalone_actions + .into_iter() + .map(|args| BaseAction::try_from_args(args, context)) + .collect::, InstructionError>>()?; + + Ok(Self { + commit, + commit_and_undelegate, + standalone_actions: actions, + }) + } + + /// Cross intent validation: + /// 1. Set of committed accounts shall not overlap with + /// set of undelegated accounts + /// 2. None for now :) + fn validate(args: &MagicIntentBundleArgs) -> Result<(), InstructionError> { + let committed_set: Option> = + args.commit.as_ref().map(|el| { + el.committed_accounts_indices().iter().copied().collect() + }); + let Some(committed_set) = committed_set else { + return Ok(()); + }; + + args.commit_and_undelegate + .as_ref() + .and_then(|el| { + let has_cross_reference = el + .committed_accounts_indices() + .iter() + .any(|ind| committed_set.contains(ind)); + if has_cross_reference { + Some(Ok(())) + } else { + // TODO(edwin): add msg here? + Some(Err(InstructionError::InvalidInstructionData)) + } + }) + .unwrap_or(Ok(())) + } + + pub fn is_undelegate(&self) -> bool { + self.commit_and_undelegate.is_some() + } + + /// Returns all the accounts that will be committed, + /// including the ones that will be undelegated as well + pub fn get_committed_accounts(&self) -> Option> { + let committed = self + .commit + .as_ref() + .map(|el| el.get_committed_accounts().to_owned()); + + let undelegated = self + .commit_and_undelegate + .as_ref() + .map(|el| el.get_committed_accounts().to_owned()); + + match (committed, undelegated) { + (None, None) => None, + (Some(mut a), Some(b)) => { + a.extend(b); + Some(a) + } + (Some(a), None) | (None, Some(a)) => Some(a), + } + } + + pub fn get_committed_pubkeys(&self) -> Option> { + self.get_committed_accounts().map(|accounts| { + accounts.iter().map(|account| account.pubkey).collect() + }) + } + + pub fn is_empty(&self) -> bool { + let has_committed = self + .commit + .as_ref() + .map(|el| !el.is_empty()) + .unwrap_or(false); + let has_committed_and_undelegated = self + .commit_and_undelegate + .as_ref() + .map(|el| !el.is_empty()) + .unwrap_or(false); + let has_actions = !self.standalone_actions.is_empty(); + + has_committed || has_committed_and_undelegated || has_actions + } +} + impl MagicBaseIntent { pub fn try_from_args( args: MagicBaseIntentArgs, diff --git a/programs/magicblock/src/magicblock_processor.rs b/programs/magicblock/src/magicblock_processor.rs index a555da4dd..55e77df17 100644 --- a/programs/magicblock/src/magicblock_processor.rs +++ b/programs/magicblock/src/magicblock_processor.rs @@ -7,7 +7,8 @@ use crate::{ schedule_task::{process_cancel_task, process_schedule_task}, schedule_transactions::{ process_accept_scheduled_commits, process_schedule_base_intent, - process_schedule_commit, ProcessScheduleCommitOptions, + process_schedule_commit, process_schedule_intent_bundle, + ProcessScheduleCommitOptions, }, toggle_executable_check::process_toggle_executable_check, }; @@ -71,6 +72,9 @@ declare_process_instruction!( ScheduleBaseIntent(args) => { process_schedule_base_intent(signers, invoke_context, args) } + ScheduleIntentBundle(args) => { + process_schedule_intent_bundle(signers, invoke_context, args) + } ScheduleTask(args) => { process_schedule_task(signers, invoke_context, args) } diff --git a/programs/magicblock/src/schedule_transactions/mod.rs b/programs/magicblock/src/schedule_transactions/mod.rs index 6d409236c..2f4cce23e 100644 --- a/programs/magicblock/src/schedule_transactions/mod.rs +++ b/programs/magicblock/src/schedule_transactions/mod.rs @@ -3,6 +3,7 @@ mod process_schedule_base_intent; mod process_schedule_commit; #[cfg(test)] mod process_schedule_commit_tests; +mod process_schedule_intent_bundle; mod process_scheduled_commit_sent; pub(crate) mod transaction_scheduler; @@ -10,6 +11,7 @@ use magicblock_magic_program_api::MAGIC_CONTEXT_PUBKEY; pub(crate) use process_accept_scheduled_commits::*; pub(crate) use process_schedule_base_intent::*; pub(crate) use process_schedule_commit::*; +pub(crate) use process_schedule_intent_bundle::process_schedule_intent_bundle; pub use process_scheduled_commit_sent::{ process_scheduled_commit_sent, register_scheduled_commit_sent, SentCommit, }; diff --git a/programs/magicblock/src/schedule_transactions/process_schedule_intent_bundle.rs b/programs/magicblock/src/schedule_transactions/process_schedule_intent_bundle.rs new file mode 100644 index 000000000..9a6a75a5f --- /dev/null +++ b/programs/magicblock/src/schedule_transactions/process_schedule_intent_bundle.rs @@ -0,0 +1,211 @@ +use std::collections::HashSet; + +use magicblock_magic_program_api::args::{ + MagicBaseIntentArgs, MagicIntentBundleArgs, +}; +use solana_account::state_traits::StateMut; +use solana_instruction::error::InstructionError; +use solana_log_collector::ic_msg; +use solana_program_runtime::invoke_context::InvokeContext; +use solana_pubkey::Pubkey; +use solana_transaction_context::TransactionContext; + +use crate::{ + magic_scheduled_base_intent::{ + CommitType, ConstructionContext, ScheduledBaseIntent, + }, + schedule_transactions::check_magic_context_id, + utils::{ + account_actions::mark_account_as_undelegated, + accounts::{ + get_instruction_account_with_idx, get_instruction_pubkey_with_idx, + }, + }, + MagicContext, +}; + +const PAYER_IDX: u16 = 0; +const MAGIC_CONTEXT_IDX: u16 = PAYER_IDX + 1; +const ACTION_ACCOUNTS_OFFSET: usize = MAGIC_CONTEXT_IDX as usize + 1; + +pub(crate) fn process_schedule_intent_bundle( + signers: HashSet, + invoke_context: &mut InvokeContext, + args: MagicIntentBundleArgs, +) -> Result<(), InstructionError> { + check_magic_context_id(invoke_context, MAGIC_CONTEXT_IDX)?; + + let transaction_context = &invoke_context.transaction_context.clone(); + let ix_ctx = transaction_context.get_current_instruction_context()?; + + // Assert MagicBlock program + ix_ctx + .find_index_of_program_account(transaction_context, &crate::id()) + .ok_or_else(|| { + ic_msg!( + invoke_context, + "ScheduleAction ERR: Magic program account not found" + ); + InstructionError::UnsupportedProgramId + })?; + + // Assert enough accounts + let ix_accs_len = ix_ctx.get_number_of_instruction_accounts() as usize; + if ix_accs_len <= ACTION_ACCOUNTS_OFFSET { + ic_msg!( + invoke_context, + "ScheduleCommit ERR: not enough accounts to schedule commit ({}), need payer, signing program an account for each pubkey to be committed", + ix_accs_len + ); + return Err(InstructionError::NotEnoughAccountKeys); + } + + // Assert Payer is signer + let payer_pubkey = + get_instruction_pubkey_with_idx(transaction_context, PAYER_IDX)?; + if !signers.contains(payer_pubkey) { + ic_msg!( + invoke_context, + "ScheduleCommit ERR: payer pubkey {} not in signers", + payer_pubkey + ); + return Err(InstructionError::MissingRequiredSignature); + } + + // + // Get the program_id of the parent instruction that invoked this one via CPI + // + + // We cannot easily simulate the transaction being invoked via CPI + // from the owning program during unit tests + // Instead the integration tests ensure that this works as expected + let parent_program_id = + get_parent_program_id(transaction_context, invoke_context)?; + + // It appears that in builtin programs `Clock::get` doesn't work as expected, thus + // we have to get it directly from the sysvar cache. + let clock = + invoke_context + .get_sysvar_cache() + .get_clock() + .map_err(|err| { + ic_msg!(invoke_context, "Failed to get clock sysvar: {}", err); + InstructionError::UnsupportedSysvar + })?; + + // NOTE: this is only protected by all the above checks however if the + // instruction fails for other reasons detected afterward then the commit + // stays scheduled + let context_acc = get_instruction_account_with_idx( + transaction_context, + MAGIC_CONTEXT_IDX, + )?; + let context_data = &mut context_acc.borrow_mut(); + let mut context = + MagicContext::deserialize(context_data).map_err(|err| { + ic_msg!( + invoke_context, + "Failed to deserialize MagicContext: {}", + err + ); + InstructionError::GenericError + })?; + + // Get next intent id + let intent_id = context.next_intent_id(); + + // Determine id and slot + let construction_context = ConstructionContext::new( + parent_program_id, + &signers, + transaction_context, + invoke_context, + ); + + let undelegated_accounts_ref = + if let MagicBaseIntentArgs::CommitAndUndelegate(ref value) = args { + Some(CommitType::extract_commit_accounts( + value.committed_accounts_indices(), + construction_context.transaction_context, + )?) + } else { + None + }; + let scheduled_intent = ScheduledBaseIntent::try_new( + args, + intent_id, + clock.slot, + payer_pubkey, + &construction_context, + )?; + + let mut undelegated_pubkeys = vec![]; + if let Some(undelegated_accounts_ref) = undelegated_accounts_ref.as_ref() { + // Change owner to dlp and set undelegating flag + // Once account is undelegated we need to make it immutable in our validator. + for (pubkey, account_ref) in undelegated_accounts_ref.iter() { + undelegated_pubkeys.push(pubkey.to_string()); + mark_account_as_undelegated(account_ref); + } + } + if !undelegated_pubkeys.is_empty() { + ic_msg!( + invoke_context, + "Scheduling undelegation for accounts: {}", + undelegated_pubkeys.join(", ") + ); + } + + let action_sent_signature = + scheduled_intent.action_sent_transaction.signatures[0]; + + context.add_scheduled_action(scheduled_intent); + context_data.set_state(&context)?; + + ic_msg!(invoke_context, "Scheduled commit with ID: {}", intent_id); + ic_msg!( + invoke_context, + "ScheduledCommitSent signature: {}", + action_sent_signature, + ); + + Ok(()) +} + +#[cfg(not(test))] +fn get_parent_program_id( + transaction_context: &TransactionContext, + invoke_context: &mut InvokeContext, +) -> Result, InstructionError> { + let frames = crate::utils::instruction_context_frames::InstructionContextFrames::try_from(transaction_context)?; + let parent_program_id = + frames.find_program_id_of_parent_of_current_instruction(); + + ic_msg!( + invoke_context, + "ScheduleCommit: parent program id: {}", + parent_program_id + .map_or_else(|| "None".to_string(), |id| id.to_string()) + ); + + Ok(parent_program_id.copied()) +} + +#[cfg(test)] +fn get_parent_program_id( + transaction_context: &TransactionContext, + _: &mut InvokeContext, +) -> Result, InstructionError> { + use solana_account::ReadableAccount; + + use crate::utils::accounts::get_instruction_account_with_idx; + + let first_committee_owner = *get_instruction_account_with_idx( + transaction_context, + ACTION_ACCOUNTS_OFFSET as u16, + )? + .borrow() + .owner(); + + Ok(Some(first_committee_owner)) +} From 4161ecb93c149c4cb72b2651a5c2c2fa70b62c54 Mon Sep 17 00:00:00 2001 From: taco-paco Date: Mon, 19 Jan 2026 19:26:28 +0700 Subject: [PATCH 02/36] feat: integrate new intent version in magic program --- magicblock-magic-program-api/src/args.rs | 19 +- .../src/magic_scheduled_base_intent.rs | 37 +++- .../magicblock/src/magicblock_processor.rs | 8 +- .../src/schedule_transactions/mod.rs | 2 - .../process_schedule_base_intent.rs | 209 ------------------ .../process_schedule_commit.rs | 4 +- .../process_schedule_intent_bundle.rs | 9 +- 7 files changed, 63 insertions(+), 225 deletions(-) delete mode 100644 programs/magicblock/src/schedule_transactions/process_schedule_base_intent.rs diff --git a/magicblock-magic-program-api/src/args.rs b/magicblock-magic-program-api/src/args.rs index 97a8e8e7d..613029f0d 100644 --- a/magicblock-magic-program-api/src/args.rs +++ b/magicblock-magic-program-api/src/args.rs @@ -87,13 +87,30 @@ pub enum MagicBaseIntentArgs { CommitAndUndelegate(CommitAndUndelegateArgs), } -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] +#[derive(Clone, Default, Serialize, Deserialize, Debug, PartialEq, Eq)] pub struct MagicIntentBundleArgs { pub commit: Option, pub commit_and_undelegate: Option, pub standalone_actions: Vec, } +impl From for MagicIntentBundleArgs { + fn from(value: MagicBaseIntentArgs) -> Self { + let mut this = Self::default(); + match value { + MagicBaseIntentArgs::BaseActions(value) => { + this.standalone_actions.extend(value) + } + MagicBaseIntentArgs::Commit(value) => this.commit = Some(value), + MagicBaseIntentArgs::CommitAndUndelegate(value) => { + this.commit_and_undelegate = Some(value) + } + } + + this + } +} + /// A compact account meta used for base-layer actions. /// /// Unlike `solana_instruction::AccountMeta`, this type **does not** carry an diff --git a/programs/magicblock/src/magic_scheduled_base_intent.rs b/programs/magicblock/src/magic_scheduled_base_intent.rs index 4a219abd0..5b99f6ec6 100644 --- a/programs/magicblock/src/magic_scheduled_base_intent.rs +++ b/programs/magicblock/src/magic_scheduled_base_intent.rs @@ -63,19 +63,20 @@ pub struct ScheduledBaseIntent { pub blockhash: Hash, pub action_sent_transaction: Transaction, pub payer: Pubkey, - // Scheduled action - pub base_intent: MagicBaseIntent, + /// Scheduled intent bundle + // TODO(edwin): rename + pub base_intent: MagicIntentBundle, } impl ScheduledBaseIntent { pub fn try_new( - args: MagicBaseIntentArgs, + args: MagicIntentBundleArgs, commit_id: u64, slot: Slot, payer_pubkey: &Pubkey, context: &ConstructionContext<'_, '_>, ) -> Result { - let action = MagicBaseIntent::try_from_args(args, context)?; + let intent = MagicIntentBundle::try_from_args(args, context)?; let blockhash = context.invoke_context.environment_config.blockhash; let action_sent_transaction = @@ -86,7 +87,7 @@ impl ScheduledBaseIntent { blockhash, payer: *payer_pubkey, action_sent_transaction, - base_intent: action, + base_intent: intent, }) } @@ -94,6 +95,15 @@ impl ScheduledBaseIntent { self.base_intent.get_committed_accounts() } + pub fn get_undelegated_accounts(&self) -> Option<&Vec> { + Some( + self.base_intent + .commit_and_undelegate + .as_ref()? + .get_committed_accounts(), + ) + } + pub fn get_committed_accounts_mut( &mut self, ) -> Option<&mut Vec> { @@ -130,6 +140,23 @@ pub struct MagicIntentBundle { pub standalone_actions: Vec, } +impl From for MagicIntentBundle { + fn from(value: MagicBaseIntent) -> Self { + let mut this = Self::default(); + match value { + MagicBaseIntent::BaseActions(value) => { + this.standalone_actions.extend(value) + } + MagicBaseIntent::Commit(value) => this.commit = Some(value), + MagicBaseIntent::CommitAndUndelegate(value) => { + this.commit_and_undelegate = Some(value) + } + } + + this + } +} + impl MagicIntentBundle { pub fn try_from_args( args: MagicIntentBundleArgs, diff --git a/programs/magicblock/src/magicblock_processor.rs b/programs/magicblock/src/magicblock_processor.rs index 55e77df17..57cdd0c1f 100644 --- a/programs/magicblock/src/magicblock_processor.rs +++ b/programs/magicblock/src/magicblock_processor.rs @@ -69,9 +69,11 @@ declare_process_instruction!( transaction_context, id, ), - ScheduleBaseIntent(args) => { - process_schedule_base_intent(signers, invoke_context, args) - } + ScheduleBaseIntent(args) => process_schedule_intent_bundle( + signers, + invoke_context, + args.into(), + ), ScheduleIntentBundle(args) => { process_schedule_intent_bundle(signers, invoke_context, args) } diff --git a/programs/magicblock/src/schedule_transactions/mod.rs b/programs/magicblock/src/schedule_transactions/mod.rs index 2f4cce23e..942a5991c 100644 --- a/programs/magicblock/src/schedule_transactions/mod.rs +++ b/programs/magicblock/src/schedule_transactions/mod.rs @@ -1,5 +1,4 @@ mod process_accept_scheduled_commits; -mod process_schedule_base_intent; mod process_schedule_commit; #[cfg(test)] mod process_schedule_commit_tests; @@ -9,7 +8,6 @@ pub(crate) mod transaction_scheduler; use magicblock_magic_program_api::MAGIC_CONTEXT_PUBKEY; pub(crate) use process_accept_scheduled_commits::*; -pub(crate) use process_schedule_base_intent::*; pub(crate) use process_schedule_commit::*; pub(crate) use process_schedule_intent_bundle::process_schedule_intent_bundle; pub use process_scheduled_commit_sent::{ diff --git a/programs/magicblock/src/schedule_transactions/process_schedule_base_intent.rs b/programs/magicblock/src/schedule_transactions/process_schedule_base_intent.rs deleted file mode 100644 index 159d456ac..000000000 --- a/programs/magicblock/src/schedule_transactions/process_schedule_base_intent.rs +++ /dev/null @@ -1,209 +0,0 @@ -use std::collections::HashSet; - -use magicblock_magic_program_api::args::MagicBaseIntentArgs; -use solana_account::state_traits::StateMut; -use solana_instruction::error::InstructionError; -use solana_log_collector::ic_msg; -use solana_program_runtime::invoke_context::InvokeContext; -use solana_pubkey::Pubkey; -use solana_transaction_context::TransactionContext; - -use crate::{ - magic_scheduled_base_intent::{ - CommitType, ConstructionContext, ScheduledBaseIntent, - }, - schedule_transactions::check_magic_context_id, - utils::{ - account_actions::mark_account_as_undelegated, - accounts::{ - get_instruction_account_with_idx, get_instruction_pubkey_with_idx, - }, - }, - MagicContext, -}; - -const PAYER_IDX: u16 = 0; -const MAGIC_CONTEXT_IDX: u16 = PAYER_IDX + 1; -const ACTION_ACCOUNTS_OFFSET: usize = MAGIC_CONTEXT_IDX as usize + 1; - -pub(crate) fn process_schedule_base_intent( - signers: HashSet, - invoke_context: &mut InvokeContext, - args: MagicBaseIntentArgs, -) -> Result<(), InstructionError> { - check_magic_context_id(invoke_context, MAGIC_CONTEXT_IDX)?; - - let transaction_context = &invoke_context.transaction_context.clone(); - let ix_ctx = transaction_context.get_current_instruction_context()?; - - // Assert MagicBlock program - ix_ctx - .find_index_of_program_account(transaction_context, &crate::id()) - .ok_or_else(|| { - ic_msg!( - invoke_context, - "ScheduleAction ERR: Magic program account not found" - ); - InstructionError::UnsupportedProgramId - })?; - - // Assert enough accounts - let ix_accs_len = ix_ctx.get_number_of_instruction_accounts() as usize; - if ix_accs_len <= ACTION_ACCOUNTS_OFFSET { - ic_msg!( - invoke_context, - "ScheduleCommit ERR: not enough accounts to schedule commit ({}), need payer, signing program an account for each pubkey to be committed", - ix_accs_len - ); - return Err(InstructionError::NotEnoughAccountKeys); - } - - // Assert Payer is signer - let payer_pubkey = - get_instruction_pubkey_with_idx(transaction_context, PAYER_IDX)?; - if !signers.contains(payer_pubkey) { - ic_msg!( - invoke_context, - "ScheduleCommit ERR: payer pubkey {} not in signers", - payer_pubkey - ); - return Err(InstructionError::MissingRequiredSignature); - } - - // - // Get the program_id of the parent instruction that invoked this one via CPI - // - - // We cannot easily simulate the transaction being invoked via CPI - // from the owning program during unit tests - // Instead the integration tests ensure that this works as expected - let parent_program_id = - get_parent_program_id(transaction_context, invoke_context)?; - - // It appears that in builtin programs `Clock::get` doesn't work as expected, thus - // we have to get it directly from the sysvar cache. - let clock = - invoke_context - .get_sysvar_cache() - .get_clock() - .map_err(|err| { - ic_msg!(invoke_context, "Failed to get clock sysvar: {}", err); - InstructionError::UnsupportedSysvar - })?; - - // NOTE: this is only protected by all the above checks however if the - // instruction fails for other reasons detected afterward then the commit - // stays scheduled - let context_acc = get_instruction_account_with_idx( - transaction_context, - MAGIC_CONTEXT_IDX, - )?; - let context_data = &mut context_acc.borrow_mut(); - let mut context = - MagicContext::deserialize(context_data).map_err(|err| { - ic_msg!( - invoke_context, - "Failed to deserialize MagicContext: {}", - err - ); - InstructionError::GenericError - })?; - - // Get next intent id - let intent_id = context.next_intent_id(); - - // Determine id and slot - let construction_context = ConstructionContext::new( - parent_program_id, - &signers, - transaction_context, - invoke_context, - ); - - let undelegated_accounts_ref = - if let MagicBaseIntentArgs::CommitAndUndelegate(ref value) = args { - Some(CommitType::extract_commit_accounts( - value.committed_accounts_indices(), - construction_context.transaction_context, - )?) - } else { - None - }; - let scheduled_intent = ScheduledBaseIntent::try_new( - args, - intent_id, - clock.slot, - payer_pubkey, - &construction_context, - )?; - - let mut undelegated_pubkeys = vec![]; - if let Some(undelegated_accounts_ref) = undelegated_accounts_ref.as_ref() { - // Change owner to dlp and set undelegating flag - // Once account is undelegated we need to make it immutable in our validator. - for (pubkey, account_ref) in undelegated_accounts_ref.iter() { - undelegated_pubkeys.push(pubkey.to_string()); - mark_account_as_undelegated(account_ref); - } - } - if !undelegated_pubkeys.is_empty() { - ic_msg!( - invoke_context, - "Scheduling undelegation for accounts: {}", - undelegated_pubkeys.join(", ") - ); - } - - let action_sent_signature = - scheduled_intent.action_sent_transaction.signatures[0]; - - context.add_scheduled_action(scheduled_intent); - context_data.set_state(&context)?; - - ic_msg!(invoke_context, "Scheduled commit with ID: {}", intent_id); - ic_msg!( - invoke_context, - "ScheduledCommitSent signature: {}", - action_sent_signature, - ); - - Ok(()) -} - -#[cfg(not(test))] -fn get_parent_program_id( - transaction_context: &TransactionContext, - invoke_context: &mut InvokeContext, -) -> Result, InstructionError> { - let frames = crate::utils::instruction_context_frames::InstructionContextFrames::try_from(transaction_context)?; - let parent_program_id = - frames.find_program_id_of_parent_of_current_instruction(); - - ic_msg!( - invoke_context, - "ScheduleCommit: parent program id: {}", - parent_program_id - .map_or_else(|| "None".to_string(), |id| id.to_string()) - ); - - Ok(parent_program_id.copied()) -} - -#[cfg(test)] -fn get_parent_program_id( - transaction_context: &TransactionContext, - _: &mut InvokeContext, -) -> Result, InstructionError> { - use solana_account::ReadableAccount; - - use crate::utils::accounts::get_instruction_account_with_idx; - - let first_committee_owner = *get_instruction_account_with_idx( - transaction_context, - ACTION_ACCOUNTS_OFFSET as u16, - )? - .borrow() - .owner(); - - Ok(Some(first_committee_owner)) -} diff --git a/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs b/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs index fd26282c9..77178f67d 100644 --- a/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs +++ b/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs @@ -258,7 +258,9 @@ pub(crate) fn process_schedule_commit( }) } else { MagicBaseIntent::Commit(CommitType::Standalone(committed_accounts)) - }; + } + .into(); + let scheduled_base_intent = ScheduledBaseIntent { id: intent_id, slot: clock.slot, diff --git a/programs/magicblock/src/schedule_transactions/process_schedule_intent_bundle.rs b/programs/magicblock/src/schedule_transactions/process_schedule_intent_bundle.rs index 9a6a75a5f..bbe804025 100644 --- a/programs/magicblock/src/schedule_transactions/process_schedule_intent_bundle.rs +++ b/programs/magicblock/src/schedule_transactions/process_schedule_intent_bundle.rs @@ -26,7 +26,7 @@ use crate::{ const PAYER_IDX: u16 = 0; const MAGIC_CONTEXT_IDX: u16 = PAYER_IDX + 1; -const ACTION_ACCOUNTS_OFFSET: usize = MAGIC_CONTEXT_IDX as usize + 1; +const ACCOUNTS_OFFSET: usize = MAGIC_CONTEXT_IDX as usize + 1; pub(crate) fn process_schedule_intent_bundle( signers: HashSet, @@ -51,7 +51,7 @@ pub(crate) fn process_schedule_intent_bundle( // Assert enough accounts let ix_accs_len = ix_ctx.get_number_of_instruction_accounts() as usize; - if ix_accs_len <= ACTION_ACCOUNTS_OFFSET { + if ix_accs_len <= ACCOUNTS_OFFSET { ic_msg!( invoke_context, "ScheduleCommit ERR: not enough accounts to schedule commit ({}), need payer, signing program an account for each pubkey to be committed", @@ -123,7 +123,7 @@ pub(crate) fn process_schedule_intent_bundle( ); let undelegated_accounts_ref = - if let MagicBaseIntentArgs::CommitAndUndelegate(ref value) = args { + if let Some(ref value) = args.commit_and_undelegate { Some(CommitType::extract_commit_accounts( value.committed_accounts_indices(), construction_context.transaction_context, @@ -131,6 +131,7 @@ pub(crate) fn process_schedule_intent_bundle( } else { None }; + let scheduled_intent = ScheduledBaseIntent::try_new( args, intent_id, @@ -202,7 +203,7 @@ fn get_parent_program_id( let first_committee_owner = *get_instruction_account_with_idx( transaction_context, - ACTION_ACCOUNTS_OFFSET as u16, + ACCOUNTS_OFFSET as u16, )? .borrow() .owner(); From c39d826db20bd7dea2c8a471f5793737bac896ef Mon Sep 17 00:00:00 2001 From: taco-paco Date: Mon, 19 Jan 2026 19:32:07 +0700 Subject: [PATCH 03/36] fix: magic-program compilation --- programs/magicblock/src/magic_scheduled_base_intent.rs | 10 ---------- programs/magicblock/src/magicblock_processor.rs | 5 ++--- .../process_schedule_intent_bundle.rs | 4 +--- 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/programs/magicblock/src/magic_scheduled_base_intent.rs b/programs/magicblock/src/magic_scheduled_base_intent.rs index 5b99f6ec6..b03ecbb35 100644 --- a/programs/magicblock/src/magic_scheduled_base_intent.rs +++ b/programs/magicblock/src/magic_scheduled_base_intent.rs @@ -91,10 +91,6 @@ impl ScheduledBaseIntent { }) } - pub fn get_committed_accounts(&self) -> Option<&Vec> { - self.base_intent.get_committed_accounts() - } - pub fn get_undelegated_accounts(&self) -> Option<&Vec> { Some( self.base_intent @@ -104,12 +100,6 @@ impl ScheduledBaseIntent { ) } - pub fn get_committed_accounts_mut( - &mut self, - ) -> Option<&mut Vec> { - self.base_intent.get_committed_accounts_mut() - } - pub fn get_committed_pubkeys(&self) -> Option> { self.base_intent.get_committed_pubkeys() } diff --git a/programs/magicblock/src/magicblock_processor.rs b/programs/magicblock/src/magicblock_processor.rs index 57cdd0c1f..30bbaec9a 100644 --- a/programs/magicblock/src/magicblock_processor.rs +++ b/programs/magicblock/src/magicblock_processor.rs @@ -6,9 +6,8 @@ use crate::{ process_scheduled_commit_sent, schedule_task::{process_cancel_task, process_schedule_task}, schedule_transactions::{ - process_accept_scheduled_commits, process_schedule_base_intent, - process_schedule_commit, process_schedule_intent_bundle, - ProcessScheduleCommitOptions, + process_accept_scheduled_commits, process_schedule_commit, + process_schedule_intent_bundle, ProcessScheduleCommitOptions, }, toggle_executable_check::process_toggle_executable_check, }; diff --git a/programs/magicblock/src/schedule_transactions/process_schedule_intent_bundle.rs b/programs/magicblock/src/schedule_transactions/process_schedule_intent_bundle.rs index bbe804025..5cf01e688 100644 --- a/programs/magicblock/src/schedule_transactions/process_schedule_intent_bundle.rs +++ b/programs/magicblock/src/schedule_transactions/process_schedule_intent_bundle.rs @@ -1,8 +1,6 @@ use std::collections::HashSet; -use magicblock_magic_program_api::args::{ - MagicBaseIntentArgs, MagicIntentBundleArgs, -}; +use magicblock_magic_program_api::args::MagicIntentBundleArgs; use solana_account::state_traits::StateMut; use solana_instruction::error::InstructionError; use solana_log_collector::ic_msg; From 423658f07058c46e154cd99cd275a97c1af0c15f Mon Sep 17 00:00:00 2001 From: taco-paco Date: Mon, 19 Jan 2026 19:35:38 +0700 Subject: [PATCH 04/36] refactor: rename ScheduledBaseIntent -> ScheduledIntentBundle --- .../src/scheduled_commits_processor.rs | 10 ++-- .../intent_execution_engine.rs | 4 +- .../intent_scheduler.rs | 22 ++++---- .../src/intent_executor/mod.rs | 10 ++-- .../src/persist/commit_persister.rs | 52 ++++++++++--------- .../src/tasks/task_builder.rs | 14 ++--- magicblock-committor-service/src/types.rs | 8 +-- programs/magicblock/src/magic_context.rs | 8 +-- .../src/magic_scheduled_base_intent.rs | 30 +++++------ .../process_schedule_commit.rs | 9 ++-- .../process_schedule_commit_tests.rs | 18 +++---- .../process_schedule_intent_bundle.rs | 6 +-- .../transaction_scheduler.rs | 14 ++--- .../tests/test_intent_executor.rs | 14 ++--- .../tests/test_ix_commit_local.rs | 20 +++---- 15 files changed, 122 insertions(+), 117 deletions(-) diff --git a/magicblock-accounts/src/scheduled_commits_processor.rs b/magicblock-accounts/src/scheduled_commits_processor.rs index e48912046..3609e96f8 100644 --- a/magicblock-accounts/src/scheduled_commits_processor.rs +++ b/magicblock-accounts/src/scheduled_commits_processor.rs @@ -24,7 +24,7 @@ use magicblock_core::{ link::transactions::TransactionSchedulerHandle, traits::AccountsBank, }; use magicblock_program::{ - magic_scheduled_base_intent::ScheduledBaseIntent, + magic_scheduled_base_intent::ScheduledIntentBundle, register_scheduled_commit_sent, SentCommit, TransactionScheduler, }; use solana_hash::Hash; @@ -89,7 +89,7 @@ impl ScheduledCommitsProcessorImpl { fn preprocess_intent( &self, - mut base_intent: ScheduledBaseIntent, + mut base_intent: ScheduledIntentBundle, ) -> (ScheduledBaseIntentWrapper, Vec) { let is_undelegate = base_intent.is_undelegate(); let Some(committed_accounts) = base_intent.get_committed_accounts_mut() @@ -389,7 +389,7 @@ struct ScheduledBaseIntentMeta { } impl ScheduledBaseIntentMeta { - fn new(intent: &ScheduledBaseIntent) -> Self { + fn new(intent: &ScheduledIntentBundle) -> Self { Self { slot: intent.slot, blockhash: intent.blockhash, @@ -397,7 +397,9 @@ impl ScheduledBaseIntentMeta { included_pubkeys: intent .get_committed_pubkeys() .unwrap_or_default(), - intent_sent_transaction: intent.action_sent_transaction.clone(), + intent_sent_transaction: intent + .intent_bundle_sent_transaction + .clone(), requested_undelegation: intent.is_undelegate(), } } diff --git a/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs b/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs index 5ef0f73c1..2fb7eee6f 100644 --- a/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs +++ b/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs @@ -370,7 +370,7 @@ mod tests { }; use async_trait::async_trait; - use magicblock_program::magic_scheduled_base_intent::ScheduledBaseIntent; + use magicblock_program::magic_scheduled_base_intent::ScheduledIntentBundle; use solana_pubkey::{pubkey, Pubkey}; use solana_signature::Signature; use solana_signer::SignerError; @@ -778,7 +778,7 @@ mod tests { impl IntentExecutor for MockIntentExecutor { async fn execute( &mut self, - _base_intent: ScheduledBaseIntent, + _base_intent: ScheduledIntentBundle, _persister: Option

, ) -> IntentExecutionResult { self.on_task_started(); diff --git a/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs b/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs index 184dceb0f..0cbbee0a6 100644 --- a/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs +++ b/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs @@ -1,6 +1,6 @@ use std::collections::{hash_map::Entry, HashMap, VecDeque}; -use magicblock_program::magic_scheduled_base_intent::ScheduledBaseIntent; +use magicblock_program::magic_scheduled_base_intent::ScheduledIntentBundle; use solana_pubkey::Pubkey; use thiserror::Error; use tracing::error; @@ -76,7 +76,7 @@ impl IntentScheduler { } } - /// Returns [`ScheduledBaseIntent`] if intent can be executed, + /// Returns [`ScheduledIntentBundle`] if intent can be executed, /// otherwise consumes it and enqueues pub fn schedule( &mut self, @@ -143,7 +143,7 @@ impl IntentScheduler { /// NOTE: this shall be called on executing intents to finilize their execution. pub fn complete( &mut self, - base_intent: &ScheduledBaseIntent, + base_intent: &ScheduledIntentBundle, ) -> IntentSchedulerResult<()> { // Release data for completed intent let intent_id = base_intent.id; @@ -592,7 +592,7 @@ mod edge_cases_test { fn test_intent_without_pubkeys() { let mut scheduler = IntentScheduler::new(); let mut msg = create_test_intent(1, &[], false); - msg.inner.base_intent = MagicBaseIntent::BaseActions(vec![]); + msg.inner.intent_bundle = MagicBaseIntent::BaseActions(vec![]); // Should execute immediately since it has no pubkeys assert!(scheduler.schedule(msg.clone()).is_some()); @@ -661,7 +661,7 @@ mod complete_error_test { assert!(scheduler.schedule(msg1.clone()).is_some()); msg1.inner - .base_intent + .intent_bundle .get_committed_accounts_mut() .unwrap() .push(CommittedAccount { @@ -733,7 +733,7 @@ pub(crate) fn create_test_intent( ) -> ScheduledBaseIntentWrapper { use magicblock_program::magic_scheduled_base_intent::{ CommitAndUndelegate, CommitType, CommittedAccount, MagicBaseIntent, - ScheduledBaseIntent, UndelegateType, + ScheduledIntentBundle, UndelegateType, }; use solana_account::Account; use solana_hash::Hash; @@ -741,13 +741,13 @@ pub(crate) fn create_test_intent( use crate::types::TriggerType; - let mut intent = ScheduledBaseIntent { + let mut intent = ScheduledIntentBundle { id, slot: 0, blockhash: Hash::default(), - action_sent_transaction: Transaction::default(), + intent_bundle_sent_transaction: Transaction::default(), payer: Pubkey::default(), - base_intent: MagicBaseIntent::BaseActions(vec![]), + intent_bundle: MagicBaseIntent::BaseActions(vec![]), }; // Only set pubkeys if provided @@ -763,13 +763,13 @@ pub(crate) fn create_test_intent( let commit_type = CommitType::Standalone(committed_accounts); if is_undelegate { - intent.base_intent = + intent.intent_bundle = MagicBaseIntent::CommitAndUndelegate(CommitAndUndelegate { commit_action: commit_type, undelegate_action: UndelegateType::Standalone, }) } else { - intent.base_intent = MagicBaseIntent::Commit(commit_type); + intent.intent_bundle = MagicBaseIntent::Commit(commit_type); } } diff --git a/magicblock-committor-service/src/intent_executor/mod.rs b/magicblock-committor-service/src/intent_executor/mod.rs index cd2e158eb..ddc15dbc8 100644 --- a/magicblock-committor-service/src/intent_executor/mod.rs +++ b/magicblock-committor-service/src/intent_executor/mod.rs @@ -10,7 +10,7 @@ use async_trait::async_trait; use futures_util::future::{join, try_join_all}; use magicblock_metrics::metrics; use magicblock_program::{ - magic_scheduled_base_intent::ScheduledBaseIntent, + magic_scheduled_base_intent::ScheduledIntentBundle, validator::validator_authority, }; use magicblock_rpc_client::{ @@ -96,7 +96,7 @@ pub trait IntentExecutor: Send + Sync + 'static { /// Returns `ExecutionOutput` or an `Error` async fn execute( &mut self, - base_intent: ScheduledBaseIntent, + base_intent: ScheduledIntentBundle, persister: Option

, ) -> IntentExecutionResult; @@ -139,7 +139,7 @@ where async fn execute_inner( &mut self, - base_intent: ScheduledBaseIntent, + base_intent: ScheduledIntentBundle, persister: &Option

, ) -> IntentExecutorResult { if base_intent.is_empty() { @@ -236,7 +236,7 @@ where /// Starting execution from single stage pub async fn single_stage_execution_flow( &mut self, - base_intent: ScheduledBaseIntent, + base_intent: ScheduledIntentBundle, transaction_strategy: TransactionStrategy, persister: &Option

, ) -> IntentExecutorResult { @@ -775,7 +775,7 @@ where /// Returns `ExecutionOutput` or an `Error` async fn execute( &mut self, - base_intent: ScheduledBaseIntent, + base_intent: ScheduledIntentBundle, persister: Option

, ) -> IntentExecutionResult { let message_id = base_intent.id; diff --git a/magicblock-committor-service/src/persist/commit_persister.rs b/magicblock-committor-service/src/persist/commit_persister.rs index 978ba1e85..06a662e15 100644 --- a/magicblock-committor-service/src/persist/commit_persister.rs +++ b/magicblock-committor-service/src/persist/commit_persister.rs @@ -3,7 +3,7 @@ use std::{ sync::{Arc, Mutex}, }; -use magicblock_program::magic_scheduled_base_intent::ScheduledBaseIntent; +use magicblock_program::magic_scheduled_base_intent::ScheduledIntentBundle; use solana_pubkey::Pubkey; use super::{ @@ -21,12 +21,12 @@ pub trait IntentPersister: Send + Sync + Clone + 'static { /// Starts persisting BaseIntents fn start_base_intents( &self, - base_intent: &[ScheduledBaseIntent], + base_intent: &[ScheduledIntentBundle], ) -> CommitPersistResult<()>; /// Starts persisting BaseIntent fn start_base_intent( &self, - base_intent: &ScheduledBaseIntent, + base_intent: &ScheduledIntentBundle, ) -> CommitPersistResult<()>; fn set_commit_id( &self, @@ -100,7 +100,7 @@ impl IntentPersisterImpl { } pub fn create_commit_rows( - base_intent: &ScheduledBaseIntent, + base_intent: &ScheduledIntentBundle, ) -> Vec { let Some(committed_accounts) = base_intent.get_committed_accounts() else { @@ -152,7 +152,7 @@ impl IntentPersisterImpl { impl IntentPersister for IntentPersisterImpl { fn start_base_intents( &self, - base_intents: &[ScheduledBaseIntent], + base_intents: &[ScheduledIntentBundle], ) -> CommitPersistResult<()> { let commit_rows = base_intents .iter() @@ -168,7 +168,7 @@ impl IntentPersister for IntentPersisterImpl { fn start_base_intent( &self, - base_intents: &ScheduledBaseIntent, + base_intents: &ScheduledIntentBundle, ) -> CommitPersistResult<()> { let commit_row = Self::create_commit_rows(base_intents); self.commits_db @@ -297,7 +297,7 @@ impl IntentPersister for IntentPersisterImpl { impl IntentPersister for Option { fn start_base_intents( &self, - base_intents: &[ScheduledBaseIntent], + base_intents: &[ScheduledIntentBundle], ) -> CommitPersistResult<()> { match self { Some(persister) => persister.start_base_intents(base_intents), @@ -307,7 +307,7 @@ impl IntentPersister for Option { fn start_base_intent( &self, - base_intents: &ScheduledBaseIntent, + base_intents: &ScheduledIntentBundle, ) -> CommitPersistResult<()> { match self { Some(persister) => persister.start_base_intent(base_intents), @@ -454,7 +454,7 @@ mod tests { (persister, temp_file) } - fn create_test_message(id: u64) -> ScheduledBaseIntent { + fn create_test_message(id: u64) -> ScheduledIntentBundle { let account1 = Account { lamports: 1000, owner: Pubkey::new_unique(), @@ -470,24 +470,26 @@ mod tests { rent_epoch: 0, }; - ScheduledBaseIntent { + ScheduledIntentBundle { id, slot: 100, blockhash: Hash::new_unique(), - action_sent_transaction: Transaction::default(), + intent_bundle_sent_transaction: Transaction::default(), payer: Pubkey::new_unique(), - base_intent: MagicBaseIntent::Commit(CommitType::Standalone(vec![ - CommittedAccount { - pubkey: Pubkey::new_unique(), - account: account1, - remote_slot: 1, - }, - CommittedAccount { - pubkey: Pubkey::new_unique(), - account: account2, - remote_slot: 2, - }, - ])), + intent_bundle: MagicBaseIntent::Commit(CommitType::Standalone( + vec![ + CommittedAccount { + pubkey: Pubkey::new_unique(), + account: account1, + remote_slot: 1, + }, + CommittedAccount { + pubkey: Pubkey::new_unique(), + account: account2, + remote_slot: 2, + }, + ], + )), } } @@ -654,8 +656,8 @@ mod tests { #[test] fn test_empty_accounts_not_persisted() { let (persister, _temp_file) = create_test_persister(); - let message = ScheduledBaseIntent { - base_intent: MagicBaseIntent::BaseActions(vec![]), // No committed accounts + let message = ScheduledIntentBundle { + intent_bundle: MagicBaseIntent::BaseActions(vec![]), // No committed accounts ..create_test_message(1) }; diff --git a/magicblock-committor-service/src/tasks/task_builder.rs b/magicblock-committor-service/src/tasks/task_builder.rs index 999f8b9c5..6791ea65c 100644 --- a/magicblock-committor-service/src/tasks/task_builder.rs +++ b/magicblock-committor-service/src/tasks/task_builder.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use async_trait::async_trait; use magicblock_program::magic_scheduled_base_intent::{ - CommitType, CommittedAccount, MagicBaseIntent, ScheduledBaseIntent, + CommitType, CommittedAccount, MagicBaseIntent, ScheduledIntentBundle, UndelegateType, }; use solana_account::Account; @@ -27,14 +27,14 @@ pub trait TasksBuilder { // Creates tasks for commit stage async fn commit_tasks( commit_id_fetcher: &Arc, - base_intent: &ScheduledBaseIntent, + base_intent: &ScheduledIntentBundle, persister: &Option

, ) -> TaskBuilderResult>>; // Create tasks for finalize stage async fn finalize_tasks( info_fetcher: &Arc, - base_intent: &ScheduledBaseIntent, + base_intent: &ScheduledIntentBundle, ) -> TaskBuilderResult>>; } @@ -87,10 +87,10 @@ impl TasksBuilder for TaskBuilderImpl { /// Returns [`Task`]s for Commit stage async fn commit_tasks( commit_id_fetcher: &Arc, - base_intent: &ScheduledBaseIntent, + base_intent: &ScheduledIntentBundle, persister: &Option

, ) -> TaskBuilderResult>> { - let (accounts, allow_undelegation) = match &base_intent.base_intent { + let (accounts, allow_undelegation) = match &base_intent.intent_bundle { MagicBaseIntent::BaseActions(actions) => { let tasks = actions .iter() @@ -179,7 +179,7 @@ impl TasksBuilder for TaskBuilderImpl { /// Returns [`Task`]s for Finalize stage async fn finalize_tasks( info_fetcher: &Arc, - base_intent: &ScheduledBaseIntent, + base_intent: &ScheduledIntentBundle, ) -> TaskBuilderResult>> { // Helper to create a finalize task fn finalize_task(account: &CommittedAccount) -> Box { @@ -229,7 +229,7 @@ impl TasksBuilder for TaskBuilderImpl { } } - match &base_intent.base_intent { + match &base_intent.intent_bundle { MagicBaseIntent::BaseActions(_) => Ok(vec![]), MagicBaseIntent::Commit(commit) => Ok(process_commit(commit)), MagicBaseIntent::CommitAndUndelegate(t) => { diff --git a/magicblock-committor-service/src/types.rs b/magicblock-committor-service/src/types.rs index d2076df46..c57d2ddbb 100644 --- a/magicblock-committor-service/src/types.rs +++ b/magicblock-committor-service/src/types.rs @@ -2,7 +2,7 @@ use std::ops::Deref; use magicblock_metrics::metrics; use magicblock_program::magic_scheduled_base_intent::{ - MagicBaseIntent, ScheduledBaseIntent, + MagicBaseIntent, ScheduledIntentBundle, }; // TODO: should be removed once cranks are supported @@ -15,13 +15,13 @@ pub enum TriggerType { #[derive(Clone, Debug, PartialEq, Eq)] pub struct ScheduledBaseIntentWrapper { - pub inner: ScheduledBaseIntent, + pub inner: ScheduledIntentBundle, pub trigger_type: TriggerType, } impl metrics::LabelValue for ScheduledBaseIntentWrapper { fn value(&self) -> &str { - match &self.inner.base_intent { + match &self.inner.intent_bundle { MagicBaseIntent::BaseActions(_) => "actions", MagicBaseIntent::Commit(_) => "commit", MagicBaseIntent::CommitAndUndelegate(_) => "commit_and_undelegate", @@ -30,7 +30,7 @@ impl metrics::LabelValue for ScheduledBaseIntentWrapper { } impl Deref for ScheduledBaseIntentWrapper { - type Target = ScheduledBaseIntent; + type Target = ScheduledIntentBundle; fn deref(&self) -> &Self::Target { &self.inner diff --git a/programs/magicblock/src/magic_context.rs b/programs/magicblock/src/magic_context.rs index f36893873..84a0e1619 100644 --- a/programs/magicblock/src/magic_context.rs +++ b/programs/magicblock/src/magic_context.rs @@ -4,12 +4,12 @@ use magicblock_magic_program_api::MAGIC_CONTEXT_SIZE; use serde::{Deserialize, Serialize}; use solana_account::{AccountSharedData, ReadableAccount}; -use crate::magic_scheduled_base_intent::ScheduledBaseIntent; +use crate::magic_scheduled_base_intent::ScheduledIntentBundle; #[derive(Debug, Default, Serialize, Deserialize)] pub struct MagicContext { pub intent_id: u64, - pub scheduled_base_intents: Vec, + pub scheduled_base_intents: Vec, } impl MagicContext { @@ -34,14 +34,14 @@ impl MagicContext { pub(crate) fn add_scheduled_action( &mut self, - base_intent: ScheduledBaseIntent, + base_intent: ScheduledIntentBundle, ) { self.scheduled_base_intents.push(base_intent); } pub(crate) fn take_scheduled_commits( &mut self, - ) -> Vec { + ) -> Vec { mem::take(&mut self.scheduled_base_intents) } diff --git a/programs/magicblock/src/magic_scheduled_base_intent.rs b/programs/magicblock/src/magic_scheduled_base_intent.rs index b03ecbb35..b375df021 100644 --- a/programs/magicblock/src/magic_scheduled_base_intent.rs +++ b/programs/magicblock/src/magic_scheduled_base_intent.rs @@ -57,43 +57,43 @@ impl<'a, 'ic> ConstructionContext<'a, 'ic> { /// Scheduled action to be executed on base layer #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct ScheduledBaseIntent { +pub struct ScheduledIntentBundle { pub id: u64, pub slot: Slot, pub blockhash: Hash, - pub action_sent_transaction: Transaction, + pub intent_bundle_sent_transaction: Transaction, pub payer: Pubkey, /// Scheduled intent bundle // TODO(edwin): rename - pub base_intent: MagicIntentBundle, + pub intent_bundle: MagicIntentBundle, } -impl ScheduledBaseIntent { +impl ScheduledIntentBundle { pub fn try_new( args: MagicIntentBundleArgs, commit_id: u64, slot: Slot, payer_pubkey: &Pubkey, context: &ConstructionContext<'_, '_>, - ) -> Result { - let intent = MagicIntentBundle::try_from_args(args, context)?; - + ) -> Result { + let intent_bundle = MagicIntentBundle::try_from_args(args, context)?; let blockhash = context.invoke_context.environment_config.blockhash; - let action_sent_transaction = + let intent_bundle_sent_transaction = InstructionUtils::scheduled_commit_sent(commit_id, blockhash); - Ok(ScheduledBaseIntent { + + Ok(ScheduledIntentBundle { id: commit_id, slot, blockhash, payer: *payer_pubkey, - action_sent_transaction, - base_intent: intent, + intent_bundle_sent_transaction, + intent_bundle, }) } pub fn get_undelegated_accounts(&self) -> Option<&Vec> { Some( - self.base_intent + self.intent_bundle .commit_and_undelegate .as_ref()? .get_committed_accounts(), @@ -101,15 +101,15 @@ impl ScheduledBaseIntent { } pub fn get_committed_pubkeys(&self) -> Option> { - self.base_intent.get_committed_pubkeys() + self.intent_bundle.get_committed_pubkeys() } pub fn is_undelegate(&self) -> bool { - self.base_intent.is_undelegate() + self.intent_bundle.is_undelegate() } pub fn is_empty(&self) -> bool { - self.base_intent.is_empty() + self.intent_bundle.is_empty() } } diff --git a/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs b/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs index 77178f67d..4308f6219 100644 --- a/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs +++ b/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs @@ -10,7 +10,8 @@ use solana_pubkey::Pubkey; use crate::{ magic_scheduled_base_intent::{ validate_commit_schedule_permissions, CommitAndUndelegate, CommitType, - CommittedAccount, MagicBaseIntent, ScheduledBaseIntent, UndelegateType, + CommittedAccount, MagicBaseIntent, ScheduledIntentBundle, + UndelegateType, }, schedule_transactions, utils::{ @@ -261,13 +262,13 @@ pub(crate) fn process_schedule_commit( } .into(); - let scheduled_base_intent = ScheduledBaseIntent { + let scheduled_base_intent = ScheduledIntentBundle { id: intent_id, slot: clock.slot, blockhash, - action_sent_transaction, + intent_bundle_sent_transaction: action_sent_transaction, payer: *payer_pubkey, - base_intent, + intent_bundle: base_intent, }; context.add_scheduled_action(scheduled_base_intent); diff --git a/programs/magicblock/src/schedule_transactions/process_schedule_commit_tests.rs b/programs/magicblock/src/schedule_transactions/process_schedule_commit_tests.rs index fe0d7d50e..350525c23 100644 --- a/programs/magicblock/src/schedule_transactions/process_schedule_commit_tests.rs +++ b/programs/magicblock/src/schedule_transactions/process_schedule_commit_tests.rs @@ -17,7 +17,7 @@ use solana_signer::Signer; use crate::{ magic_context::MagicContext, - magic_scheduled_base_intent::ScheduledBaseIntent, + magic_scheduled_base_intent::ScheduledIntentBundle, schedule_transactions::transaction_scheduler::TransactionScheduler, test_utils::{ensure_started_validator, process_instruction}, utils::DELEGATION_PROGRAM_ID, @@ -172,7 +172,7 @@ fn assert_accepted_actions( processed_accepted: &[AccountSharedData], payer: &Pubkey, expected_scheduled_actions: usize, -) -> Vec { +) -> Vec { let magic_context_acc = find_magic_context_account(processed_accepted) .expect("magic context account not found"); let magic_context = @@ -218,7 +218,7 @@ fn extend_transaction_accounts_from_ix_adding_magic_context( } fn assert_first_commit( - scheduled_base_intents: &[ScheduledBaseIntent], + scheduled_base_intents: &[ScheduledIntentBundle], payer: &Pubkey, committees: &[Pubkey], expected_request_undelegation: bool, @@ -227,13 +227,13 @@ fn assert_first_commit( let test_clock = get_clock(); assert_matches!( scheduled_base_intent, - ScheduledBaseIntent { + ScheduledIntentBundle { id, slot, payer: actual_payer, blockhash: _, - action_sent_transaction: _, - base_intent, + intent_bundle_sent_transaction: _, + intent_bundle, } => { assert!(id >= &0); assert_eq!(slot, &test_clock.slot); @@ -520,7 +520,7 @@ mod tests { assert_accepted_actions(&processed_accepted, &payer.pubkey(), 1); // Verify the committed pubkey remapped to eATA assert_eq!( - scheduled[0].base_intent.get_committed_pubkeys().unwrap(), + scheduled[0].intent_bundle.get_committed_pubkeys().unwrap(), vec![eata_pubkey] ); } @@ -602,11 +602,11 @@ mod tests { assert_accepted_actions(&processed_accepted, &payer.pubkey(), 1); // Verify the committed pubkey remapped to eATA assert_eq!( - scheduled[0].base_intent.get_committed_pubkeys().unwrap(), + scheduled[0].intent_bundle.get_committed_pubkeys().unwrap(), vec![eata_pubkey] ); // And the intent contains undelegation - assert!(scheduled[0].base_intent.is_undelegate()); + assert!(scheduled[0].intent_bundle.is_undelegate()); } #[test] diff --git a/programs/magicblock/src/schedule_transactions/process_schedule_intent_bundle.rs b/programs/magicblock/src/schedule_transactions/process_schedule_intent_bundle.rs index 5cf01e688..cd927141c 100644 --- a/programs/magicblock/src/schedule_transactions/process_schedule_intent_bundle.rs +++ b/programs/magicblock/src/schedule_transactions/process_schedule_intent_bundle.rs @@ -10,7 +10,7 @@ use solana_transaction_context::TransactionContext; use crate::{ magic_scheduled_base_intent::{ - CommitType, ConstructionContext, ScheduledBaseIntent, + CommitType, ConstructionContext, ScheduledIntentBundle, }, schedule_transactions::check_magic_context_id, utils::{ @@ -130,7 +130,7 @@ pub(crate) fn process_schedule_intent_bundle( None }; - let scheduled_intent = ScheduledBaseIntent::try_new( + let scheduled_intent = ScheduledIntentBundle::try_new( args, intent_id, clock.slot, @@ -156,7 +156,7 @@ pub(crate) fn process_schedule_intent_bundle( } let action_sent_signature = - scheduled_intent.action_sent_transaction.signatures[0]; + scheduled_intent.intent_bundle_sent_transaction.signatures[0]; context.add_scheduled_action(scheduled_intent); context_data.set_state(&context)?; diff --git a/programs/magicblock/src/schedule_transactions/transaction_scheduler.rs b/programs/magicblock/src/schedule_transactions/transaction_scheduler.rs index 8ebdcb93b..31c2cc7be 100644 --- a/programs/magicblock/src/schedule_transactions/transaction_scheduler.rs +++ b/programs/magicblock/src/schedule_transactions/transaction_scheduler.rs @@ -13,12 +13,12 @@ use solana_pubkey::Pubkey; use crate::{ magic_context::MagicContext, - magic_scheduled_base_intent::ScheduledBaseIntent, + magic_scheduled_base_intent::ScheduledIntentBundle, }; #[derive(Clone)] pub struct TransactionScheduler { - scheduled_base_intents: Arc>>, + scheduled_base_intents: Arc>>, } impl Default for TransactionScheduler { @@ -27,7 +27,7 @@ impl Default for TransactionScheduler { /// This vec tracks commits that went through the entire process of first /// being scheduled into the MagicContext, and then being moved /// over to this global. - static ref SCHEDULED_ACTION: Arc>> = + static ref SCHEDULED_ACTION: Arc>> = Default::default(); } Self { @@ -40,7 +40,7 @@ impl TransactionScheduler { pub fn schedule_base_intent( invoke_context: &InvokeContext, context_account: &RefCell, - action: ScheduledBaseIntent, + action: ScheduledIntentBundle, ) -> Result<(), InstructionError> { let context_data = &mut context_account.borrow_mut(); let mut context = @@ -59,7 +59,7 @@ impl TransactionScheduler { pub fn accept_scheduled_base_intent( &self, - base_intents: Vec, + base_intents: Vec, ) { self.scheduled_base_intents .write() @@ -70,7 +70,7 @@ impl TransactionScheduler { pub fn get_scheduled_actions_by_payer( &self, payer: &Pubkey, - ) -> Vec { + ) -> Vec { let commits = self .scheduled_base_intents .read() @@ -83,7 +83,7 @@ impl TransactionScheduler { .collect::>() } - pub fn take_scheduled_actions(&self) -> Vec { + pub fn take_scheduled_actions(&self) -> Vec { let mut lock = self .scheduled_base_intents .write() diff --git a/test-integration/test-committor-service/tests/test_intent_executor.rs b/test-integration/test-committor-service/tests/test_intent_executor.rs index edde6dcd0..74938187b 100644 --- a/test-integration/test-committor-service/tests/test_intent_executor.rs +++ b/test-integration/test-committor-service/tests/test_intent_executor.rs @@ -33,7 +33,7 @@ use magicblock_program::{ args::ShortAccountMeta, magic_scheduled_base_intent::{ BaseAction, CommitAndUndelegate, CommitType, CommittedAccount, - MagicBaseIntent, ProgramArgs, ScheduledBaseIntent, UndelegateType, + MagicBaseIntent, ProgramArgs, ScheduledIntentBundle, UndelegateType, }, validator::validator_authority_id, }; @@ -1178,7 +1178,7 @@ async fn setup_counter( fn create_intent( committed_accounts: Vec, is_undelegate: bool, -) -> ScheduledBaseIntent { +) -> ScheduledIntentBundle { let base_intent = if is_undelegate { MagicBaseIntent::CommitAndUndelegate(CommitAndUndelegate { commit_action: CommitType::Standalone(committed_accounts), @@ -1193,23 +1193,23 @@ fn create_intent( fn create_scheduled_intent( base_intent: MagicBaseIntent, -) -> ScheduledBaseIntent { +) -> ScheduledIntentBundle { static INTENT_ID: AtomicU64 = AtomicU64::new(0); - ScheduledBaseIntent { + ScheduledIntentBundle { id: INTENT_ID.fetch_add(1, Ordering::Relaxed), slot: 10, blockhash: Hash::new_unique(), - action_sent_transaction: Transaction::default(), + intent_bundle_sent_transaction: Transaction::default(), payer: Pubkey::new_unique(), - base_intent, + intent_bundle: base_intent, } } async fn single_flow_transaction_strategy( authority: &Pubkey, task_info_fetcher: &Arc, - intent: &ScheduledBaseIntent, + intent: &ScheduledIntentBundle, ) -> TransactionStrategy { let mut tasks = TaskBuilderImpl::commit_tasks( task_info_fetcher, diff --git a/test-integration/test-committor-service/tests/test_ix_commit_local.rs b/test-integration/test-committor-service/tests/test_ix_commit_local.rs index 58497e5bc..de4f09689 100644 --- a/test-integration/test-committor-service/tests/test_ix_commit_local.rs +++ b/test-integration/test-committor-service/tests/test_ix_commit_local.rs @@ -15,7 +15,7 @@ use magicblock_committor_service::{ }; use magicblock_program::magic_scheduled_base_intent::{ CommitAndUndelegate, CommitType, CommittedAccount, MagicBaseIntent, - ScheduledBaseIntent, UndelegateType, + ScheduledIntentBundle, UndelegateType, }; use magicblock_rpc_client::MagicblockRpcClient; use program_flexi_counter::state::FlexiCounter; @@ -190,13 +190,13 @@ async fn commit_single_account( let intent = ScheduledBaseIntentWrapper { trigger_type: TriggerType::OnChain, - inner: ScheduledBaseIntent { + inner: ScheduledIntentBundle { id: 0, slot: 10, blockhash: Hash::new_unique(), - action_sent_transaction: Transaction::default(), + intent_bundle_sent_transaction: Transaction::default(), payer: counter_auth.pubkey(), - base_intent, + intent_bundle: base_intent, }, }; @@ -257,13 +257,13 @@ async fn commit_book_order_account( let intent = ScheduledBaseIntentWrapper { trigger_type: TriggerType::OnChain, - inner: ScheduledBaseIntent { + inner: ScheduledIntentBundle { id: 0, slot: 10, blockhash: Hash::new_unique(), - action_sent_transaction: Transaction::default(), + intent_bundle_sent_transaction: Transaction::default(), payer: payer.pubkey(), - base_intent, + intent_bundle: base_intent, }, }; @@ -581,13 +581,13 @@ async fn commit_multiple_accounts( } }) .enumerate() - .map(|(id, base_intent)| ScheduledBaseIntent { + .map(|(id, base_intent)| ScheduledIntentBundle { id: id as u64, slot: 0, blockhash: Hash::new_unique(), - action_sent_transaction: Transaction::default(), + intent_bundle_sent_transaction: Transaction::default(), payer: Pubkey::new_unique(), - base_intent, + intent_bundle: base_intent, }) .map(|intent| ScheduledBaseIntentWrapper { trigger_type: TriggerType::OnChain, From b5524d3bb4a5baa931e0b5b01e39418c511d2486 Mon Sep 17 00:00:00 2001 From: taco-paco Date: Mon, 19 Jan 2026 19:37:23 +0700 Subject: [PATCH 05/36] refactor: ranamings --- .../src/scheduled_commits_processor.rs | 8 +++---- .../src/committor_processor.rs | 4 ++-- .../src/intent_execution_manager.rs | 6 ++--- .../src/intent_execution_manager/db.rs | 16 +++++++------- .../intent_execution_engine.rs | 22 +++++++++---------- .../intent_scheduler.rs | 14 ++++++------ magicblock-committor-service/src/service.rs | 8 +++---- .../src/service_ext.rs | 8 +++---- .../src/stubs/changeset_committor_stub.rs | 8 +++---- magicblock-committor-service/src/types.rs | 7 +++--- .../tests/test_ix_commit_local.rs | 10 ++++----- 11 files changed, 56 insertions(+), 55 deletions(-) diff --git a/magicblock-accounts/src/scheduled_commits_processor.rs b/magicblock-accounts/src/scheduled_commits_processor.rs index 3609e96f8..213b84fca 100644 --- a/magicblock-accounts/src/scheduled_commits_processor.rs +++ b/magicblock-accounts/src/scheduled_commits_processor.rs @@ -17,7 +17,7 @@ use magicblock_chainlink::{ use magicblock_committor_service::{ intent_execution_manager::BroadcastedIntentExecutionResult, intent_executor::ExecutionOutput, - types::{ScheduledBaseIntentWrapper, TriggerType}, + types::{ScheduleIntentBundleWrapper, TriggerType}, BaseIntentCommittor, CommittorService, }; use magicblock_core::{ @@ -90,11 +90,11 @@ impl ScheduledCommitsProcessorImpl { fn preprocess_intent( &self, mut base_intent: ScheduledIntentBundle, - ) -> (ScheduledBaseIntentWrapper, Vec) { + ) -> (ScheduleIntentBundleWrapper, Vec) { let is_undelegate = base_intent.is_undelegate(); let Some(committed_accounts) = base_intent.get_committed_accounts_mut() else { - let intent = ScheduledBaseIntentWrapper { + let intent = ScheduleIntentBundleWrapper { inner: base_intent, trigger_type: TriggerType::OnChain, }; @@ -127,7 +127,7 @@ impl ScheduledCommitsProcessorImpl { }) .collect(); - let intent = ScheduledBaseIntentWrapper { + let intent = ScheduleIntentBundleWrapper { inner: base_intent, trigger_type: TriggerType::OnChain, }; diff --git a/magicblock-committor-service/src/committor_processor.rs b/magicblock-committor-service/src/committor_processor.rs index 18062bb80..b6cde5093 100644 --- a/magicblock-committor-service/src/committor_processor.rs +++ b/magicblock-committor-service/src/committor_processor.rs @@ -19,7 +19,7 @@ use crate::{ CommitStatusRow, IntentPersister, IntentPersisterImpl, MessageSignatures, }, - types::ScheduledBaseIntentWrapper, + types::ScheduleIntentBundleWrapper, }; pub(crate) struct CommittorProcessor { @@ -123,7 +123,7 @@ impl CommittorProcessor { pub async fn schedule_base_intents( &self, - base_intents: Vec, + base_intents: Vec, ) -> CommittorServiceResult<()> { let intents = base_intents .iter() diff --git a/magicblock-committor-service/src/intent_execution_manager.rs b/magicblock-committor-service/src/intent_execution_manager.rs index 5c85354ed..61ccc43fe 100644 --- a/magicblock-committor-service/src/intent_execution_manager.rs +++ b/magicblock-committor-service/src/intent_execution_manager.rs @@ -19,14 +19,14 @@ use crate::{ task_info_fetcher::CacheTaskInfoFetcher, }, persist::IntentPersister, - types::ScheduledBaseIntentWrapper, + types::ScheduleIntentBundleWrapper, ComputeBudgetConfig, }; pub struct IntentExecutionManager { db: Arc, result_subscriber: ResultSubscriber, - intent_sender: mpsc::Sender, + intent_sender: mpsc::Sender, } impl IntentExecutionManager { @@ -69,7 +69,7 @@ impl IntentExecutionManager { /// Intents will be extracted and handled in the [`IntentExecutionEngine`] pub async fn schedule( &self, - base_intents: Vec, + base_intents: Vec, ) -> Result<(), IntentExecutionManagerError> { // If db not empty push el-t there // This means that at some point channel got full diff --git a/magicblock-committor-service/src/intent_execution_manager/db.rs b/magicblock-committor-service/src/intent_execution_manager/db.rs index f84000437..1a83d63e9 100644 --- a/magicblock-committor-service/src/intent_execution_manager/db.rs +++ b/magicblock-committor-service/src/intent_execution_manager/db.rs @@ -4,7 +4,7 @@ use std::{collections::VecDeque, sync::Mutex}; use async_trait::async_trait; use magicblock_metrics::metrics; -use crate::types::ScheduledBaseIntentWrapper; +use crate::types::ScheduleIntentBundleWrapper; const POISONED_MUTEX_MSG: &str = "Dummy db mutex poisoned"; @@ -12,22 +12,22 @@ const POISONED_MUTEX_MSG: &str = "Dummy db mutex poisoned"; pub trait DB: Send + Sync + 'static { async fn store_base_intent( &self, - base_intent: ScheduledBaseIntentWrapper, + base_intent: ScheduleIntentBundleWrapper, ) -> DBResult<()>; async fn store_base_intents( &self, - base_intents: Vec, + base_intents: Vec, ) -> DBResult<()>; /// Returns intent with smallest id async fn pop_base_intent( &self, - ) -> DBResult>; + ) -> DBResult>; fn is_empty(&self) -> bool; } pub(crate) struct DummyDB { - db: Mutex>, + db: Mutex>, } impl DummyDB { @@ -42,7 +42,7 @@ impl DummyDB { impl DB for DummyDB { async fn store_base_intent( &self, - base_intent: ScheduledBaseIntentWrapper, + base_intent: ScheduleIntentBundleWrapper, ) -> DBResult<()> { let mut db = self.db.lock().expect(POISONED_MUTEX_MSG); db.push_back(base_intent); @@ -53,7 +53,7 @@ impl DB for DummyDB { async fn store_base_intents( &self, - base_intents: Vec, + base_intents: Vec, ) -> DBResult<()> { let mut db = self.db.lock().expect(POISONED_MUTEX_MSG); db.extend(base_intents); @@ -64,7 +64,7 @@ impl DB for DummyDB { async fn pop_base_intent( &self, - ) -> DBResult> { + ) -> DBResult> { let mut db = self.db.lock().expect(POISONED_MUTEX_MSG); let res = db.pop_front(); diff --git a/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs b/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs index 2fb7eee6f..266f4fe8e 100644 --- a/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs +++ b/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs @@ -30,7 +30,7 @@ use crate::{ ExecutionOutput, IntentExecutionResult, IntentExecutor, }, persist::IntentPersister, - types::{ScheduledBaseIntentWrapper, TriggerType}, + types::{ScheduleIntentBundleWrapper, TriggerType}, }; const SEMAPHORE_CLOSED_MSG: &str = "Executors semaphore closed!"; @@ -88,7 +88,7 @@ pub(crate) struct IntentExecutionEngine { db: Arc, executor_factory: F, intents_persister: Option

, - receiver: mpsc::Receiver, + receiver: mpsc::Receiver, inner: Arc>, running_executors: FuturesUnordered>, @@ -106,7 +106,7 @@ where db: Arc, executor_factory: F, intents_persister: Option

, - receiver: mpsc::Receiver, + receiver: mpsc::Receiver, ) -> Self { Self { db, @@ -186,10 +186,10 @@ where } } - /// Returns [`ScheduledBaseIntentWrapper`] or None if all intents are blocked + /// Returns [`ScheduleIntentBundleWrapper`] or None if all intents are blocked async fn next_scheduled_intent( &mut self, - ) -> Result, IntentExecutionManagerError> + ) -> Result, IntentExecutionManagerError> { // Limit on number of intents that can be stored in scheduler const SCHEDULER_CAPACITY: usize = 1000; @@ -238,11 +238,11 @@ where Ok(intent) } - /// Returns [`ScheduledBaseIntentWrapper`] from external channel + /// Returns [`ScheduleIntentBundleWrapper`] from external channel async fn get_new_intent( - receiver: &mut mpsc::Receiver, + receiver: &mut mpsc::Receiver, db: &Arc, - ) -> Result { + ) -> Result { match receiver.try_recv() { Ok(val) => Ok(val), Err(TryRecvError::Empty) => { @@ -267,7 +267,7 @@ where async fn execute( mut executor: E, persister: Option

, - intent: ScheduledBaseIntentWrapper, + intent: ScheduleIntentBundleWrapper, inner_scheduler: Arc>, execution_permit: OwnedSemaphorePermit, result_sender: broadcast::Sender, @@ -322,7 +322,7 @@ where /// Records metrics related to intent execution fn execution_metrics( execution_time: Duration, - intent: &ScheduledBaseIntentWrapper, + intent: &ScheduleIntentBundleWrapper, result: &IntentExecutorResult, ) { const EXECUTION_TIME_THRESHOLD: f64 = 5.0; @@ -399,7 +399,7 @@ mod tests { fn setup_engine( should_fail: bool, ) -> ( - mpsc::Sender, + mpsc::Sender, MockIntentExecutionEngine, ) { let (sender, receiver) = mpsc::channel(1000); diff --git a/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs b/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs index 0cbbee0a6..d5b2a2774 100644 --- a/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs +++ b/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs @@ -5,7 +5,7 @@ use solana_pubkey::Pubkey; use thiserror::Error; use tracing::error; -use crate::types::ScheduledBaseIntentWrapper; +use crate::types::ScheduleIntentBundleWrapper; pub(crate) const POISONED_INNER_MSG: &str = "Mutex on CommitSchedulerInner is poisoned."; @@ -13,7 +13,7 @@ pub(crate) const POISONED_INNER_MSG: &str = type IntentID = u64; struct IntentMeta { num_keys: usize, - intent: ScheduledBaseIntentWrapper, + intent: ScheduleIntentBundleWrapper, } /// A scheduler that ensures mutually exclusive access to pubkeys across intents @@ -80,8 +80,8 @@ impl IntentScheduler { /// otherwise consumes it and enqueues pub fn schedule( &mut self, - base_intent: ScheduledBaseIntentWrapper, - ) -> Option { + base_intent: ScheduleIntentBundleWrapper, + ) -> Option { let intent_id = base_intent.inner.id; // To check duplicate scheduling its enough to check: @@ -229,7 +229,7 @@ impl IntentScheduler { // Returns [`ScheduledBaseIntent`] that can be executed pub fn pop_next_scheduled_intent( &mut self, - ) -> Option { + ) -> Option { // TODO(edwin): optimize. Create counter im IntentMeta & update let mut execute_candidates: HashMap = HashMap::new(); self.blocked_keys.iter().for_each(|(_, queue)| { @@ -730,7 +730,7 @@ pub(crate) fn create_test_intent( id: u64, pubkeys: &[Pubkey], is_undelegate: bool, -) -> ScheduledBaseIntentWrapper { +) -> ScheduleIntentBundleWrapper { use magicblock_program::magic_scheduled_base_intent::{ CommitAndUndelegate, CommitType, CommittedAccount, MagicBaseIntent, ScheduledIntentBundle, UndelegateType, @@ -773,7 +773,7 @@ pub(crate) fn create_test_intent( } } - ScheduledBaseIntentWrapper { + ScheduleIntentBundleWrapper { inner: intent, trigger_type: TriggerType::OffChain, } diff --git a/magicblock-committor-service/src/service.rs b/magicblock-committor-service/src/service.rs index a7571b536..ef990ce11 100644 --- a/magicblock-committor-service/src/service.rs +++ b/magicblock-committor-service/src/service.rs @@ -22,7 +22,7 @@ use crate::{ intent_execution_manager::BroadcastedIntentExecutionResult, persist::{CommitStatusRow, MessageSignatures}, pubkeys_provider::{provide_committee_pubkeys, provide_common_pubkeys}, - types::ScheduledBaseIntentWrapper, + types::ScheduleIntentBundleWrapper, }; #[derive(Debug)] @@ -55,7 +55,7 @@ pub enum CommittorMessage { }, ScheduleBaseIntents { /// The [`ScheduledBaseIntent`]s to commit - base_intents: Vec, + base_intents: Vec, respond_to: oneshot::Sender>, }, GetCommitStatuses { @@ -361,7 +361,7 @@ impl BaseIntentCommittor for CommittorService { fn schedule_base_intent( &self, - base_intents: Vec, + base_intents: Vec, ) -> oneshot::Receiver> { let (tx, rx) = oneshot::channel(); self.try_send(CommittorMessage::ScheduleBaseIntents { @@ -442,7 +442,7 @@ pub trait BaseIntentCommittor: Send + Sync + 'static { /// Commits the changeset and returns fn schedule_base_intent( &self, - base_intents: Vec, + base_intents: Vec, ) -> oneshot::Receiver>; /// Subscribes for results of BaseIntent execution diff --git a/magicblock-committor-service/src/service_ext.rs b/magicblock-committor-service/src/service_ext.rs index 6a77b4613..5742580ee 100644 --- a/magicblock-committor-service/src/service_ext.rs +++ b/magicblock-committor-service/src/service_ext.rs @@ -18,7 +18,7 @@ use crate::{ error::{CommittorServiceError, CommittorServiceResult}, intent_execution_manager::BroadcastedIntentExecutionResult, persist::{CommitStatusRow, MessageSignatures}, - types::ScheduledBaseIntentWrapper, + types::ScheduleIntentBundleWrapper, BaseIntentCommittor, }; @@ -30,7 +30,7 @@ pub trait BaseIntentCommittorExt: BaseIntentCommittor { /// Schedules Base Intents and waits for their results async fn schedule_base_intents_waiting( &self, - base_intents: Vec, + base_intents: Vec, ) -> BaseIntentCommitorExtResult>; } @@ -116,7 +116,7 @@ impl BaseIntentCommittorExt { async fn schedule_base_intents_waiting( &self, - base_intents: Vec, + base_intents: Vec, ) -> BaseIntentCommitorExtResult> { // Critical section @@ -164,7 +164,7 @@ impl BaseIntentCommittor for CommittorServiceExt { fn schedule_base_intent( &self, - base_intents: Vec, + base_intents: Vec, ) -> oneshot::Receiver> { self.inner.schedule_base_intent(base_intents) } diff --git a/magicblock-committor-service/src/stubs/changeset_committor_stub.rs b/magicblock-committor-service/src/stubs/changeset_committor_stub.rs index 245cc0658..455d44352 100644 --- a/magicblock-committor-service/src/stubs/changeset_committor_stub.rs +++ b/magicblock-committor-service/src/stubs/changeset_committor_stub.rs @@ -21,7 +21,7 @@ use crate::{ intent_executor::ExecutionOutput, persist::{CommitStatusRow, IntentPersisterImpl, MessageSignatures}, service_ext::{BaseIntentCommitorExtResult, BaseIntentCommittorExt}, - types::{ScheduledBaseIntentWrapper, TriggerType}, + types::{ScheduleIntentBundleWrapper, TriggerType}, BaseIntentCommittor, }; @@ -30,7 +30,7 @@ pub struct ChangesetCommittorStub { cancellation_token: CancellationToken, reserved_pubkeys_for_committee: Arc>>, #[allow(clippy::type_complexity)] - committed_changesets: Arc>>, + committed_changesets: Arc>>, committed_accounts: Arc>>, } @@ -66,7 +66,7 @@ impl BaseIntentCommittor for ChangesetCommittorStub { fn schedule_base_intent( &self, - base_intents: Vec, + base_intents: Vec, ) -> oneshot::Receiver> { let (sender, receiver) = oneshot::channel(); let _ = sender.send(Ok(())); @@ -191,7 +191,7 @@ impl BaseIntentCommittor for ChangesetCommittorStub { impl BaseIntentCommittorExt for ChangesetCommittorStub { async fn schedule_base_intents_waiting( &self, - base_intents: Vec, + base_intents: Vec, ) -> BaseIntentCommitorExtResult> { self.schedule_base_intent(base_intents.clone()).await??; diff --git a/magicblock-committor-service/src/types.rs b/magicblock-committor-service/src/types.rs index c57d2ddbb..395b887e6 100644 --- a/magicblock-committor-service/src/types.rs +++ b/magicblock-committor-service/src/types.rs @@ -13,13 +13,14 @@ pub enum TriggerType { OffChain, } +// TODO(edwin): can be removed? #[derive(Clone, Debug, PartialEq, Eq)] -pub struct ScheduledBaseIntentWrapper { +pub struct ScheduleIntentBundleWrapper { pub inner: ScheduledIntentBundle, pub trigger_type: TriggerType, } -impl metrics::LabelValue for ScheduledBaseIntentWrapper { +impl metrics::LabelValue for ScheduleIntentBundleWrapper { fn value(&self) -> &str { match &self.inner.intent_bundle { MagicBaseIntent::BaseActions(_) => "actions", @@ -29,7 +30,7 @@ impl metrics::LabelValue for ScheduledBaseIntentWrapper { } } -impl Deref for ScheduledBaseIntentWrapper { +impl Deref for ScheduleIntentBundleWrapper { type Target = ScheduledIntentBundle; fn deref(&self) -> &Self::Target { diff --git a/test-integration/test-committor-service/tests/test_ix_commit_local.rs b/test-integration/test-committor-service/tests/test_ix_commit_local.rs index de4f09689..52115c8a6 100644 --- a/test-integration/test-committor-service/tests/test_ix_commit_local.rs +++ b/test-integration/test-committor-service/tests/test_ix_commit_local.rs @@ -10,7 +10,7 @@ use magicblock_committor_service::{ intent_executor::ExecutionOutput, persist::CommitStrategy, service_ext::{BaseIntentCommittorExt, CommittorServiceExt}, - types::{ScheduledBaseIntentWrapper, TriggerType}, + types::{ScheduleIntentBundleWrapper, TriggerType}, BaseIntentCommittor, CommittorService, ComputeBudgetConfig, }; use magicblock_program::magic_scheduled_base_intent::{ @@ -188,7 +188,7 @@ async fn commit_single_account( MagicBaseIntent::Commit(CommitType::Standalone(vec![account])) }; - let intent = ScheduledBaseIntentWrapper { + let intent = ScheduleIntentBundleWrapper { trigger_type: TriggerType::OnChain, inner: ScheduledIntentBundle { id: 0, @@ -255,7 +255,7 @@ async fn commit_book_order_account( MagicBaseIntent::Commit(CommitType::Standalone(vec![account])) }; - let intent = ScheduledBaseIntentWrapper { + let intent = ScheduleIntentBundleWrapper { trigger_type: TriggerType::OnChain, inner: ScheduledIntentBundle { id: 0, @@ -589,7 +589,7 @@ async fn commit_multiple_accounts( payer: Pubkey::new_unique(), intent_bundle: base_intent, }) - .map(|intent| ScheduledBaseIntentWrapper { + .map(|intent| ScheduleIntentBundleWrapper { trigger_type: TriggerType::OnChain, inner: intent, }) @@ -627,7 +627,7 @@ async fn commit_multiple_accounts( // ----------------- async fn ix_commit_local( service: CommittorServiceExt, - base_intents: Vec, + base_intents: Vec, expected_strategies: ExpectedStrategies, ) { let execution_outputs = service From 90aa5fb01258d2209f6920b96abeaaed2e283b91 Mon Sep 17 00:00:00 2001 From: taco-paco Date: Mon, 19 Jan 2026 19:40:52 +0700 Subject: [PATCH 06/36] refactor: renamings --- .../src/committor_processor.rs | 10 +++++----- .../src/intent_execution_manager.rs | 6 +++--- magicblock-committor-service/src/service.rs | 16 ++++++++-------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/magicblock-committor-service/src/committor_processor.rs b/magicblock-committor-service/src/committor_processor.rs index b6cde5093..e81bec279 100644 --- a/magicblock-committor-service/src/committor_processor.rs +++ b/magicblock-committor-service/src/committor_processor.rs @@ -121,15 +121,15 @@ impl CommittorProcessor { Ok(signatures) } - pub async fn schedule_base_intents( + pub async fn schedule_intent_bundle( &self, - base_intents: Vec, + intent_bundles: Vec, ) -> CommittorServiceResult<()> { - let intents = base_intents + let intent_bundles = intent_bundles .iter() .map(|base_intent| base_intent.inner.clone()) .collect::>(); - if let Err(err) = self.persister.start_base_intents(&intents) { + if let Err(err) = self.persister.start_base_intents(&intent_bundles) { // We will still try to perform the commits, but the fact that we cannot // persist the intent is very serious and we should probably restart the // valiator @@ -140,7 +140,7 @@ impl CommittorProcessor { }; self.commits_scheduler - .schedule(base_intents) + .schedule(intent_bundles) .await .inspect_err(|err| { error!("Failed to schedule base intent: {}", err); diff --git a/magicblock-committor-service/src/intent_execution_manager.rs b/magicblock-committor-service/src/intent_execution_manager.rs index 61ccc43fe..ef919113e 100644 --- a/magicblock-committor-service/src/intent_execution_manager.rs +++ b/magicblock-committor-service/src/intent_execution_manager.rs @@ -69,18 +69,18 @@ impl IntentExecutionManager { /// Intents will be extracted and handled in the [`IntentExecutionEngine`] pub async fn schedule( &self, - base_intents: Vec, + intent_bundles: Vec, ) -> Result<(), IntentExecutionManagerError> { // If db not empty push el-t there // This means that at some point channel got full // Worker first will clean-up channel, and then DB. // Pushing into channel would break order of commits if !self.db.is_empty() { - self.db.store_base_intents(base_intents).await?; + self.db.store_base_intents(intent_bundles).await?; return Ok(()); } - for el in base_intents { + for el in intent_bundles { let err = if let Err(err) = self.intent_sender.try_send(el) { err } else { diff --git a/magicblock-committor-service/src/service.rs b/magicblock-committor-service/src/service.rs index ef990ce11..08cfaefc9 100644 --- a/magicblock-committor-service/src/service.rs +++ b/magicblock-committor-service/src/service.rs @@ -53,9 +53,9 @@ pub enum CommittorMessage { /// Called once the pubkeys have been released respond_to: oneshot::Sender<()>, }, - ScheduleBaseIntents { - /// The [`ScheduledBaseIntent`]s to commit - base_intents: Vec, + ScheduleIntentBundle { + /// The [`ScheduleIntentBundle`]s to commit + intent_bundle: Vec, respond_to: oneshot::Sender>, }, GetCommitStatuses { @@ -163,12 +163,12 @@ impl CommittorActor { } }); } - ScheduleBaseIntents { - base_intents, + ScheduleIntentBundle { + intent_bundle, respond_to, } => { let result = - self.processor.schedule_base_intents(base_intents).await; + self.processor.schedule_intent_bundle(intent_bundle).await; if let Err(e) = respond_to.send(result) { error!("Failed to send response {:?}", e); } @@ -364,8 +364,8 @@ impl BaseIntentCommittor for CommittorService { base_intents: Vec, ) -> oneshot::Receiver> { let (tx, rx) = oneshot::channel(); - self.try_send(CommittorMessage::ScheduleBaseIntents { - base_intents, + self.try_send(CommittorMessage::ScheduleIntentBundle { + intent_bundle: base_intents, respond_to: tx, }); rx From 4a7b02ada04e671d0c14ad742780c8abafd44534 Mon Sep 17 00:00:00 2001 From: taco-paco Date: Mon, 19 Jan 2026 20:17:50 +0700 Subject: [PATCH 07/36] feat: integrate IntentBundle into Persistor --- .../src/scheduled_commits_processor.rs | 4 +- .../src/committor_processor.rs | 6 +- .../intent_execution_engine.rs | 2 +- .../src/intent_executor/mod.rs | 2 +- .../src/persist/commit_persister.rs | 245 ++++++++++++------ .../src/magic_scheduled_base_intent.rs | 16 +- .../process_schedule_commit_tests.rs | 2 +- .../tests/test_ix_commit_local.rs | 2 +- 8 files changed, 186 insertions(+), 93 deletions(-) diff --git a/magicblock-accounts/src/scheduled_commits_processor.rs b/magicblock-accounts/src/scheduled_commits_processor.rs index 213b84fca..92f37183e 100644 --- a/magicblock-accounts/src/scheduled_commits_processor.rs +++ b/magicblock-accounts/src/scheduled_commits_processor.rs @@ -91,7 +91,7 @@ impl ScheduledCommitsProcessorImpl { &self, mut base_intent: ScheduledIntentBundle, ) -> (ScheduleIntentBundleWrapper, Vec) { - let is_undelegate = base_intent.is_undelegate(); + let is_undelegate = base_intent.has_undelegate_intent(); let Some(committed_accounts) = base_intent.get_committed_accounts_mut() else { let intent = ScheduleIntentBundleWrapper { @@ -400,7 +400,7 @@ impl ScheduledBaseIntentMeta { intent_sent_transaction: intent .intent_bundle_sent_transaction .clone(), - requested_undelegation: intent.is_undelegate(), + requested_undelegation: intent.has_undelegate_intent(), } } } diff --git a/magicblock-committor-service/src/committor_processor.rs b/magicblock-committor-service/src/committor_processor.rs index e81bec279..ca0fc3a90 100644 --- a/magicblock-committor-service/src/committor_processor.rs +++ b/magicblock-committor-service/src/committor_processor.rs @@ -125,11 +125,13 @@ impl CommittorProcessor { &self, intent_bundles: Vec, ) -> CommittorServiceResult<()> { - let intent_bundles = intent_bundles + let base_intent_bundles = intent_bundles .iter() .map(|base_intent| base_intent.inner.clone()) .collect::>(); - if let Err(err) = self.persister.start_base_intents(&intent_bundles) { + if let Err(err) = + self.persister.start_base_intents(&base_intent_bundles) + { // We will still try to perform the commits, but the fact that we cannot // persist the intent is very serious and we should probably restart the // valiator diff --git a/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs b/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs index 266f4fe8e..0a0089563 100644 --- a/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs +++ b/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs @@ -348,7 +348,7 @@ where } // Alert - if intent.is_undelegate() && result.is_err() { + if intent.has_undelegate_intent() && result.is_err() { warn!( "Intent execution resulted in stuck accounts: {:?}", intent.get_committed_pubkeys() diff --git a/magicblock-committor-service/src/intent_executor/mod.rs b/magicblock-committor-service/src/intent_executor/mod.rs index ddc15dbc8..82cbcc05f 100644 --- a/magicblock-committor-service/src/intent_executor/mod.rs +++ b/magicblock-committor-service/src/intent_executor/mod.rs @@ -779,7 +779,7 @@ where persister: Option

, ) -> IntentExecutionResult { let message_id = base_intent.id; - let is_undelegate = base_intent.is_undelegate(); + let is_undelegate = base_intent.has_undelegate_intent(); let pubkeys = base_intent.get_committed_pubkeys(); let result = self.execute_inner(base_intent, &persister).await; diff --git a/magicblock-committor-service/src/persist/commit_persister.rs b/magicblock-committor-service/src/persist/commit_persister.rs index 06a662e15..86436959a 100644 --- a/magicblock-committor-service/src/persist/commit_persister.rs +++ b/magicblock-committor-service/src/persist/commit_persister.rs @@ -3,7 +3,9 @@ use std::{ sync::{Arc, Mutex}, }; -use magicblock_program::magic_scheduled_base_intent::ScheduledIntentBundle; +use magicblock_program::magic_scheduled_base_intent::{ + CommittedAccount, ScheduledIntentBundle, +}; use solana_pubkey::Pubkey; use super::{ @@ -100,52 +102,51 @@ impl IntentPersisterImpl { } pub fn create_commit_rows( - base_intent: &ScheduledIntentBundle, + intent_bundle: &ScheduledIntentBundle, ) -> Vec { - let Some(committed_accounts) = base_intent.get_committed_accounts() - else { - // We don't persist standalone actions - return vec![]; + let created_at = now(); + + let create_row = |undelegate: bool, account: &CommittedAccount| { + let data = &account.account.data; + let (commit_type, data) = if data.is_empty() { + (CommitType::EmptyAccount, None) + } else { + (CommitType::DataAccount, Some(data.clone())) + }; + + CommitStatusRow { + message_id: intent_bundle.id, + commit_id: 0, // Not known at creation, set later + pubkey: account.pubkey, + delegated_account_owner: account.account.owner, + slot: intent_bundle.slot, + ephemeral_blockhash: intent_bundle.blockhash, + undelegate, + lamports: account.account.lamports, + data, + commit_type, + created_at, + commit_strategy: CommitStrategy::default(), + commit_status: CommitStatus::Pending, + last_retried_at: created_at, + retries_count: 0, + } }; - let undelegate = base_intent.is_undelegate(); - let created_at = now(); - committed_accounts - .iter() - .map(|account| { - let data = &account.account.data; - let commit_type = if data.is_empty() { - CommitType::EmptyAccount - } else { - CommitType::DataAccount - }; - - let data = if commit_type == CommitType::DataAccount { - Some(data.clone()) - } else { - None - }; - - // Create a commit status row for this account - CommitStatusRow { - message_id: base_intent.id, - commit_id: 0, // Not known at creation, set later - pubkey: account.pubkey, - delegated_account_owner: account.account.owner, - slot: base_intent.slot, - ephemeral_blockhash: base_intent.blockhash, - undelegate, - lamports: account.account.lamports, - data, - commit_type, - created_at, - commit_strategy: CommitStrategy::default(), - commit_status: CommitStatus::Pending, - last_retried_at: created_at, - retries_count: 0, - } - }) - .collect() + [ + (false, intent_bundle.get_commit_intent_accounts()), + (true, intent_bundle.get_undelegate_intent_accounts()), + ] + .into_iter() + .filter_map(|(undelegate, accounts)| { + accounts.map(|accounts| (undelegate, accounts)) + }) + .flat_map(|(undelegate, accounts)| { + accounts + .iter() + .map(move |account| create_row(undelegate, account)) + }) + .collect() } } @@ -436,7 +437,8 @@ impl IntentPersister for Option { #[cfg(test)] mod tests { use magicblock_program::magic_scheduled_base_intent::{ - CommitType, CommittedAccount, MagicBaseIntent, + CommitAndUndelegate, CommitType, CommittedAccount, MagicBaseIntent, + MagicIntentBundle, UndelegateType, }; use solana_account::Account; use solana_hash::Hash; @@ -454,7 +456,7 @@ mod tests { (persister, temp_file) } - fn create_test_message(id: u64) -> ScheduledIntentBundle { + fn test_accounts() -> Vec { let account1 = Account { lamports: 1000, owner: Pubkey::new_unique(), @@ -470,35 +472,79 @@ mod tests { rent_epoch: 0, }; + vec![ + CommittedAccount { + pubkey: Pubkey::new_unique(), + account: account1, + remote_slot: 1, + }, + CommittedAccount { + pubkey: Pubkey::new_unique(), + account: account2, + remote_slot: 2, + }, + ] + } + + fn commit_only_budle() -> MagicIntentBundle { + MagicIntentBundle { + commit: Some(CommitType::Standalone(test_accounts())), + commit_and_undelegate: None, + standalone_actions: vec![], + } + } + + fn undelegate_only_bundle() -> MagicIntentBundle { + MagicIntentBundle { + commit: None, + commit_and_undelegate: Some(CommitAndUndelegate { + commit_action: CommitType::Standalone(test_accounts()), + undelegate_action: UndelegateType::Standalone, + }), + standalone_actions: vec![], + } + } + + fn bundle_both() -> MagicIntentBundle { + MagicIntentBundle { + commit: Some(CommitType::Standalone(test_accounts())), + commit_and_undelegate: Some(CommitAndUndelegate { + commit_action: CommitType::Standalone(test_accounts()), + undelegate_action: UndelegateType::Standalone, + }), + standalone_actions: vec![], + } + } + + fn bundle_none() -> MagicIntentBundle { + MagicIntentBundle { + commit: None, + commit_and_undelegate: None, + standalone_actions: vec![], + } + } + + fn create_test_message( + id: u64, + intent_bundle: MagicIntentBundle, + ) -> ScheduledIntentBundle { ScheduledIntentBundle { id, slot: 100, blockhash: Hash::new_unique(), intent_bundle_sent_transaction: Transaction::default(), payer: Pubkey::new_unique(), - intent_bundle: MagicBaseIntent::Commit(CommitType::Standalone( - vec![ - CommittedAccount { - pubkey: Pubkey::new_unique(), - account: account1, - remote_slot: 1, - }, - CommittedAccount { - pubkey: Pubkey::new_unique(), - account: account2, - remote_slot: 2, - }, - ], - )), + intent_bundle, } } #[test] - fn test_create_commit_rows() { - let message = create_test_message(1); + fn test_create_commit_rows_commit_only() { + let message = create_test_message(1, commit_only_budle()); let rows = IntentPersisterImpl::create_commit_rows(&message); assert_eq!(rows.len(), 2); + assert!(rows.iter().all(|r| r.undelegate == false)); let empty_account = rows.iter().find(|r| r.data.is_none()).unwrap(); assert_eq!(empty_account.commit_type, types::CommitType::EmptyAccount); @@ -511,9 +557,33 @@ mod tests { } #[test] - fn test_start_base_message() { + fn test_create_commit_rows_undelegate_only() { + let message = create_test_message(1, undelegate_only_bundle()); + let rows = IntentPersisterImpl::create_commit_rows(&message); + + assert_eq!(rows.len(), 2); + assert!(rows.iter().all(|r| r.undelegate == true)); + } + + #[test] + fn test_create_commit_rows_both_intents() { + let message = create_test_message(1, bundle_both()); + let rows = IntentPersisterImpl::create_commit_rows(&message); + + // 2 from commit + 2 from commit_and_undelegate + assert_eq!(rows.len(), 4); + + let commit_rows = rows.iter().filter(|r| !r.undelegate).count(); + let undelegate_rows = rows.iter().filter(|r| r.undelegate).count(); + + assert_eq!(commit_rows, 2); + assert_eq!(undelegate_rows, 2); + } + + #[test] + fn test_start_base_message_commit_only() { let (persister, _temp_file) = create_test_persister(); - let message = create_test_message(1); + let message = create_test_message(1, commit_only_budle()); persister.start_base_intent(&message).unwrap(); @@ -521,29 +591,45 @@ mod tests { IntentPersisterImpl::create_commit_rows(&message); let statuses = persister.get_commit_statuses_by_message(1).unwrap(); - assert_eq!(statuses.len(), 2); + assert_eq!(statuses.len(), expected_statuses.len()); assert_eq!(expected_statuses[0], statuses[0]); assert_eq!(expected_statuses[1], statuses[1]); } #[test] - fn test_start_base_messages() { + fn test_start_base_messages_mixed() { let (persister, _temp_file) = create_test_persister(); - let message1 = create_test_message(1); - let message2 = create_test_message(2); + let message1 = create_test_message(1, commit_only_budle()); // 2 rows + let message2 = create_test_message(2, undelegate_only_bundle()); // 2 rows + let message3 = create_test_message(3, bundle_both()); // 4 rows + let message4 = create_test_message(4, bundle_none()); // 0 rows - persister.start_base_intents(&[message1, message2]).unwrap(); + persister + .start_base_intents(&[message1, message2, message3, message4]) + .unwrap(); - let statuses1 = persister.get_commit_statuses_by_message(1).unwrap(); - let statuses2 = persister.get_commit_statuses_by_message(2).unwrap(); - assert_eq!(statuses1.len(), 2); - assert_eq!(statuses2.len(), 2); + assert_eq!( + persister.get_commit_statuses_by_message(1).unwrap().len(), + 2 + ); + assert_eq!( + persister.get_commit_statuses_by_message(2).unwrap().len(), + 2 + ); + assert_eq!( + persister.get_commit_statuses_by_message(3).unwrap().len(), + 4 + ); + assert_eq!( + persister.get_commit_statuses_by_message(4).unwrap().len(), + 0 + ); } #[test] fn test_update_status() { let (persister, _temp_file) = create_test_persister(); - let message = create_test_message(1); + let message = create_test_message(1, commit_only_budle()); persister.start_base_intent(&message).unwrap(); let pubkey = message.get_committed_pubkeys().unwrap()[0]; @@ -582,7 +668,7 @@ mod tests { #[test] fn test_set_commit_strategy() { let (persister, _temp_file) = create_test_persister(); - let message = create_test_message(1); + let message = create_test_message(1, commit_only_budle()); persister.start_base_intent(&message).unwrap(); let pubkey = message.get_committed_pubkeys().unwrap()[0]; @@ -602,7 +688,7 @@ mod tests { #[test] fn test_get_signatures() { let (persister, _temp_file) = create_test_persister(); - let message = create_test_message(1); + let message = create_test_message(1, commit_only_budle()); persister.start_base_intent(&message).unwrap(); let statuses = persister.get_commit_statuses_by_message(1).unwrap(); @@ -656,14 +742,11 @@ mod tests { #[test] fn test_empty_accounts_not_persisted() { let (persister, _temp_file) = create_test_persister(); - let message = ScheduledIntentBundle { - intent_bundle: MagicBaseIntent::BaseActions(vec![]), // No committed accounts - ..create_test_message(1) - }; + let message = create_test_message(1, bundle_none()); persister.start_base_intent(&message).unwrap(); let statuses = persister.get_commit_statuses_by_message(1).unwrap(); - assert_eq!(statuses.len(), 0); // No rows should be persisted + assert_eq!(statuses.len(), 0); } } diff --git a/programs/magicblock/src/magic_scheduled_base_intent.rs b/programs/magicblock/src/magic_scheduled_base_intent.rs index b375df021..28052c7d8 100644 --- a/programs/magicblock/src/magic_scheduled_base_intent.rs +++ b/programs/magicblock/src/magic_scheduled_base_intent.rs @@ -91,7 +91,10 @@ impl ScheduledIntentBundle { }) } - pub fn get_undelegated_accounts(&self) -> Option<&Vec> { + /// Returns `[CommitAndUndelegate]` intent's accounts + pub fn get_undelegate_intent_accounts( + &self, + ) -> Option<&Vec> { Some( self.intent_bundle .commit_and_undelegate @@ -100,12 +103,17 @@ impl ScheduledIntentBundle { ) } + /// Returns `Commit` intent's accounts + pub fn get_commit_intent_accounts(&self) -> Option<&Vec> { + Some(self.intent_bundle.commit.as_ref()?.get_committed_accounts()) + } + pub fn get_committed_pubkeys(&self) -> Option> { self.intent_bundle.get_committed_pubkeys() } - pub fn is_undelegate(&self) -> bool { - self.intent_bundle.is_undelegate() + pub fn has_undelegate_intent(&self) -> bool { + self.intent_bundle.has_undelegate_intent() } pub fn is_empty(&self) -> bool { @@ -205,7 +213,7 @@ impl MagicIntentBundle { .unwrap_or(Ok(())) } - pub fn is_undelegate(&self) -> bool { + pub fn has_undelegate_intent(&self) -> bool { self.commit_and_undelegate.is_some() } diff --git a/programs/magicblock/src/schedule_transactions/process_schedule_commit_tests.rs b/programs/magicblock/src/schedule_transactions/process_schedule_commit_tests.rs index 350525c23..2253ed8f4 100644 --- a/programs/magicblock/src/schedule_transactions/process_schedule_commit_tests.rs +++ b/programs/magicblock/src/schedule_transactions/process_schedule_commit_tests.rs @@ -606,7 +606,7 @@ mod tests { vec![eata_pubkey] ); // And the intent contains undelegation - assert!(scheduled[0].intent_bundle.is_undelegate()); + assert!(scheduled[0].intent_bundle.has_undelegate_intent()); } #[test] diff --git a/test-integration/test-committor-service/tests/test_ix_commit_local.rs b/test-integration/test-committor-service/tests/test_ix_commit_local.rs index 52115c8a6..38605fd4f 100644 --- a/test-integration/test-committor-service/tests/test_ix_commit_local.rs +++ b/test-integration/test-committor-service/tests/test_ix_commit_local.rs @@ -668,7 +668,7 @@ async fn ix_commit_local( tx_logs_contain(&rpc_client, &finalize_signature, "Finalize").await ); - let is_undelegate = base_intent.is_undelegate(); + let is_undelegate = base_intent.has_undelegate_intent(); if is_undelegate { // Undelegate is part of atomic Finalization Stage assert!( From 26e2cca0037632f9da8773851c06ebda238230b1 Mon Sep 17 00:00:00 2001 From: taco-paco Date: Tue, 20 Jan 2026 13:48:34 +0700 Subject: [PATCH 08/36] fix: commit tasks building --- .../src/tasks/task_builder.rs | 123 ++++++++++-------- magicblock-committor-service/src/types.rs | 10 -- .../src/magic_scheduled_base_intent.rs | 17 +++ 3 files changed, 88 insertions(+), 62 deletions(-) diff --git a/magicblock-committor-service/src/tasks/task_builder.rs b/magicblock-committor-service/src/tasks/task_builder.rs index 6791ea65c..5b619661e 100644 --- a/magicblock-committor-service/src/tasks/task_builder.rs +++ b/magicblock-committor-service/src/tasks/task_builder.rs @@ -2,8 +2,8 @@ use std::sync::Arc; use async_trait::async_trait; use magicblock_program::magic_scheduled_base_intent::{ - CommitType, CommittedAccount, MagicBaseIntent, ScheduledIntentBundle, - UndelegateType, + BaseAction, CommitType, CommittedAccount, MagicBaseIntent, + ScheduledIntentBundle, UndelegateType, }; use solana_account::Account; use solana_pubkey::Pubkey; @@ -80,6 +80,17 @@ impl TaskBuilderImpl { } .into() } + + fn create_action_tasks(actions: &[BaseAction]) -> Vec> { + actions + .iter() + .map(|el| { + let task = BaseActionTask { action: el.clone() }; + let task = ArgsTask::new(ArgsTaskType::BaseAction(task)); + Box::new(task) as Box + }) + .collect() + } } #[async_trait] @@ -87,42 +98,40 @@ impl TasksBuilder for TaskBuilderImpl { /// Returns [`Task`]s for Commit stage async fn commit_tasks( commit_id_fetcher: &Arc, - base_intent: &ScheduledIntentBundle, + intent_bundle: &ScheduledIntentBundle, persister: &Option

, ) -> TaskBuilderResult>> { - let (accounts, allow_undelegation) = match &base_intent.intent_bundle { - MagicBaseIntent::BaseActions(actions) => { - let tasks = actions - .iter() - .map(|el| { - let task = BaseActionTask { action: el.clone() }; - let task = - ArgsTask::new(ArgsTaskType::BaseAction(task)); - Box::new(task) as Box - }) - .collect(); + let standalone_action_tasks = Self::create_action_tasks( + intent_bundle.standalone_actions().as_slice(), + ); - return Ok(tasks); - } - MagicBaseIntent::Commit(t) => (t.get_committed_accounts(), false), - MagicBaseIntent::CommitAndUndelegate(t) => { - (t.commit_action.get_committed_accounts(), true) - } - }; + let committed_accounts = + intent_bundle.get_commit_intent_accounts().cloned(); + let undelegated_accounts = + intent_bundle.get_undelegate_intent_accounts().cloned(); + // Get commit nonces and base accounts + // TODO(edwin): split into funcs let (commit_ids, base_accounts) = { let mut min_context_slot = 0; - let committed_pubkeys = accounts - .iter() - .map(|account| { - min_context_slot = - std::cmp::max(min_context_slot, account.remote_slot); - account.pubkey - }) - .collect::>(); + let committed_pubkeys = + [&committed_accounts, &undelegated_accounts] + .into_iter() + .filter_map(|el| el.as_ref()) + .flatten() + .map(|account| { + min_context_slot = std::cmp::max( + min_context_slot, + account.remote_slot, + ); + account.pubkey + }) + .collect::>(); - let diffable_pubkeys = accounts - .iter() + let diffable_pubkeys = [&committed_accounts, &undelegated_accounts] + .into_iter() + .filter_map(|el| el.as_ref()) + .flatten() .filter(|account| { account.account.data.len() > COMMIT_STATE_SIZE_THRESHOLD }) @@ -143,35 +152,45 @@ impl TasksBuilder for TaskBuilderImpl { let commit_ids = commit_ids.map_err(TaskBuilderError::CommitTasksBuildError)?; - - let base_accounts = match base_accounts { - Ok(map) => map, - Err(err) => { - tracing::warn!("Failed to fetch base accounts for CommitDiff (id={}): {}; falling back to CommitState", base_intent.id, err); - Default::default() - } - }; + let mut base_accounts = base_accounts.unwrap_or_else(|err| { + tracing::warn!("Failed to fetch base accounts for CommitDiff (id={}): {}; falling back to CommitState", base_intent.id, err); + Default::default() + }); // Persist commit ids for commitees commit_ids .iter() .for_each(|(pubkey, commit_id) | { - if let Err(err) = persister.set_commit_id(base_intent.id, pubkey, *commit_id) { - error!("Failed to persist commit id: {}, for message id: {} with pubkey {}: {}", commit_id, base_intent.id, pubkey, err); + if let Err(err) = persister.set_commit_id(intent_bundle.id, pubkey, *commit_id) { + error!("Failed to persist commit id: {}, for message id: {} with pubkey {}: {}", commit_id, intent_bundle.id, pubkey, err); } }); - let tasks = accounts - .iter() - .map(|account| { - let commit_id = *commit_ids.get(&account.pubkey).expect("CommitIdFetcher provide commit ids for all listed pubkeys, or errors!"); - // TODO (snawaz): if accounts do not have duplicate, then we can use remove - // instead: - // let base_account = base_accounts.remove(&account.pubkey); - let base_account = base_accounts.get(&account.pubkey).cloned(); - let task = Self::create_commit_task(commit_id, allow_undelegation, account.clone(), base_account); - Box::new(task) as Box - }).collect(); + let tasks: Vec> = [ + (false, committed_accounts), + (true, undelegated_accounts), + ] + .into_iter() + .flat_map(|(allow_undelegation, accounts)| { + accounts + .into_iter() // Option> -> 0/1 Vec<_> + .flatten() // Vec>? only if accounts is Option; otherwise ignore this variant + .map(move |account| (allow_undelegation, account)) + }) + .map(|(allow_undelegation, account)| { + let commit_id = *commit_ids + .get(&account.pubkey) + .expect("CommitIdFetcher must provide commit ids for all listed pubkeys, or error!"); + let base_account = base_accounts.remove(&account.pubkey); + + Box::new(Self::create_commit_task( + commit_id, + allow_undelegation, + account.clone(), + base_account, + )) as Box + }) + .collect(); Ok(tasks) } diff --git a/magicblock-committor-service/src/types.rs b/magicblock-committor-service/src/types.rs index 395b887e6..c71523bfd 100644 --- a/magicblock-committor-service/src/types.rs +++ b/magicblock-committor-service/src/types.rs @@ -20,16 +20,6 @@ pub struct ScheduleIntentBundleWrapper { pub trigger_type: TriggerType, } -impl metrics::LabelValue for ScheduleIntentBundleWrapper { - fn value(&self) -> &str { - match &self.inner.intent_bundle { - MagicBaseIntent::BaseActions(_) => "actions", - MagicBaseIntent::Commit(_) => "commit", - MagicBaseIntent::CommitAndUndelegate(_) => "commit_and_undelegate", - } - } -} - impl Deref for ScheduleIntentBundleWrapper { type Target = ScheduledIntentBundle; diff --git a/programs/magicblock/src/magic_scheduled_base_intent.rs b/programs/magicblock/src/magic_scheduled_base_intent.rs index 28052c7d8..dd407939e 100644 --- a/programs/magicblock/src/magic_scheduled_base_intent.rs +++ b/programs/magicblock/src/magic_scheduled_base_intent.rs @@ -91,6 +91,19 @@ impl ScheduledIntentBundle { }) } + /// Returns all accounts that will be committed on chain, + /// including the one scheduled for undelegation + pub fn get_all_committed_accounts(&self) -> Vec { + let committed = self.get_commit_intent_accounts(); + let undelegated = self.get_undelegate_intent_accounts(); + [committed, undelegated] + .into_iter() + .filter_map(|el| el) + .cloned() + .flatten() + .collect() + } + /// Returns `[CommitAndUndelegate]` intent's accounts pub fn get_undelegate_intent_accounts( &self, @@ -119,6 +132,10 @@ impl ScheduledIntentBundle { pub fn is_empty(&self) -> bool { self.intent_bundle.is_empty() } + + pub fn standalone_actions(&self) -> &Vec { + &self.intent_bundle.standalone_actions + } } // BaseIntent user wants to send to base layer From c9fe2c0e5e1cacdeaf4c14b898e2244c5cb82192 Mon Sep 17 00:00:00 2001 From: taco-paco Date: Tue, 20 Jan 2026 14:08:25 +0700 Subject: [PATCH 09/36] refactor: commit tasks builder --- .../src/tasks/task_builder.rs | 113 ++++++++++-------- 1 file changed, 63 insertions(+), 50 deletions(-) diff --git a/magicblock-committor-service/src/tasks/task_builder.rs b/magicblock-committor-service/src/tasks/task_builder.rs index 5b619661e..274f9d892 100644 --- a/magicblock-committor-service/src/tasks/task_builder.rs +++ b/magicblock-committor-service/src/tasks/task_builder.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; use async_trait::async_trait; use magicblock_program::magic_scheduled_base_intent::{ @@ -13,7 +13,7 @@ use tracing::error; use super::{CommitDiffTask, CommitTask}; use crate::{ intent_executor::task_info_fetcher::{ - TaskInfoFetcher, TaskInfoFetcherError, + TaskInfoFetcher, TaskInfoFetcherError, TaskInfoFetcherResult, }, persist::IntentPersister, tasks::{ @@ -91,13 +91,46 @@ impl TaskBuilderImpl { }) .collect() } + + async fn fetch_commit_nonces( + task_info_fetcher: &Arc, + accounts: &[(bool, CommittedAccount)], + min_context_slot: u64, + ) -> TaskInfoFetcherResult> { + let committed_pubkeys = accounts + .into_iter() + .map(|(_, account)| account.pubkey) + .collect::>(); + + task_info_fetcher + .fetch_next_commit_ids(&committed_pubkeys, min_context_slot) + .await + } + + async fn fetch_diffable_accounts( + task_info_fetcher: &Arc, + accounts: &[(bool, CommittedAccount)], + min_context_slot: u64, + ) -> TaskInfoFetcherResult> { + let diffable_pubkeys = accounts + .iter() + .filter(|(_, account)| { + account.account.data.len() > COMMIT_STATE_SIZE_THRESHOLD + }) + .map(|(_, account)| account.pubkey) + .collect::>(); + + task_info_fetcher + .get_base_accounts(&diffable_pubkeys, min_context_slot) + .await + } } #[async_trait] impl TasksBuilder for TaskBuilderImpl { /// Returns [`Task`]s for Commit stage async fn commit_tasks( - commit_id_fetcher: &Arc, + task_info_fetcher: &Arc, intent_bundle: &ScheduledIntentBundle, persister: &Option

, ) -> TaskBuilderResult>> { @@ -109,47 +142,35 @@ impl TasksBuilder for TaskBuilderImpl { intent_bundle.get_commit_intent_accounts().cloned(); let undelegated_accounts = intent_bundle.get_undelegate_intent_accounts().cloned(); - - // Get commit nonces and base accounts - // TODO(edwin): split into funcs - let (commit_ids, base_accounts) = { - let mut min_context_slot = 0; - let committed_pubkeys = - [&committed_accounts, &undelegated_accounts] - .into_iter() - .filter_map(|el| el.as_ref()) - .flatten() - .map(|account| { - min_context_slot = std::cmp::max( - min_context_slot, - account.remote_slot, - ); - account.pubkey - }) - .collect::>(); - - let diffable_pubkeys = [&committed_accounts, &undelegated_accounts] + let flagged_accounts: Vec<_> = + [(false, committed_accounts), (true, undelegated_accounts)] .into_iter() - .filter_map(|el| el.as_ref()) - .flatten() - .filter(|account| { - account.account.data.len() > COMMIT_STATE_SIZE_THRESHOLD + .flat_map(|(allow_undelegation, accounts)| { + accounts + .into_iter() + .flatten() + .map(move |account| (allow_undelegation, account)) }) - .map(|account| account.pubkey) - .collect::>(); + .collect(); - tokio::join!( - commit_id_fetcher.fetch_next_commit_ids( - &committed_pubkeys, - min_context_slot - ), - commit_id_fetcher.get_base_accounts( - diffable_pubkeys.as_slice(), - min_context_slot - ) + // Get commit nonces and base accounts + let min_context_slot = flagged_accounts + .iter() + .map(|(_, account)| account.remote_slot) + .max() + .unwrap_or(0); + let (commit_ids, base_accounts) = tokio::join!( + Self::fetch_commit_nonces( + task_info_fetcher, + &flagged_accounts, + min_context_slot + ), + Self::fetch_diffable_accounts( + task_info_fetcher, + &flagged_accounts, + min_context_slot ) - }; - + ); let commit_ids = commit_ids.map_err(TaskBuilderError::CommitTasksBuildError)?; let mut base_accounts = base_accounts.unwrap_or_else(|err| { @@ -166,17 +187,9 @@ impl TasksBuilder for TaskBuilderImpl { } }); - let tasks: Vec> = [ - (false, committed_accounts), - (true, undelegated_accounts), - ] + // Create commit tasks + let tasks: Vec> = flagged_accounts .into_iter() - .flat_map(|(allow_undelegation, accounts)| { - accounts - .into_iter() // Option> -> 0/1 Vec<_> - .flatten() // Vec>? only if accounts is Option; otherwise ignore this variant - .map(move |account| (allow_undelegation, account)) - }) .map(|(allow_undelegation, account)| { let commit_id = *commit_ids .get(&account.pubkey) From f2b497405490e0536df245c819ff428cc067c3e5 Mon Sep 17 00:00:00 2001 From: taco-paco Date: Tue, 20 Jan 2026 14:16:48 +0700 Subject: [PATCH 10/36] feat: update finalize_tasks creation --- .../src/tasks/task_builder.rs | 93 +++++++++---------- 1 file changed, 46 insertions(+), 47 deletions(-) diff --git a/magicblock-committor-service/src/tasks/task_builder.rs b/magicblock-committor-service/src/tasks/task_builder.rs index 274f9d892..1347f9131 100644 --- a/magicblock-committor-service/src/tasks/task_builder.rs +++ b/magicblock-committor-service/src/tasks/task_builder.rs @@ -211,7 +211,7 @@ impl TasksBuilder for TaskBuilderImpl { /// Returns [`Task`]s for Finalize stage async fn finalize_tasks( info_fetcher: &Arc, - base_intent: &ScheduledIntentBundle, + intent_bundle: &ScheduledIntentBundle, ) -> TaskBuilderResult>> { // Helper to create a finalize task fn finalize_task(account: &CommittedAccount) -> Box { @@ -261,53 +261,52 @@ impl TasksBuilder for TaskBuilderImpl { } } - match &base_intent.intent_bundle { - MagicBaseIntent::BaseActions(_) => Ok(vec![]), - MagicBaseIntent::Commit(commit) => Ok(process_commit(commit)), - MagicBaseIntent::CommitAndUndelegate(t) => { - let mut tasks = process_commit(&t.commit_action); - - // Get rent reimbursments for undelegated accounts - let accounts = t.get_committed_accounts(); - let mut min_context_slot = 0; - let pubkeys = accounts - .iter() - .map(|account| { - min_context_slot = std::cmp::max( - min_context_slot, - account.remote_slot, - ); - account.pubkey - }) - .collect::>(); - let rent_reimbursements = info_fetcher - .fetch_rent_reimbursements(&pubkeys, min_context_slot) - .await - .map_err(TaskBuilderError::FinalizedTasksBuildError)?; - - tasks.extend(accounts.iter().zip(rent_reimbursements).map( - |(account, rent_reimbursement)| { - undelegate_task(account, &rent_reimbursement) - }, - )); - - match &t.undelegate_action { - UndelegateType::Standalone => Ok(tasks), - UndelegateType::WithBaseActions(actions) => { - tasks.extend(actions.iter().map(|action| { - let task = BaseActionTask { - action: action.clone(), - }; - let task = - ArgsTask::new(ArgsTaskType::BaseAction(task)); - Box::new(task) as Box - })); - - Ok(tasks) - } - } - } + let mut tasks = Vec::new(); + if let Some(ref value) = intent_bundle.intent_bundle.commit { + tasks.extend(process_commit(value).into_iter()); } + + if let Some(ref value) = + intent_bundle.intent_bundle.commit_and_undelegate + { + tasks.extend(process_commit(&value.commit_action).into_iter()); + + // Get rent reimbursments for undelegated accounts + let accounts = value.get_committed_accounts(); + let mut min_context_slot = 0; + let pubkeys = accounts + .iter() + .map(|account| { + min_context_slot = + std::cmp::max(min_context_slot, account.remote_slot); + account.pubkey + }) + .collect::>(); + let rent_reimbursements = info_fetcher + .fetch_rent_reimbursements(&pubkeys, min_context_slot) + .await + .map_err(TaskBuilderError::FinalizedTasksBuildError)?; + + tasks.extend(accounts.iter().zip(rent_reimbursements).map( + |(account, rent_reimbursement)| { + undelegate_task(account, &rent_reimbursement) + }, + )); + + if let UndelegateType::WithBaseActions(actions) = + &value.undelegate_action + { + tasks.extend(actions.iter().map(|action| { + let task = BaseActionTask { + action: action.clone(), + }; + let task = ArgsTask::new(ArgsTaskType::BaseAction(task)); + Box::new(task) as Box + })); + } + }; + + Ok(tasks) } } From d6d8c948f99c9cc648f1ad0a22d1c664b773980f Mon Sep 17 00:00:00 2001 From: taco-paco Date: Tue, 20 Jan 2026 15:18:27 +0700 Subject: [PATCH 11/36] feat: enforce uniqueness of committed account across bundle --- .../src/tasks/task_builder.rs | 28 ++++++--- magicblock-committor-service/src/types.rs | 12 ++-- .../src/magic_scheduled_base_intent.rs | 57 ++++++++++++++++++- 3 files changed, 81 insertions(+), 16 deletions(-) diff --git a/magicblock-committor-service/src/tasks/task_builder.rs b/magicblock-committor-service/src/tasks/task_builder.rs index 1347f9131..7173a823c 100644 --- a/magicblock-committor-service/src/tasks/task_builder.rs +++ b/magicblock-committor-service/src/tasks/task_builder.rs @@ -2,8 +2,8 @@ use std::{collections::HashMap, sync::Arc}; use async_trait::async_trait; use magicblock_program::magic_scheduled_base_intent::{ - BaseAction, CommitType, CommittedAccount, MagicBaseIntent, - ScheduledIntentBundle, UndelegateType, + BaseAction, CommitType, CommittedAccount, ScheduledIntentBundle, + UndelegateType, }; use solana_account::Account; use solana_pubkey::Pubkey; @@ -134,8 +134,12 @@ impl TasksBuilder for TaskBuilderImpl { intent_bundle: &ScheduledIntentBundle, persister: &Option

, ) -> TaskBuilderResult>> { - let standalone_action_tasks = Self::create_action_tasks( - intent_bundle.standalone_actions().as_slice(), + let mut tasks = Vec::new(); + tasks.extend( + Self::create_action_tasks( + intent_bundle.standalone_actions().as_slice(), + ) + .into_iter(), ); let committed_accounts = @@ -174,7 +178,10 @@ impl TasksBuilder for TaskBuilderImpl { let commit_ids = commit_ids.map_err(TaskBuilderError::CommitTasksBuildError)?; let mut base_accounts = base_accounts.unwrap_or_else(|err| { - tracing::warn!("Failed to fetch base accounts for CommitDiff (id={}): {}; falling back to CommitState", base_intent.id, err); + tracing::warn!( + "Failed to fetch base accounts for CommitDiff (id={}): {}; falling back to CommitState", + intent_bundle.id, err + ); Default::default() }); @@ -183,12 +190,15 @@ impl TasksBuilder for TaskBuilderImpl { .iter() .for_each(|(pubkey, commit_id) | { if let Err(err) = persister.set_commit_id(intent_bundle.id, pubkey, *commit_id) { - error!("Failed to persist commit id: {}, for message id: {} with pubkey {}: {}", commit_id, intent_bundle.id, pubkey, err); + error!( + "Failed to persist commit id: {}, for message id: {} with pubkey {}: {}", + commit_id, intent_bundle.id, pubkey, err + ); } }); // Create commit tasks - let tasks: Vec> = flagged_accounts + let commit_tasks_iter = flagged_accounts .into_iter() .map(|(allow_undelegation, account)| { let commit_id = *commit_ids @@ -202,8 +212,8 @@ impl TasksBuilder for TaskBuilderImpl { account.clone(), base_account, )) as Box - }) - .collect(); + }); + tasks.extend(commit_tasks_iter); Ok(tasks) } diff --git a/magicblock-committor-service/src/types.rs b/magicblock-committor-service/src/types.rs index c71523bfd..029630cd3 100644 --- a/magicblock-committor-service/src/types.rs +++ b/magicblock-committor-service/src/types.rs @@ -1,9 +1,7 @@ use std::ops::Deref; -use magicblock_metrics::metrics; -use magicblock_program::magic_scheduled_base_intent::{ - MagicBaseIntent, ScheduledIntentBundle, -}; +use magicblock_metrics::metrics::LabelValue; +use magicblock_program::magic_scheduled_base_intent::ScheduledIntentBundle; // TODO: should be removed once cranks are supported // Ideally even now OffChain/"Manual" commits should be triggered via Tx @@ -27,3 +25,9 @@ impl Deref for ScheduleIntentBundleWrapper { &self.inner } } + +impl LabelValue for ScheduleIntentBundleWrapper { + fn value(&self) -> &str { + "intent_bundle" + } +} diff --git a/programs/magicblock/src/magic_scheduled_base_intent.rs b/programs/magicblock/src/magic_scheduled_base_intent.rs index dd407939e..d301a01df 100644 --- a/programs/magicblock/src/magic_scheduled_base_intent.rs +++ b/programs/magicblock/src/magic_scheduled_base_intent.rs @@ -91,7 +91,7 @@ impl ScheduledIntentBundle { }) } - /// Returns all accounts that will be committed on chain, + /// Returns all accounts that will be committed on Base layer, /// including the one scheduled for undelegation pub fn get_all_committed_accounts(&self) -> Vec { let committed = self.get_commit_intent_accounts(); @@ -104,6 +104,20 @@ impl ScheduledIntentBundle { .collect() } + /// Return `true` if there're account that will be committed on Base layer + pub fn has_committed_accounts(&self) -> bool { + let has_commit_intent_accounts = self + .get_commit_intent_accounts() + .and_then(|el| Some(!el.is_empty())) + .unwrap_or(false); + let has_undelegate_intent_accounts = self + .get_undelegate_intent_accounts() + .and_then(|el| Some(!el.is_empty())) + .unwrap_or(false); + + has_commit_intent_accounts || has_undelegate_intent_accounts + } + /// Returns `[CommitAndUndelegate]` intent's accounts pub fn get_undelegate_intent_accounts( &self, @@ -193,11 +207,14 @@ impl MagicIntentBundle { .map(|args| BaseAction::try_from_args(args, context)) .collect::, InstructionError>>()?; - Ok(Self { + let this = Self { commit, commit_and_undelegate, standalone_actions: actions, - }) + }; + this.post_validation(context)?; + + Ok(this) } /// Cross intent validation: @@ -230,6 +247,40 @@ impl MagicIntentBundle { .unwrap_or(Ok(())) } + /// Post cross intent validation: + /// 1. Validates that all committed accounts across the entire intent bundle + /// are globally unique by pubkey. + fn post_validation( + &self, + context: &ConstructionContext<'_, '_>, + ) -> Result<(), InstructionError> { + let mut seen = HashSet::::new(); + + let mut check = + |accounts: &Vec| -> Result<(), InstructionError> { + for a in accounts { + if !seen.insert(a.pubkey) { + ic_msg!( + context.invoke_context, + "ScheduleCommit ERR: duplicate committed account pubkey across bundle: {}", + a.pubkey + ); + return Err(InstructionError::InvalidInstructionData); + } + } + Ok(()) + }; + + if let Some(commit) = &self.commit { + check(commit.get_committed_accounts())?; + } + if let Some(cau) = &self.commit_and_undelegate { + check(cau.get_committed_accounts())?; + } + + Ok(()) + } + pub fn has_undelegate_intent(&self) -> bool { self.commit_and_undelegate.is_some() } From 739dd0bd279524862b11a3e65c041b06fce01133 Mon Sep 17 00:00:00 2001 From: taco-paco Date: Tue, 20 Jan 2026 15:35:07 +0700 Subject: [PATCH 12/36] feat: dedup committed account in magic program --- .../src/scheduled_commits_processor.rs | 64 +++---------------- .../src/magic_scheduled_base_intent.rs | 31 ++++++++- .../process_schedule_commit.rs | 7 ++ 3 files changed, 46 insertions(+), 56 deletions(-) diff --git a/magicblock-accounts/src/scheduled_commits_processor.rs b/magicblock-accounts/src/scheduled_commits_processor.rs index 92f37183e..bd50562fc 100644 --- a/magicblock-accounts/src/scheduled_commits_processor.rs +++ b/magicblock-accounts/src/scheduled_commits_processor.rs @@ -87,54 +87,6 @@ impl ScheduledCommitsProcessorImpl { } } - fn preprocess_intent( - &self, - mut base_intent: ScheduledIntentBundle, - ) -> (ScheduleIntentBundleWrapper, Vec) { - let is_undelegate = base_intent.has_undelegate_intent(); - let Some(committed_accounts) = base_intent.get_committed_accounts_mut() - else { - let intent = ScheduleIntentBundleWrapper { - inner: base_intent, - trigger_type: TriggerType::OnChain, - }; - return (intent, vec![]); - }; - - // Filter duplicate accounts - let mut seen = HashSet::with_capacity(committed_accounts.len()); - committed_accounts.retain(|account| seen.insert(account.pubkey)); - - // dump undelegated pubkeys - let pubkeys_being_undelegated: Vec<_> = committed_accounts - .iter() - .inspect(|account| { - let pubkey = account.pubkey; - if self.accounts_bank.get_account(&pubkey).is_none() { - // This doesn't affect intent validity - // We assume that intent is correct at the moment of scheduling - // All the checks are performed by runtime & magic-program at the moment of scheduling - // This log could be a sign of eviction or a bug in implementation - info!("Account got evicted from AccountsDB after intent was scheduled!"); - } - }) - .filter_map(|account| { - if is_undelegate { - Some(account.pubkey) - } else { - None - } - }) - .collect(); - - let intent = ScheduleIntentBundleWrapper { - inner: base_intent, - trigger_type: TriggerType::OnChain, - }; - - (intent, pubkeys_being_undelegated) - } - async fn process_undelegation_requests(&self, pubkeys: Vec) { let mut join_set = task::JoinSet::new(); for pubkey in pubkeys.into_iter() { @@ -332,23 +284,27 @@ impl ScheduledCommitsProcessor for ScheduledCommitsProcessorImpl { return Ok(()); } - let intents = scheduled_base_intents - .into_iter() - .map(|intent| self.preprocess_intent(intent)); + let intents = scheduled_base_intents.into_iter().map(|intent| { + ScheduleIntentBundleWrapper { + inner: intent, + trigger_type: TriggerType::OnChain, + } + }); // Add metas for intent we schedule let (intents, pubkeys_being_undelegated) = { let mut intent_metas = self.intents_meta_map.lock().expect(POISONED_MUTEX_MSG); - let mut pubkeys_being_undelegated = HashSet::new(); + let mut pubkeys_being_undelegated = HashSet::::new(); let intents = intents - .map(|(intent, undelegated)| { + .map(|intent| { intent_metas.insert( intent.id, ScheduledBaseIntentMeta::new(&intent), ); - pubkeys_being_undelegated.extend(undelegated); + pubkeys_being_undelegated + .extend(intent.get_undelegate_intent_pubkeys()); intent }) diff --git a/programs/magicblock/src/magic_scheduled_base_intent.rs b/programs/magicblock/src/magic_scheduled_base_intent.rs index d301a01df..11be857d8 100644 --- a/programs/magicblock/src/magic_scheduled_base_intent.rs +++ b/programs/magicblock/src/magic_scheduled_base_intent.rs @@ -135,8 +135,12 @@ impl ScheduledIntentBundle { Some(self.intent_bundle.commit.as_ref()?.get_committed_accounts()) } - pub fn get_committed_pubkeys(&self) -> Option> { - self.intent_bundle.get_committed_pubkeys() + pub fn get_commit_intent_pubkeys(&self) -> Option> { + self.intent_bundle.get_commit_intent_pubkeys() + } + + pub fn get_undelegate_intent_pubkeys(&self) -> Option> { + self.intent_bundle.get_undelegate_intent_pubkeys() } pub fn has_undelegate_intent(&self) -> bool { @@ -314,6 +318,18 @@ impl MagicIntentBundle { }) } + pub fn get_commit_intent_pubkeys(&self) -> Option> { + self.commit + .as_ref() + .and_then(|value| Some(value.get_committed_pubkeys())) + } + + pub fn get_undelegate_intent_pubkeys(&self) -> Option> { + self.commit_and_undelegate + .as_ref() + .and_then(|value| Some(value.get_committed_pubkeys())) + } + pub fn is_empty(&self) -> bool { let has_committed = self .commit @@ -455,6 +471,10 @@ impl CommitAndUndelegate { self.commit_action.get_committed_accounts_mut() } + pub fn get_committed_pubkeys(&self) -> Vec { + self.commit_action.get_committed_pubkeys() + } + pub fn is_empty(&self) -> bool { self.commit_action.is_empty() } @@ -717,6 +737,13 @@ impl CommitType { } } + pub fn get_committed_pubkeys(&self) -> Vec { + self.get_committed_accounts() + .iter() + .map(|account| account.pubkey) + .collect() + } + pub fn is_empty(&self) -> bool { match self { Self::Standalone(committed_accounts) => { diff --git a/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs b/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs index 4308f6219..12901f140 100644 --- a/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs +++ b/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs @@ -126,6 +126,7 @@ pub(crate) fn process_schedule_commit( // program owning the PDAs invoked us directly via CPI is sufficient // Thus we can be `invoke`d unsigned and no seeds need to be provided let mut committed_accounts: Vec = Vec::new(); + let mut seen_committed_pubkeys: HashSet = HashSet::new(); for idx in COMMITTEES_START..ix_accs_len { let acc_pubkey = get_instruction_pubkey_with_idx(transaction_context, idx as u16)?; @@ -190,6 +191,12 @@ pub(crate) fn process_schedule_commit( ); } + // Backwards-compat: merge duplicate accounts (by final restored pubkey). + // Keep the first occurrence and ignore subsequent duplicates. + if !(seen_committed_pubkeys.insert(committed.pubkey)) { + continue; + } + committed_accounts.push(committed); } From fdbf0e67162d932160057503dc90453668c5fcf7 Mon Sep 17 00:00:00 2001 From: taco-paco Date: Tue, 20 Jan 2026 16:02:13 +0700 Subject: [PATCH 13/36] wip: fix some compilation errors --- .../src/scheduled_commits_processor.rs | 15 ++-- .../intent_execution_engine.rs | 2 +- .../intent_scheduler.rs | 20 ++--- .../src/intent_executor/mod.rs | 73 +++++++++---------- .../intent_executor/single_stage_executor.rs | 8 +- .../src/magic_scheduled_base_intent.rs | 13 ++++ 6 files changed, 69 insertions(+), 62 deletions(-) diff --git a/magicblock-accounts/src/scheduled_commits_processor.rs b/magicblock-accounts/src/scheduled_commits_processor.rs index bd50562fc..96a9b7d8a 100644 --- a/magicblock-accounts/src/scheduled_commits_processor.rs +++ b/magicblock-accounts/src/scheduled_commits_processor.rs @@ -20,9 +20,7 @@ use magicblock_committor_service::{ types::{ScheduleIntentBundleWrapper, TriggerType}, BaseIntentCommittor, CommittorService, }; -use magicblock_core::{ - link::transactions::TransactionSchedulerHandle, traits::AccountsBank, -}; +use magicblock_core::link::transactions::TransactionSchedulerHandle; use magicblock_program::{ magic_scheduled_base_intent::ScheduledIntentBundle, register_scheduled_commit_sent, SentCommit, TransactionScheduler, @@ -303,8 +301,11 @@ impl ScheduledCommitsProcessor for ScheduledCommitsProcessorImpl { intent.id, ScheduledBaseIntentMeta::new(&intent), ); - pubkeys_being_undelegated - .extend(intent.get_undelegate_intent_pubkeys()); + if let Some(undelegate) = + intent.get_undelegate_intent_pubkeys() + { + pubkeys_being_undelegated.extend(undelegate); + } intent }) @@ -350,9 +351,7 @@ impl ScheduledBaseIntentMeta { slot: intent.slot, blockhash: intent.blockhash, payer: intent.payer, - included_pubkeys: intent - .get_committed_pubkeys() - .unwrap_or_default(), + included_pubkeys: intent.get_all_committed_pubkeys(), intent_sent_transaction: intent .intent_bundle_sent_transaction .clone(), diff --git a/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs b/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs index 0a0089563..ad6e4a9c1 100644 --- a/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs +++ b/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs @@ -351,7 +351,7 @@ where if intent.has_undelegate_intent() && result.is_err() { warn!( "Intent execution resulted in stuck accounts: {:?}", - intent.get_committed_pubkeys() + intent.get_undelegate_intent_pubkeys() ); } } diff --git a/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs b/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs index d5b2a2774..1ee9ccfab 100644 --- a/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs +++ b/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs @@ -80,9 +80,9 @@ impl IntentScheduler { /// otherwise consumes it and enqueues pub fn schedule( &mut self, - base_intent: ScheduleIntentBundleWrapper, + intent_bundle: ScheduleIntentBundleWrapper, ) -> Option { - let intent_id = base_intent.inner.id; + let intent_id = intent_bundle.inner.id; // To check duplicate scheduling its enough to check: // 1. currently blocked @@ -107,8 +107,9 @@ impl IntentScheduler { return None; } - let Some(pubkeys) = base_intent.inner.get_committed_pubkeys() else { - return Some(base_intent); + let pubkeys = intent_bundle.get_all_committed_pubkeys(); + if pubkeys.is_empty() { + return Some(intent_bundle); }; // Check if there are any conflicting keys @@ -129,12 +130,12 @@ impl IntentScheduler { intent_id, IntentMeta { num_keys: pubkeys.len(), - intent: base_intent, + intent: intent_bundle, }, ); None } else { - Some(base_intent) + Some(intent_bundle) } } @@ -143,11 +144,12 @@ impl IntentScheduler { /// NOTE: this shall be called on executing intents to finilize their execution. pub fn complete( &mut self, - base_intent: &ScheduledIntentBundle, + intent_bundle: &ScheduledIntentBundle, ) -> IntentSchedulerResult<()> { // Release data for completed intent - let intent_id = base_intent.id; - let Some(pubkeys) = base_intent.get_committed_pubkeys() else { + let intent_id = intent_bundle.id; + let pubkeys = intent_bundle.get_all_committed_pubkeys(); + if pubkeys.is_empty() { // This means BaseAction, it doesn't have to be scheduled return Ok(()); }; diff --git a/magicblock-committor-service/src/intent_executor/mod.rs b/magicblock-committor-service/src/intent_executor/mod.rs index 82cbcc05f..ad38f2127 100644 --- a/magicblock-committor-service/src/intent_executor/mod.rs +++ b/magicblock-committor-service/src/intent_executor/mod.rs @@ -139,61 +139,56 @@ where async fn execute_inner( &mut self, - base_intent: ScheduledIntentBundle, + intent_bundle: ScheduledIntentBundle, persister: &Option

, ) -> IntentExecutorResult { - if base_intent.is_empty() { + if intent_bundle.is_empty() { return Err(IntentExecutorError::EmptyIntentError); } + let all_committed_pubkeys = intent_bundle.get_all_committed_pubkeys(); + // Update tasks status to Pending - if let Some(pubkeys) = base_intent.get_committed_pubkeys() { + { let update_status = CommitStatus::Pending; persist_status_update_by_message_set( persister, - base_intent.id, - &pubkeys, + intent_bundle.id, + &all_committed_pubkeys, update_status, ); } - let committed_pubkeys = match base_intent.get_committed_pubkeys() { - Some(value) => value, - None => { - // Build tasks for commit stage - let commit_tasks = TaskBuilderImpl::commit_tasks( - &self.task_info_fetcher, - &base_intent, - persister, - ) - .await?; + if all_committed_pubkeys.is_empty() { + // Build tasks for commit stage + let commit_tasks = TaskBuilderImpl::commit_tasks( + &self.task_info_fetcher, + &intent_bundle, + persister, + ) + .await?; - // Standalone actions executed in single stage - let strategy = TaskStrategist::build_strategy( - commit_tasks, - &self.authority.pubkey(), - persister, - )?; - return self - .single_stage_execution_flow( - base_intent, - strategy, - persister, - ) - .await; - } + // Standalone actions executed in single stage + let strategy = TaskStrategist::build_strategy( + commit_tasks, + &self.authority.pubkey(), + persister, + )?; + return self + .single_stage_execution_flow(intent_bundle, strategy, persister) + .await; }; // Build tasks for commit & finalize stages let (commit_tasks, finalize_tasks) = { let commit_tasks_fut = TaskBuilderImpl::commit_tasks( &self.task_info_fetcher, - &base_intent, + &intent_bundle, persister, ); let finalize_tasks_fut = TaskBuilderImpl::finalize_tasks( &self.task_info_fetcher, - &base_intent, + &intent_bundle, ); let (commit_tasks, finalize_tasks) = join(commit_tasks_fut, finalize_tasks_fut).await; @@ -211,7 +206,7 @@ where StrategyExecutionMode::SingleStage(strategy) => { trace!("Executing intent in single stage"); self.single_stage_execution_flow( - base_intent, + intent_bundle, strategy, persister, ) @@ -223,7 +218,7 @@ where } => { trace!("Executing intent in two stages"); self.two_stage_execution_flow( - &committed_pubkeys, + &all_committed_pubkeys, commit_stage, finalize_stage, persister, @@ -243,9 +238,9 @@ where let mut single_stage_executor = SingleStageExecutor::new(self, transaction_strategy); - let committed_pubkeys = base_intent.get_committed_pubkeys(); + let committed_pubkeys = base_intent.get_all_committed_pubkeys(); let res = single_stage_executor - .execute(committed_pubkeys.as_deref(), persister) + .execute(&committed_pubkeys, persister) .await; // Here we continue only IF the error is CpiLimitError @@ -259,7 +254,7 @@ where err @ TransactionStrategyExecutionError::CpiLimitError(_, _), commit_signature: _, finalize_signature: _, - }) if committed_pubkeys.is_some() => err, + }) if !committed_pubkeys.is_empty() => err, res => { self.junk.push(transaction_strategy); return res; @@ -269,8 +264,6 @@ where // With actions, we can't predict num of CPIs // If we get here we will try to switch from Single stage to Two Stage commit // Note that this not necessarily will pass at the end due to the same reason - // SAFETY: is_some() checked prior - let committed_pubkeys = committed_pubkeys.unwrap(); let (commit_strategy, finalize_strategy, cleanup) = self.handle_cpi_limit_error(transaction_strategy); self.junk.push(cleanup); @@ -780,10 +773,10 @@ where ) -> IntentExecutionResult { let message_id = base_intent.id; let is_undelegate = base_intent.has_undelegate_intent(); - let pubkeys = base_intent.get_committed_pubkeys(); + let pubkeys = base_intent.get_all_committed_pubkeys(); let result = self.execute_inner(base_intent, &persister).await; - if let Some(pubkeys) = pubkeys { + if !pubkeys.is_empty() { // Reset TaskInfoFetcher, as cache could become invalid // NOTE: if undelegation was removed - we still reset // We assume its safe since all consecutive commits will fail diff --git a/magicblock-committor-service/src/intent_executor/single_stage_executor.rs b/magicblock-committor-service/src/intent_executor/single_stage_executor.rs index 77c302e5b..0c0a1e0a6 100644 --- a/magicblock-committor-service/src/intent_executor/single_stage_executor.rs +++ b/magicblock-committor-service/src/intent_executor/single_stage_executor.rs @@ -45,7 +45,7 @@ where pub async fn execute( &mut self, - committed_pubkeys: Option<&[Pubkey]>, + committed_pubkeys: &[Pubkey], persister: &Option

, ) -> IntentExecutorResult { const RECURSION_CEILING: u8 = 10; @@ -119,12 +119,12 @@ where inner: &IntentExecutorImpl, err: &TransactionStrategyExecutionError, transaction_strategy: &mut TransactionStrategy, - committed_pubkeys: Option<&[Pubkey]>, + committed_pubkeys: &[Pubkey], ) -> IntentExecutorResult> { - let Some(committed_pubkeys) = committed_pubkeys else { + if committed_pubkeys.is_empty() { // No patching is applicable if intent doesn't commit accounts return Ok(ControlFlow::Break(())); - }; + } match err { TransactionStrategyExecutionError::ActionsError(_, _) => { diff --git a/programs/magicblock/src/magic_scheduled_base_intent.rs b/programs/magicblock/src/magic_scheduled_base_intent.rs index 11be857d8..69307d536 100644 --- a/programs/magicblock/src/magic_scheduled_base_intent.rs +++ b/programs/magicblock/src/magic_scheduled_base_intent.rs @@ -104,6 +104,19 @@ impl ScheduledIntentBundle { .collect() } + /// Returns pubkeys of all accounts that will be committed on Base layer, + /// including the one scheduled for undelegation + pub fn get_all_committed_pubkeys(&self) -> Vec { + [ + self.get_commit_intent_pubkeys(), + self.get_undelegate_intent_pubkeys(), + ] + .into_iter() + .filter_map(|el| el) + .flatten() + .collect() + } + /// Return `true` if there're account that will be committed on Base layer pub fn has_committed_accounts(&self) -> bool { let has_commit_intent_accounts = self From 6b5897a46988e89cecfecae539745a9c2130801f Mon Sep 17 00:00:00 2001 From: taco-paco Date: Tue, 20 Jan 2026 16:03:12 +0700 Subject: [PATCH 14/36] refactor: remove bank from ScheduledCommitsProcessor --- magicblock-accounts/src/scheduled_commits_processor.rs | 3 --- magicblock-api/src/magic_validator.rs | 1 - 2 files changed, 4 deletions(-) diff --git a/magicblock-accounts/src/scheduled_commits_processor.rs b/magicblock-accounts/src/scheduled_commits_processor.rs index 96a9b7d8a..36d96dc09 100644 --- a/magicblock-accounts/src/scheduled_commits_processor.rs +++ b/magicblock-accounts/src/scheduled_commits_processor.rs @@ -50,7 +50,6 @@ pub type ChainlinkImpl = Chainlink< >; pub struct ScheduledCommitsProcessorImpl { - accounts_bank: Arc, committor: Arc, chainlink: Arc, cancellation_token: CancellationToken, @@ -60,7 +59,6 @@ pub struct ScheduledCommitsProcessorImpl { impl ScheduledCommitsProcessorImpl { pub fn new( - accounts_bank: Arc, committor: Arc, chainlink: Arc, internal_transaction_scheduler: TransactionSchedulerHandle, @@ -76,7 +74,6 @@ impl ScheduledCommitsProcessorImpl { )); Self { - accounts_bank, committor, chainlink, cancellation_token, diff --git a/magicblock-api/src/magic_validator.rs b/magicblock-api/src/magic_validator.rs index f6f3a8da9..8b7646e8c 100644 --- a/magicblock-api/src/magic_validator.rs +++ b/magicblock-api/src/magic_validator.rs @@ -207,7 +207,6 @@ impl MagicValidator { let scheduled_commits_processor = committor_service.as_ref().map(|committor_service| { Arc::new(ScheduledCommitsProcessorImpl::new( - accountsdb.clone(), committor_service.clone(), chainlink.clone(), dispatch.transaction_scheduler.clone(), From d1df7d62829b30dbf060353fbfd0579eefe720c1 Mon Sep 17 00:00:00 2001 From: taco-paco Date: Tue, 20 Jan 2026 16:07:54 +0700 Subject: [PATCH 15/36] wip --- magicblock-committor-service/src/persist/commit_persister.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/magicblock-committor-service/src/persist/commit_persister.rs b/magicblock-committor-service/src/persist/commit_persister.rs index 86436959a..99ea29139 100644 --- a/magicblock-committor-service/src/persist/commit_persister.rs +++ b/magicblock-committor-service/src/persist/commit_persister.rs @@ -437,7 +437,7 @@ impl IntentPersister for Option { #[cfg(test)] mod tests { use magicblock_program::magic_scheduled_base_intent::{ - CommitAndUndelegate, CommitType, CommittedAccount, MagicBaseIntent, + CommitAndUndelegate, CommitType, CommittedAccount, MagicIntentBundle, UndelegateType, }; use solana_account::Account; From a3f4257e8753d2514a4b0e4abcb6c930beb1a9e7 Mon Sep 17 00:00:00 2001 From: taco-paco Date: Tue, 20 Jan 2026 17:55:45 +0700 Subject: [PATCH 16/36] fix: unit tests --- .../intent_scheduler.rs | 26 ++-- .../src/persist/commit_persister.rs | 8 +- .../src/stubs/changeset_committor_stub.rs | 16 +-- .../src/magic_scheduled_base_intent.rs | 122 ++++++++++-------- .../process_schedule_commit_tests.rs | 12 +- 5 files changed, 97 insertions(+), 87 deletions(-) diff --git a/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs b/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs index 1ee9ccfab..a3e122052 100644 --- a/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs +++ b/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs @@ -586,7 +586,7 @@ mod complex_blocking_test { #[cfg(test)] mod edge_cases_test { - use magicblock_program::magic_scheduled_base_intent::MagicBaseIntent; + use magicblock_program::magic_scheduled_base_intent::MagicIntentBundle; use super::*; @@ -594,7 +594,7 @@ mod edge_cases_test { fn test_intent_without_pubkeys() { let mut scheduler = IntentScheduler::new(); let mut msg = create_test_intent(1, &[], false); - msg.inner.intent_bundle = MagicBaseIntent::BaseActions(vec![]); + msg.inner.intent_bundle = MagicIntentBundle::default(); // Should execute immediately since it has no pubkeys assert!(scheduler.schedule(msg.clone()).is_some()); @@ -641,7 +641,7 @@ mod complete_error_test { let msg2 = create_test_intent(2, &[pubkey1], false); assert!(scheduler.schedule(msg2.clone()).is_none()); - msg1.inner.get_committed_accounts_mut().unwrap().pop(); + msg1.inner.get_commit_intent_accounts_mut().unwrap().pop(); // Attempt to complete msg1 - should detect corrupted state let result = scheduler.complete(&msg1.inner); @@ -662,15 +662,13 @@ mod complete_error_test { let mut msg1 = create_test_intent(1, &[pubkey1, pubkey2], false); assert!(scheduler.schedule(msg1.clone()).is_some()); - msg1.inner - .intent_bundle - .get_committed_accounts_mut() - .unwrap() - .push(CommittedAccount { + msg1.inner.intent_bundle.get_commit_intent_accounts_mut().unwrap().push( + CommittedAccount { pubkey: pubkey3, account: Account::default(), remote_slot: Default::default(), - }); + }, + ); // Attempt to complete msg1 - should detect corrupted state let result = scheduler.complete(&msg1.inner); @@ -734,7 +732,7 @@ pub(crate) fn create_test_intent( is_undelegate: bool, ) -> ScheduleIntentBundleWrapper { use magicblock_program::magic_scheduled_base_intent::{ - CommitAndUndelegate, CommitType, CommittedAccount, MagicBaseIntent, + CommitAndUndelegate, CommitType, CommittedAccount, MagicIntentBundle, ScheduledIntentBundle, UndelegateType, }; use solana_account::Account; @@ -749,7 +747,7 @@ pub(crate) fn create_test_intent( blockhash: Hash::default(), intent_bundle_sent_transaction: Transaction::default(), payer: Pubkey::default(), - intent_bundle: MagicBaseIntent::BaseActions(vec![]), + intent_bundle: MagicIntentBundle::default(), }; // Only set pubkeys if provided @@ -765,13 +763,13 @@ pub(crate) fn create_test_intent( let commit_type = CommitType::Standalone(committed_accounts); if is_undelegate { - intent.intent_bundle = - MagicBaseIntent::CommitAndUndelegate(CommitAndUndelegate { + intent.intent_bundle.commit_and_undelegate = + Some(CommitAndUndelegate { commit_action: commit_type, undelegate_action: UndelegateType::Standalone, }) } else { - intent.intent_bundle = MagicBaseIntent::Commit(commit_type); + intent.intent_bundle.commit = Some(commit_type); } } diff --git a/magicblock-committor-service/src/persist/commit_persister.rs b/magicblock-committor-service/src/persist/commit_persister.rs index 99ea29139..c3ff10988 100644 --- a/magicblock-committor-service/src/persist/commit_persister.rs +++ b/magicblock-committor-service/src/persist/commit_persister.rs @@ -437,8 +437,8 @@ impl IntentPersister for Option { #[cfg(test)] mod tests { use magicblock_program::magic_scheduled_base_intent::{ - CommitAndUndelegate, CommitType, CommittedAccount, - MagicIntentBundle, UndelegateType, + CommitAndUndelegate, CommitType, CommittedAccount, MagicIntentBundle, + UndelegateType, }; use solana_account::Account; use solana_hash::Hash; @@ -632,7 +632,7 @@ mod tests { let message = create_test_message(1, commit_only_budle()); persister.start_base_intent(&message).unwrap(); - let pubkey = message.get_committed_pubkeys().unwrap()[0]; + let pubkey = message.get_all_committed_pubkeys()[0]; // Update by message persister @@ -671,7 +671,7 @@ mod tests { let message = create_test_message(1, commit_only_budle()); persister.start_base_intent(&message).unwrap(); - let pubkey = message.get_committed_pubkeys().unwrap()[0]; + let pubkey = message.get_all_committed_pubkeys()[0]; persister.set_commit_id(1, &pubkey, 100).unwrap(); persister diff --git a/magicblock-committor-service/src/stubs/changeset_committor_stub.rs b/magicblock-committor-service/src/stubs/changeset_committor_stub.rs index 455d44352..a0885d8df 100644 --- a/magicblock-committor-service/src/stubs/changeset_committor_stub.rs +++ b/magicblock-committor-service/src/stubs/changeset_committor_stub.rs @@ -75,15 +75,13 @@ impl BaseIntentCommittor for ChangesetCommittorStub { let mut committed_accounts = self.committed_accounts.lock().unwrap(); base_intents.iter().for_each(|intent| { - let intent_committed_accounts = intent.get_committed_accounts(); - let Some(accounts) = intent_committed_accounts else { - return; - }; - - accounts.iter().for_each(|account| { - committed_accounts - .insert(account.pubkey, account.account.clone()); - }) + intent + .get_all_committed_accounts() + .iter() + .for_each(|account| { + committed_accounts + .insert(account.pubkey, account.account.clone()); + }) }) } diff --git a/programs/magicblock/src/magic_scheduled_base_intent.rs b/programs/magicblock/src/magic_scheduled_base_intent.rs index 69307d536..21272283c 100644 --- a/programs/magicblock/src/magic_scheduled_base_intent.rs +++ b/programs/magicblock/src/magic_scheduled_base_intent.rs @@ -94,58 +94,37 @@ impl ScheduledIntentBundle { /// Returns all accounts that will be committed on Base layer, /// including the one scheduled for undelegation pub fn get_all_committed_accounts(&self) -> Vec { - let committed = self.get_commit_intent_accounts(); - let undelegated = self.get_undelegate_intent_accounts(); - [committed, undelegated] - .into_iter() - .filter_map(|el| el) - .cloned() - .flatten() - .collect() + self.intent_bundle.get_all_committed_accounts() } /// Returns pubkeys of all accounts that will be committed on Base layer, /// including the one scheduled for undelegation pub fn get_all_committed_pubkeys(&self) -> Vec { - [ - self.get_commit_intent_pubkeys(), - self.get_undelegate_intent_pubkeys(), - ] - .into_iter() - .filter_map(|el| el) - .flatten() - .collect() + self.intent_bundle.get_all_committed_pubkeys() } /// Return `true` if there're account that will be committed on Base layer pub fn has_committed_accounts(&self) -> bool { - let has_commit_intent_accounts = self - .get_commit_intent_accounts() - .and_then(|el| Some(!el.is_empty())) - .unwrap_or(false); - let has_undelegate_intent_accounts = self - .get_undelegate_intent_accounts() - .and_then(|el| Some(!el.is_empty())) - .unwrap_or(false); - - has_commit_intent_accounts || has_undelegate_intent_accounts + self.intent_bundle.has_committed_accounts() } /// Returns `[CommitAndUndelegate]` intent's accounts pub fn get_undelegate_intent_accounts( &self, ) -> Option<&Vec> { - Some( - self.intent_bundle - .commit_and_undelegate - .as_ref()? - .get_committed_accounts(), - ) + self.intent_bundle.get_undelegate_intent_accounts() } /// Returns `Commit` intent's accounts pub fn get_commit_intent_accounts(&self) -> Option<&Vec> { - Some(self.intent_bundle.commit.as_ref()?.get_committed_accounts()) + self.intent_bundle.get_commit_intent_accounts() + } + + /// Returns `Commit` intent's accounts + pub fn get_commit_intent_accounts_mut( + &mut self, + ) -> Option<&mut Vec> { + self.intent_bundle.get_commit_intent_accounts_mut() } pub fn get_commit_intent_pubkeys(&self) -> Option> { @@ -302,33 +281,64 @@ impl MagicIntentBundle { self.commit_and_undelegate.is_some() } - /// Returns all the accounts that will be committed, - /// including the ones that will be undelegated as well - pub fn get_committed_accounts(&self) -> Option> { - let committed = self - .commit - .as_ref() - .map(|el| el.get_committed_accounts().to_owned()); + pub fn has_committed_accounts(&self) -> bool { + let has_commit_intent_accounts = self + .get_commit_intent_accounts() + .and_then(|el| Some(!el.is_empty())) + .unwrap_or(false); + let has_undelegate_intent_accounts = self + .get_undelegate_intent_accounts() + .and_then(|el| Some(!el.is_empty())) + .unwrap_or(false); - let undelegated = self - .commit_and_undelegate - .as_ref() - .map(|el| el.get_committed_accounts().to_owned()); + has_commit_intent_accounts || has_undelegate_intent_accounts + } - match (committed, undelegated) { - (None, None) => None, - (Some(mut a), Some(b)) => { - a.extend(b); - Some(a) - } - (Some(a), None) | (None, Some(a)) => Some(a), - } + /// Returns `[CommitAndUndelegate]` intent's accounts + pub fn get_undelegate_intent_accounts( + &self, + ) -> Option<&Vec> { + Some( + self.commit_and_undelegate + .as_ref()? + .get_committed_accounts(), + ) } - pub fn get_committed_pubkeys(&self) -> Option> { - self.get_committed_accounts().map(|accounts| { - accounts.iter().map(|account| account.pubkey).collect() - }) + /// Returns `Commit` intent's accounts + pub fn get_commit_intent_accounts(&self) -> Option<&Vec> { + Some(self.commit.as_ref()?.get_committed_accounts()) + } + + /// Returns `Commit` intent's accounts + pub fn get_commit_intent_accounts_mut( + &mut self, + ) -> Option<&mut Vec> { + Some(self.commit.as_mut()?.get_committed_accounts_mut()) + } + + /// Returns all the accounts that will be committed, + /// including the ones that will be undelegated as well + pub fn get_all_committed_accounts(&self) -> Vec { + let committed = self.get_commit_intent_accounts(); + let undelegated = self.get_undelegate_intent_accounts(); + [committed, undelegated] + .into_iter() + .filter_map(|el| el) + .cloned() + .flatten() + .collect() + } + + pub fn get_all_committed_pubkeys(&self) -> Vec { + [ + self.get_commit_intent_pubkeys(), + self.get_undelegate_intent_pubkeys(), + ] + .into_iter() + .filter_map(|el| el) + .flatten() + .collect() } pub fn get_commit_intent_pubkeys(&self) -> Option> { diff --git a/programs/magicblock/src/schedule_transactions/process_schedule_commit_tests.rs b/programs/magicblock/src/schedule_transactions/process_schedule_commit_tests.rs index 2253ed8f4..688657a3a 100644 --- a/programs/magicblock/src/schedule_transactions/process_schedule_commit_tests.rs +++ b/programs/magicblock/src/schedule_transactions/process_schedule_commit_tests.rs @@ -238,14 +238,14 @@ fn assert_first_commit( assert!(id >= &0); assert_eq!(slot, &test_clock.slot); assert_eq!(actual_payer, payer); - assert_eq!(base_intent.get_committed_pubkeys().unwrap().as_slice(), committees); + assert_eq!(intent_bundle.get_all_committed_pubkeys().as_slice(), committees); let _instruction = MagicBlockInstruction::ScheduledCommitSent((*id, 0)); // TODO(edwin) @@@ this fails in CI only with the similar to the below // left: [4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0] // right: [4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // See: https://github.com/magicblock-labs/magicblock-validator/actions/runs/18565403532/job/52924982063#step:6:1063 // assert_eq!(action_sent_transaction.data(0), instruction.try_to_vec().unwrap()); - assert_eq!(base_intent.is_undelegate(), expected_request_undelegation); + assert_eq!(intent_bundle.has_undelegate_intent(), expected_request_undelegation); } ); } @@ -520,7 +520,9 @@ mod tests { assert_accepted_actions(&processed_accepted, &payer.pubkey(), 1); // Verify the committed pubkey remapped to eATA assert_eq!( - scheduled[0].intent_bundle.get_committed_pubkeys().unwrap(), + scheduled[0] + .intent_bundle + .get_all_committed_pubkeys(), vec![eata_pubkey] ); } @@ -602,7 +604,9 @@ mod tests { assert_accepted_actions(&processed_accepted, &payer.pubkey(), 1); // Verify the committed pubkey remapped to eATA assert_eq!( - scheduled[0].intent_bundle.get_committed_pubkeys().unwrap(), + scheduled[0] + .intent_bundle + .get_all_committed_pubkeys(), vec![eata_pubkey] ); // And the intent contains undelegation From c580c37cd6e20debca336833f1a7224688955540 Mon Sep 17 00:00:00 2001 From: taco-paco Date: Tue, 20 Jan 2026 18:05:29 +0700 Subject: [PATCH 17/36] feat: add new tests for scheduler --- .../intent_scheduler.rs | 146 +++++++++++++++++- .../process_schedule_commit_tests.rs | 8 +- 2 files changed, 144 insertions(+), 10 deletions(-) diff --git a/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs b/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs index a3e122052..6be9108b2 100644 --- a/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs +++ b/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs @@ -662,13 +662,15 @@ mod complete_error_test { let mut msg1 = create_test_intent(1, &[pubkey1, pubkey2], false); assert!(scheduler.schedule(msg1.clone()).is_some()); - msg1.inner.intent_bundle.get_commit_intent_accounts_mut().unwrap().push( - CommittedAccount { + msg1.inner + .intent_bundle + .get_commit_intent_accounts_mut() + .unwrap() + .push(CommittedAccount { pubkey: pubkey3, account: Account::default(), remote_slot: Default::default(), - }, - ); + }); // Attempt to complete msg1 - should detect corrupted state let result = scheduler.complete(&msg1.inner); @@ -724,6 +726,85 @@ mod complete_error_test { } } +#[cfg(test)] +mod intent_bundle_test { + use solana_pubkey::pubkey; + + use super::*; + + /// Bundle contains BOTH Commit and CommitAndUndelegate. + /// Scheduler must treat committed pubkeys as UNION across both. + #[test] + fn test_bundle_with_commit_and_cau_blocks_on_union() { + let mut scheduler = IntentScheduler::new(); + + let a = pubkey!("1111111111111111111111111111111111111111111"); + let b = pubkey!("21111111111111111111111111111111111111111111"); + let c = pubkey!("31111111111111111111111111111111111111111111"); + + // msg1 has commit[a] and cau[b] + let msg1 = create_test_intent_bundle(1, &[a], &[b]); + + // msg2 conflicts with commit key (a) + let msg2 = create_test_intent(2, &[a], false); + // msg3 conflicts with cau key (b) + let msg3 = create_test_intent(3, &[b], false); + // msg4 is unrelated (c), should run immediately even while msg1 executes + let msg4 = create_test_intent(4, &[c], false); + + // msg1 executes immediately + let executed1 = scheduler.schedule(msg1.clone()).unwrap(); + assert_eq!(executed1, msg1); + assert_eq!(scheduler.intents_blocked(), 0); + + // msg2 and msg3 should be blocked due to union keys [a, b] + assert!(scheduler.schedule(msg2.clone()).is_none()); + assert!(scheduler.schedule(msg3.clone()).is_none()); + assert_eq!(scheduler.intents_blocked(), 2); + + // msg4 doesn't conflict, should execute immediately + assert!(scheduler.schedule(msg4.clone()).is_some()); + assert_eq!(scheduler.intents_blocked(), 2); + } + + /// After completing a bundle with both intents, the blocked intents should become eligible. + #[test] + fn test_bundle_with_commit_and_cau_unblocks_correctly() { + let mut scheduler = IntentScheduler::new(); + + let a = pubkey!("1111111111111111111111111111111111111111111"); + let b = pubkey!("21111111111111111111111111111111111111111111"); + + // msg1 has commit[a] and cau[b] + let msg1 = create_test_intent_bundle(1, &[a], &[b]); + // both should be blocked behind msg1 + let msg2 = create_test_intent(2, &[a], false); + let msg3 = create_test_intent(3, &[b], false); + + // msg1 executes immediately + let executed1 = scheduler.schedule(msg1.clone()).unwrap(); + // enqueue blockers + assert!(scheduler.schedule(msg2.clone()).is_none()); + assert!(scheduler.schedule(msg3.clone()).is_none()); + assert_eq!(scheduler.intents_blocked(), 2); + + // Complete msg1 + assert!(scheduler.complete(&executed1.inner).is_ok()); + + // Now both msg2 and msg3 are eligible (order doesn't matter) + let next1 = scheduler.pop_next_scheduled_intent().unwrap(); + assert!(next1 == msg2 || next1 == msg3); + assert_eq!(scheduler.intents_blocked(), 1); + + assert!(scheduler.complete(&next1.inner).is_ok()); + + let next2 = scheduler.pop_next_scheduled_intent().unwrap(); + assert!(next2 == msg2 || next2 == msg3); + assert_ne!(next1, next2); + assert_eq!(scheduler.intents_blocked(), 0); + } +} + // Helper function to create test intents #[cfg(test)] pub(crate) fn create_test_intent( @@ -778,3 +859,60 @@ pub(crate) fn create_test_intent( trigger_type: TriggerType::OffChain, } } + +#[cfg(test)] +pub(crate) fn create_test_intent_bundle( + id: u64, + commit_pubkeys: &[Pubkey], + commit_and_undelegate_pubkeys: &[Pubkey], +) -> ScheduleIntentBundleWrapper { + use magicblock_program::magic_scheduled_base_intent::{ + CommitAndUndelegate, CommitType, CommittedAccount, MagicIntentBundle, + ScheduledIntentBundle, UndelegateType, + }; + use solana_account::Account; + use solana_hash::Hash; + use solana_transaction::Transaction; + + use crate::types::TriggerType; + + let to_accounts = |keys: &[Pubkey]| -> Vec { + keys.iter() + .copied() + .map(|pubkey| CommittedAccount { + pubkey, + account: Account::default(), + remote_slot: Default::default(), + }) + .collect() + }; + + let mut intent = ScheduledIntentBundle { + id, + slot: 0, + blockhash: Hash::default(), + intent_bundle_sent_transaction: Transaction::default(), + payer: Pubkey::default(), + intent_bundle: MagicIntentBundle::default(), + }; + + if !commit_pubkeys.is_empty() { + intent.intent_bundle.commit = + Some(CommitType::Standalone(to_accounts(commit_pubkeys))); + } + + if !commit_and_undelegate_pubkeys.is_empty() { + intent.intent_bundle.commit_and_undelegate = + Some(CommitAndUndelegate { + commit_action: CommitType::Standalone(to_accounts( + commit_and_undelegate_pubkeys, + )), + undelegate_action: UndelegateType::Standalone, + }); + } + + ScheduleIntentBundleWrapper { + inner: intent, + trigger_type: TriggerType::OffChain, + } +} diff --git a/programs/magicblock/src/schedule_transactions/process_schedule_commit_tests.rs b/programs/magicblock/src/schedule_transactions/process_schedule_commit_tests.rs index 688657a3a..8cee50f88 100644 --- a/programs/magicblock/src/schedule_transactions/process_schedule_commit_tests.rs +++ b/programs/magicblock/src/schedule_transactions/process_schedule_commit_tests.rs @@ -520,9 +520,7 @@ mod tests { assert_accepted_actions(&processed_accepted, &payer.pubkey(), 1); // Verify the committed pubkey remapped to eATA assert_eq!( - scheduled[0] - .intent_bundle - .get_all_committed_pubkeys(), + scheduled[0].intent_bundle.get_all_committed_pubkeys(), vec![eata_pubkey] ); } @@ -604,9 +602,7 @@ mod tests { assert_accepted_actions(&processed_accepted, &payer.pubkey(), 1); // Verify the committed pubkey remapped to eATA assert_eq!( - scheduled[0] - .intent_bundle - .get_all_committed_pubkeys(), + scheduled[0].intent_bundle.get_all_committed_pubkeys(), vec![eata_pubkey] ); // And the intent contains undelegation From 2973cee0f2e621c12c605fba497b3fb68b083b3b Mon Sep 17 00:00:00 2001 From: taco-paco Date: Tue, 20 Jan 2026 18:10:41 +0700 Subject: [PATCH 18/36] feat: add intent bundle test into IntentExecutionEngine --- .../intent_execution_engine.rs | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs b/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs index ad6e4a9c1..215ffbc9c 100644 --- a/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs +++ b/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs @@ -381,7 +381,7 @@ mod tests { use crate::{ intent_execution_manager::{ db::{DummyDB, DB}, - intent_scheduler::create_test_intent, + intent_scheduler::{create_test_intent, create_test_intent_bundle}, }, intent_executor::{ error::{IntentExecutorError as ExecutorError, InternalError}, @@ -465,6 +465,32 @@ mod tests { assert_eq!(result2.id, 2); } + #[tokio::test] + async fn test_worker_handles_conflicting_bundles() { + let (sender, worker) = setup_engine(false); + let result_subscriber = worker.spawn(); + let mut result_receiver = result_subscriber.subscribe(); + + // Send two conflicting messages + let a = pubkey!("1111111111111111111111111111111111111111111"); + let b = pubkey!("21111111111111111111111111111111111111111111"); + let msg1 = create_test_intent_bundle(1, &[a], &[b]); + let msg2 = create_test_intent(2, &[a], false); + + sender.send(msg1.clone()).await.unwrap(); + sender.send(msg2.clone()).await.unwrap(); + + // First message should be processed immediately + let result1 = result_receiver.recv().await.unwrap(); + assert!(result1.is_ok()); + assert_eq!(result1.id, 1); + + // Second message should be processed after first completes + let result2 = result_receiver.recv().await.unwrap(); + assert!(result2.is_ok()); + assert_eq!(result2.id, 2); + } + #[tokio::test] async fn test_worker_handles_executor_failure() { let (sender, worker) = setup_engine(true); From 86a22374baf5c69164734f6c1768634a3f3a7c37 Mon Sep 17 00:00:00 2001 From: taco-paco Date: Tue, 20 Jan 2026 18:36:53 +0700 Subject: [PATCH 19/36] fix: integration tests compilation --- .../tests/test_intent_executor.rs | 5 +- .../tests/test_ix_commit_local.rs | 48 +++++++++++-------- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/test-integration/test-committor-service/tests/test_intent_executor.rs b/test-integration/test-committor-service/tests/test_intent_executor.rs index 74938187b..456f2dae0 100644 --- a/test-integration/test-committor-service/tests/test_intent_executor.rs +++ b/test-integration/test-committor-service/tests/test_intent_executor.rs @@ -112,6 +112,7 @@ impl TestEnv { } } +// TODO(edwin): add tests #[tokio::test] async fn test_commit_id_error_parsing() { const COUNTER_SIZE: u64 = 70; @@ -138,7 +139,7 @@ async fn test_commit_id_error_parsing() { // Invalidate ids before execution task_info_fetcher .fetch_next_commit_ids( - &intent.get_committed_pubkeys().unwrap(), + &intent.get_undelegate_intent_pubkeys().unwrap(), remote_slot, ) .await @@ -1202,7 +1203,7 @@ fn create_scheduled_intent( blockhash: Hash::new_unique(), intent_bundle_sent_transaction: Transaction::default(), payer: Pubkey::new_unique(), - intent_bundle: base_intent, + intent_bundle: base_intent.into(), } } diff --git a/test-integration/test-committor-service/tests/test_ix_commit_local.rs b/test-integration/test-committor-service/tests/test_ix_commit_local.rs index 38605fd4f..0fbb1e35f 100644 --- a/test-integration/test-committor-service/tests/test_ix_commit_local.rs +++ b/test-integration/test-committor-service/tests/test_ix_commit_local.rs @@ -196,7 +196,7 @@ async fn commit_single_account( blockhash: Hash::new_unique(), intent_bundle_sent_transaction: Transaction::default(), payer: counter_auth.pubkey(), - intent_bundle: base_intent, + intent_bundle: base_intent.into(), }, }; @@ -263,7 +263,7 @@ async fn commit_book_order_account( blockhash: Hash::new_unique(), intent_bundle_sent_transaction: Transaction::default(), payer: payer.pubkey(), - intent_bundle: base_intent, + intent_bundle: base_intent.into(), }, }; @@ -492,6 +492,7 @@ async fn commit_5_accounts_1kb( .await; } +// TODO(edwin): add tests to cover intent bundles async fn commit_8_accounts_1kb( bundle_size: usize, expected_strategies: ExpectedStrategies, @@ -587,7 +588,7 @@ async fn commit_multiple_accounts( blockhash: Hash::new_unique(), intent_bundle_sent_transaction: Transaction::default(), payer: Pubkey::new_unique(), - intent_bundle: base_intent, + intent_bundle: base_intent.into(), }) .map(|intent| ScheduleIntentBundleWrapper { trigger_type: TriggerType::OnChain, @@ -668,8 +669,8 @@ async fn ix_commit_local( tx_logs_contain(&rpc_client, &finalize_signature, "Finalize").await ); - let is_undelegate = base_intent.has_undelegate_intent(); - if is_undelegate { + let has_undelegate = base_intent.has_undelegate_intent(); + if has_undelegate { // Undelegate is part of atomic Finalization Stage assert!( tx_logs_contain(&rpc_client, &finalize_signature, "Undelegate") @@ -677,12 +678,18 @@ async fn ix_commit_local( ); } - let mut committed_accounts = base_intent - .get_committed_accounts() - .unwrap() - .iter() - .map(|el| (el.pubkey, el.clone())) - .collect::>(); + let committed_accounts = base_intent.get_commit_intent_accounts(); + let undelegated_accounts = base_intent.get_undelegate_intent_accounts(); + let mut committed_accounts: HashMap = + [(false, committed_accounts), (true, undelegated_accounts)] + .into_iter() + .flat_map(|(allow_undelegation, accounts)| { + accounts.into_iter().flatten().map(move |account| { + (account.pubkey, (allow_undelegation, account)) + }) + }) + .collect(); + let statuses = service .get_commit_statuses(base_intent.id) .await @@ -697,18 +704,19 @@ async fn ix_commit_local( .join("\n") ); - // When we finalize it is possible to also undelegate the account - let expected_owner = if is_undelegate { - program_flexi_counter::id() - } else { - dlp::id() - }; - assert_eq!(statuses.len(), committed_accounts.len()); for commit_status in statuses { - let account = committed_accounts + let (is_undelegate, account) = committed_accounts .remove(&commit_status.pubkey) .expect("Account should be persisted"); + + // When we finalize it is possible to also undelegate the account + let expected_owner = if is_undelegate { + program_flexi_counter::id() + } else { + dlp::id() + }; + let lamports = account.account.lamports; get_account!( rpc_client, @@ -722,7 +730,7 @@ async fn ix_commit_local( lamports, expected_owner, account.pubkey, - is_undelegate, + has_undelegate, ) } ); From a9973c58a19e3e4c0bc3a997676529c5853c1254 Mon Sep 17 00:00:00 2001 From: taco-paco Date: Tue, 20 Jan 2026 19:07:22 +0700 Subject: [PATCH 20/36] feat: add integration tests --- .../src/magic_scheduled_base_intent.rs | 12 +- .../tests/test_ix_commit_local.rs | 108 ++++++++++++++++-- 2 files changed, 104 insertions(+), 16 deletions(-) diff --git a/programs/magicblock/src/magic_scheduled_base_intent.rs b/programs/magicblock/src/magic_scheduled_base_intent.rs index 21272283c..9165aeeaf 100644 --- a/programs/magicblock/src/magic_scheduled_base_intent.rs +++ b/programs/magicblock/src/magic_scheduled_base_intent.rs @@ -354,19 +354,19 @@ impl MagicIntentBundle { } pub fn is_empty(&self) -> bool { - let has_committed = self + let no_committed = self .commit .as_ref() - .map(|el| !el.is_empty()) + .map(|el| el.is_empty()) .unwrap_or(false); - let has_committed_and_undelegated = self + let no_committed_and_undelegated = self .commit_and_undelegate .as_ref() - .map(|el| !el.is_empty()) + .map(|el| el.is_empty()) .unwrap_or(false); - let has_actions = !self.standalone_actions.is_empty(); + let no_actions = self.standalone_actions.is_empty(); - has_committed || has_committed_and_undelegated || has_actions + no_committed && no_committed_and_undelegated && no_actions } } diff --git a/test-integration/test-committor-service/tests/test_ix_commit_local.rs b/test-integration/test-committor-service/tests/test_ix_commit_local.rs index 0fbb1e35f..799ef7f25 100644 --- a/test-integration/test-committor-service/tests/test_ix_commit_local.rs +++ b/test-integration/test-committor-service/tests/test_ix_commit_local.rs @@ -15,7 +15,7 @@ use magicblock_committor_service::{ }; use magicblock_program::magic_scheduled_base_intent::{ CommitAndUndelegate, CommitType, CommittedAccount, MagicBaseIntent, - ScheduledIntentBundle, UndelegateType, + MagicIntentBundle, ScheduledIntentBundle, UndelegateType, }; use magicblock_rpc_client::MagicblockRpcClient; use program_flexi_counter::state::FlexiCounter; @@ -476,6 +476,37 @@ async fn test_commit_20_accounts_1kb_bundle_size_8() { .await; } +#[tokio::test] +async fn test_ix_execute_intent_bundle_commit_and_cau_simultaneously_union_of_accounts( +) { + execute_intent_bundle( + &[1024, 2048], + &[1024, 2048], + expect_strategies(&[(CommitStrategy::DiffBufferWithLookupTable, 4)]), + ) + .await; +} + +#[tokio::test] +async fn test_ix_execute_intent_bundle_commit_three_accounts_cau_one_account() { + execute_intent_bundle( + &[512, 512, 512], + &[512], + expect_strategies(&[(CommitStrategy::DiffBufferWithLookupTable, 4)]), + ) + .await; +} + +#[tokio::test] +async fn test_ix_execute_intent_bundle_mixed_fits_in_args() { + execute_intent_bundle( + &[10, 20, 10], + &[20], + expect_strategies(&[(CommitStrategy::StateArgs, 4)]), + ) + .await; +} + async fn commit_5_accounts_1kb( bundle_size: usize, expected_strategies: ExpectedStrategies, @@ -513,10 +544,9 @@ async fn commit_20_accounts_1kb( .await; } -async fn create_bundles( - bundle_size: usize, +async fn create_and_delegate_accounts( bytess: &[usize], -) -> Vec> { +) -> Vec { let mut join_set = JoinSet::new(); for bytes in bytess { let bytes = *bytes; @@ -540,7 +570,14 @@ async fn create_bundles( } // Wait for all tasks to complete - let committed = join_set.join_all().await; + join_set.join_all().await +} + +async fn create_bundles( + bundle_size: usize, + bytess: &[usize], +) -> Vec> { + let committed = create_and_delegate_accounts(bytess).await; committed .chunks(bundle_size) .map(|chunk| chunk.to_vec()) @@ -599,6 +636,56 @@ async fn commit_multiple_accounts( ix_commit_local(service, intents, expected_strategies).await; } +async fn execute_intent_bundle( + bytess_to_commit: &[usize], + bytes_to_undelegate: &[usize], + expected_strategies: ExpectedStrategies, +) { + init_logger!(); + + let validator_auth = ensure_validator_authority(); + fund_validator_auth_and_ensure_validator_fees_vault(&validator_auth).await; + + let service = CommittorService::try_start( + validator_auth.insecure_clone(), + ":memory:", + ChainConfig::local(ComputeBudgetConfig::new(1_000_000)), + ) + .unwrap(); + let service = CommittorServiceExt::new(Arc::new(service)); + + // Create bundles of committed accounts + let to_commit = create_and_delegate_accounts(bytess_to_commit); + let to_undelegate = create_and_delegate_accounts(bytes_to_undelegate); + let (committees, undelegetees) = tokio::join!(to_commit, to_undelegate); + + let mut intent_bundle = MagicIntentBundle::default(); + if !committees.is_empty() { + intent_bundle.commit = Some(CommitType::Standalone(committees)); + } + if !undelegetees.is_empty() { + intent_bundle.commit_and_undelegate = Some(CommitAndUndelegate { + commit_action: CommitType::Standalone(undelegetees), + undelegate_action: UndelegateType::Standalone, + }); + } + + // Create intent for each bundle + let intent_bundle = ScheduledIntentBundle { + id: 0, + slot: 0, + blockhash: Hash::new_unique(), + intent_bundle_sent_transaction: Transaction::default(), + payer: Pubkey::new_unique(), + intent_bundle, + }; + let intent_bundle = ScheduleIntentBundleWrapper { + trigger_type: TriggerType::OnChain, + inner: intent_bundle, + }; + ix_commit_local(service, vec![intent_bundle], expected_strategies).await; +} + // TODO(thlorenz/snawaz): once delegation program supports larger commits add the following // tests // @@ -628,24 +715,25 @@ async fn commit_multiple_accounts( // ----------------- async fn ix_commit_local( service: CommittorServiceExt, - base_intents: Vec, + intent_bundles: Vec, expected_strategies: ExpectedStrategies, ) { let execution_outputs = service - .schedule_base_intents_waiting(base_intents.clone()) + .schedule_base_intents_waiting(intent_bundles.clone()) .await .unwrap() .into_iter() .collect::>(); // Assert that all completed - assert_eq!(execution_outputs.len(), base_intents.len()); + assert_eq!(execution_outputs.len(), intent_bundles.len()); service.release_common_pubkeys().await.unwrap(); let rpc_client = RpcClient::new("http://localhost:7799".to_string()); let mut strategies = ExpectedStrategies::new(); - for (execution_result, base_intent) in - execution_outputs.into_iter().zip(base_intents.into_iter()) + for (execution_result, base_intent) in execution_outputs + .into_iter() + .zip(intent_bundles.into_iter()) { let output = execution_result.inner.unwrap(); let (commit_signature, finalize_signature) = match output { From 99b464025372bab8c61e51a8026d05e33a74e225 Mon Sep 17 00:00:00 2001 From: taco-paco Date: Tue, 20 Jan 2026 19:36:26 +0700 Subject: [PATCH 21/36] feat: remove ScheduleIntentBundleWrapper --- .../src/scheduled_commits_processor.rs | 59 +++++---------- .../src/committor_processor.rs | 6 +- .../src/intent_execution_manager.rs | 10 +-- .../src/intent_execution_manager/db.rs | 33 +++++---- .../intent_execution_engine.rs | 52 ++++++-------- .../intent_scheduler.rs | 71 ++++++++----------- magicblock-committor-service/src/lib.rs | 10 ++- magicblock-committor-service/src/service.rs | 18 ++--- .../src/service_ext.rs | 22 +++--- .../src/stubs/changeset_committor_stub.rs | 26 +++---- magicblock-committor-service/src/types.rs | 33 --------- .../transaction_scheduler.rs | 16 ++--- .../tests/test_ix_commit_local.rs | 2 +- 13 files changed, 139 insertions(+), 219 deletions(-) delete mode 100644 magicblock-committor-service/src/types.rs diff --git a/magicblock-accounts/src/scheduled_commits_processor.rs b/magicblock-accounts/src/scheduled_commits_processor.rs index 36d96dc09..1972d8496 100644 --- a/magicblock-accounts/src/scheduled_commits_processor.rs +++ b/magicblock-accounts/src/scheduled_commits_processor.rs @@ -16,9 +16,7 @@ use magicblock_chainlink::{ }; use magicblock_committor_service::{ intent_execution_manager::BroadcastedIntentExecutionResult, - intent_executor::ExecutionOutput, - types::{ScheduleIntentBundleWrapper, TriggerType}, - BaseIntentCommittor, CommittorService, + intent_executor::ExecutionOutput, BaseIntentCommittor, CommittorService, }; use magicblock_core::link::transactions::TransactionSchedulerHandle; use magicblock_program::{ @@ -155,14 +153,6 @@ impl ScheduledCommitsProcessorImpl { }; let intent_id = execution_result.id; - let trigger_type = execution_result.trigger_type; - // Here we handle on OnChain triggered intent - // TODO: should be removed once crank supported - if matches!(trigger_type, TriggerType::OffChain) { - info!("OffChain triggered BaseIntent executed: {}", intent_id); - continue; - } - // Remove intent from metas let intent_meta = if let Some(intent_meta) = intents_meta_map .lock() @@ -272,51 +262,36 @@ impl ScheduledCommitsProcessorImpl { #[async_trait] impl ScheduledCommitsProcessor for ScheduledCommitsProcessorImpl { async fn process(&self) -> ScheduledCommitsProcessorResult<()> { - let scheduled_base_intents = - self.transaction_scheduler.take_scheduled_actions(); + let intent_bundles = + self.transaction_scheduler.take_scheduled_intent_bundles(); - if scheduled_base_intents.is_empty() { + if intent_bundles.is_empty() { return Ok(()); } - let intents = scheduled_base_intents.into_iter().map(|intent| { - ScheduleIntentBundleWrapper { - inner: intent, - trigger_type: TriggerType::OnChain, - } - }); - // Add metas for intent we schedule - let (intents, pubkeys_being_undelegated) = { + let pubkeys_being_undelegated = { let mut intent_metas = self.intents_meta_map.lock().expect(POISONED_MUTEX_MSG); let mut pubkeys_being_undelegated = HashSet::::new(); - let intents = intents - .map(|intent| { - intent_metas.insert( - intent.id, - ScheduledBaseIntentMeta::new(&intent), - ); - if let Some(undelegate) = - intent.get_undelegate_intent_pubkeys() - { - pubkeys_being_undelegated.extend(undelegate); - } - - intent - }) - .collect::>(); + intent_bundles.iter().for_each(|intent| { + intent_metas + .insert(intent.id, ScheduledBaseIntentMeta::new(&intent)); + if let Some(undelegate) = intent.get_undelegate_intent_pubkeys() + { + pubkeys_being_undelegated.extend(undelegate); + } + }); - ( - intents, - pubkeys_being_undelegated.into_iter().collect::>(), - ) + pubkeys_being_undelegated.into_iter().collect::>() }; self.process_undelegation_requests(pubkeys_being_undelegated) .await; - self.committor.schedule_base_intent(intents).await??; + self.committor + .schedule_intent_bundles(intent_bundles) + .await??; Ok(()) } diff --git a/magicblock-committor-service/src/committor_processor.rs b/magicblock-committor-service/src/committor_processor.rs index ca0fc3a90..cd5e69e1f 100644 --- a/magicblock-committor-service/src/committor_processor.rs +++ b/magicblock-committor-service/src/committor_processor.rs @@ -1,5 +1,6 @@ use std::{collections::HashSet, path::Path, sync::Arc}; +use magicblock_program::magic_scheduled_base_intent::ScheduledIntentBundle; use magicblock_rpc_client::MagicblockRpcClient; use magicblock_table_mania::{GarbageCollectorConfig, TableMania}; use solana_keypair::Keypair; @@ -19,7 +20,6 @@ use crate::{ CommitStatusRow, IntentPersister, IntentPersisterImpl, MessageSignatures, }, - types::ScheduleIntentBundleWrapper, }; pub(crate) struct CommittorProcessor { @@ -123,11 +123,11 @@ impl CommittorProcessor { pub async fn schedule_intent_bundle( &self, - intent_bundles: Vec, + intent_bundles: Vec, ) -> CommittorServiceResult<()> { let base_intent_bundles = intent_bundles .iter() - .map(|base_intent| base_intent.inner.clone()) + .map(|base_intent| base_intent.clone()) .collect::>(); if let Err(err) = self.persister.start_base_intents(&base_intent_bundles) diff --git a/magicblock-committor-service/src/intent_execution_manager.rs b/magicblock-committor-service/src/intent_execution_manager.rs index ef919113e..bb13063c6 100644 --- a/magicblock-committor-service/src/intent_execution_manager.rs +++ b/magicblock-committor-service/src/intent_execution_manager.rs @@ -5,6 +5,7 @@ pub mod intent_scheduler; use std::sync::Arc; pub use intent_execution_engine::BroadcastedIntentExecutionResult; +use magicblock_program::magic_scheduled_base_intent::ScheduledIntentBundle; use magicblock_rpc_client::MagicblockRpcClient; use magicblock_table_mania::TableMania; use tokio::sync::{broadcast, mpsc, mpsc::error::TrySendError}; @@ -19,14 +20,13 @@ use crate::{ task_info_fetcher::CacheTaskInfoFetcher, }, persist::IntentPersister, - types::ScheduleIntentBundleWrapper, ComputeBudgetConfig, }; pub struct IntentExecutionManager { db: Arc, result_subscriber: ResultSubscriber, - intent_sender: mpsc::Sender, + intent_sender: mpsc::Sender, } impl IntentExecutionManager { @@ -69,14 +69,14 @@ impl IntentExecutionManager { /// Intents will be extracted and handled in the [`IntentExecutionEngine`] pub async fn schedule( &self, - intent_bundles: Vec, + intent_bundles: Vec, ) -> Result<(), IntentExecutionManagerError> { // If db not empty push el-t there // This means that at some point channel got full // Worker first will clean-up channel, and then DB. // Pushing into channel would break order of commits if !self.db.is_empty() { - self.db.store_base_intents(intent_bundles).await?; + self.db.store_intent_bundles(intent_bundles).await?; return Ok(()); } @@ -93,7 +93,7 @@ impl IntentExecutionManager { } TrySendError::Full(el) => self .db - .store_base_intent(el) + .store_intent_bundle(el) .await .map_err(IntentExecutionManagerError::from), }?; diff --git a/magicblock-committor-service/src/intent_execution_manager/db.rs b/magicblock-committor-service/src/intent_execution_manager/db.rs index 1a83d63e9..be2b5c211 100644 --- a/magicblock-committor-service/src/intent_execution_manager/db.rs +++ b/magicblock-committor-service/src/intent_execution_manager/db.rs @@ -3,31 +3,30 @@ use std::{collections::VecDeque, sync::Mutex}; /// DB for storing intents that overflow committor channel use async_trait::async_trait; use magicblock_metrics::metrics; - -use crate::types::ScheduleIntentBundleWrapper; +use magicblock_program::magic_scheduled_base_intent::ScheduledIntentBundle; const POISONED_MUTEX_MSG: &str = "Dummy db mutex poisoned"; #[async_trait] pub trait DB: Send + Sync + 'static { - async fn store_base_intent( + async fn store_intent_bundle( &self, - base_intent: ScheduleIntentBundleWrapper, + intent_bundle: ScheduledIntentBundle, ) -> DBResult<()>; - async fn store_base_intents( + async fn store_intent_bundles( &self, - base_intents: Vec, + intent_bundles: Vec, ) -> DBResult<()>; /// Returns intent with smallest id - async fn pop_base_intent( + async fn pop_intent_bundle( &self, - ) -> DBResult>; + ) -> DBResult>; fn is_empty(&self) -> bool; } pub(crate) struct DummyDB { - db: Mutex>, + db: Mutex>, } impl DummyDB { @@ -40,31 +39,31 @@ impl DummyDB { #[async_trait] impl DB for DummyDB { - async fn store_base_intent( + async fn store_intent_bundle( &self, - base_intent: ScheduleIntentBundleWrapper, + intent_bundle: ScheduledIntentBundle, ) -> DBResult<()> { let mut db = self.db.lock().expect(POISONED_MUTEX_MSG); - db.push_back(base_intent); + db.push_back(intent_bundle); metrics::set_committor_intents_backlog_count(db.len() as i64); Ok(()) } - async fn store_base_intents( + async fn store_intent_bundles( &self, - base_intents: Vec, + intent_bundles: Vec, ) -> DBResult<()> { let mut db = self.db.lock().expect(POISONED_MUTEX_MSG); - db.extend(base_intents); + db.extend(intent_bundles); metrics::set_committor_intents_backlog_count(db.len() as i64); Ok(()) } - async fn pop_base_intent( + async fn pop_intent_bundle( &self, - ) -> DBResult> { + ) -> DBResult> { let mut db = self.db.lock().expect(POISONED_MUTEX_MSG); let res = db.pop_front(); diff --git a/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs b/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs index 215ffbc9c..6daf5c133 100644 --- a/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs +++ b/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs @@ -6,6 +6,7 @@ use std::{ use futures_util::{stream::FuturesUnordered, StreamExt}; use magicblock_metrics::metrics; +use magicblock_program::magic_scheduled_base_intent::ScheduledIntentBundle; use tokio::{ sync::{ broadcast, mpsc, mpsc::error::TryRecvError, OwnedSemaphorePermit, @@ -30,7 +31,6 @@ use crate::{ ExecutionOutput, IntentExecutionResult, IntentExecutor, }, persist::IntentPersister, - types::{ScheduleIntentBundleWrapper, TriggerType}, }; const SEMAPHORE_CLOSED_MSG: &str = "Executors semaphore closed!"; @@ -43,21 +43,15 @@ pub type PatchedErrors = Vec; pub struct BroadcastedIntentExecutionResult { pub inner: Result>, pub id: u64, - pub trigger_type: TriggerType, pub patched_errors: Arc, } impl BroadcastedIntentExecutionResult { - fn new( - id: u64, - trigger_type: TriggerType, - execution_result: IntentExecutionResult, - ) -> Self { + fn new(id: u64, execution_result: IntentExecutionResult) -> Self { let inner = execution_result.inner.map_err(Arc::new); let patched_errors = execution_result.patched_errors.into(); Self { id, - trigger_type, patched_errors, inner, } @@ -88,7 +82,7 @@ pub(crate) struct IntentExecutionEngine { db: Arc, executor_factory: F, intents_persister: Option

, - receiver: mpsc::Receiver, + receiver: mpsc::Receiver, inner: Arc>, running_executors: FuturesUnordered>, @@ -106,7 +100,7 @@ where db: Arc, executor_factory: F, intents_persister: Option

, - receiver: mpsc::Receiver, + receiver: mpsc::Receiver, ) -> Self { Self { db, @@ -189,7 +183,7 @@ where /// Returns [`ScheduleIntentBundleWrapper`] or None if all intents are blocked async fn next_scheduled_intent( &mut self, - ) -> Result, IntentExecutionManagerError> + ) -> Result, IntentExecutionManagerError> { // Limit on number of intents that can be stored in scheduler const SCHEDULER_CAPACITY: usize = 1000; @@ -240,16 +234,16 @@ where /// Returns [`ScheduleIntentBundleWrapper`] from external channel async fn get_new_intent( - receiver: &mut mpsc::Receiver, + receiver: &mut mpsc::Receiver, db: &Arc, - ) -> Result { + ) -> Result { match receiver.try_recv() { Ok(val) => Ok(val), Err(TryRecvError::Empty) => { // Worker either cleaned-up congested channel and now need to clean-up DB // or we're just waiting on empty channel - if let Some(base_intent) = db.pop_base_intent().await? { - Ok(base_intent) + if let Some(intent_bundle) = db.pop_intent_bundle().await? { + Ok(intent_bundle) } else { receiver .recv() @@ -267,7 +261,7 @@ where async fn execute( mut executor: E, persister: Option

, - intent: ScheduleIntentBundleWrapper, + intent: ScheduledIntentBundle, inner_scheduler: Arc>, execution_permit: OwnedSemaphorePermit, result_sender: broadcast::Sender, @@ -275,7 +269,7 @@ where let instant = Instant::now(); // Execute an Intent - let result = executor.execute(intent.inner.clone(), persister).await; + let result = executor.execute(intent.clone(), persister).await; let _ = result.inner.as_ref().inspect_err(|err| { error!("Failed to execute BaseIntent. id: {}. {}", intent.id, err) }); @@ -284,11 +278,8 @@ where Self::execution_metrics(instant.elapsed(), &intent, &result.inner); // Broadcast result to subscribers - let broadcasted_result = BroadcastedIntentExecutionResult::new( - intent.id, - intent.trigger_type, - result, - ); + let broadcasted_result = + BroadcastedIntentExecutionResult::new(intent.id, result); if let Err(err) = result_sender.send(broadcasted_result) { warn!("No result listeners of intent execution: {}", err); } @@ -300,7 +291,7 @@ where inner_scheduler .lock() .expect(POISONED_INNER_MSG) - .complete(&intent.inner) + .complete(&intent) .expect("Valid completion of previously scheduled message"); tokio::spawn(async move { @@ -322,19 +313,23 @@ where /// Records metrics related to intent execution fn execution_metrics( execution_time: Duration, - intent: &ScheduleIntentBundleWrapper, + intent: &ScheduledIntentBundle, result: &IntentExecutorResult, ) { const EXECUTION_TIME_THRESHOLD: f64 = 5.0; + const INTENT_BUNDLE_LABEL: &str = "intent_bundle"; let intent_execution_secs = execution_time.as_secs_f64(); metrics::observe_committor_intent_execution_time_histogram( intent_execution_secs, - intent, + &INTENT_BUNDLE_LABEL, result, ); if let Err(ref err) = result { - metrics::inc_committor_failed_intents_count(intent, err); + metrics::inc_committor_failed_intents_count( + &INTENT_BUNDLE_LABEL, + err, + ); } // Loki alerts @@ -399,7 +394,7 @@ mod tests { fn setup_engine( should_fail: bool, ) -> ( - mpsc::Sender, + mpsc::Sender, MockIntentExecutionEngine, ) { let (sender, receiver) = mpsc::channel(1000); @@ -509,7 +504,6 @@ mod tests { let result = result_receiver.recv().await.unwrap(); assert!(result.inner.is_err()); assert_eq!(result.id, 1); - assert_eq!(result.trigger_type, TriggerType::OffChain); assert_eq!( result.patched_errors[0].to_string(), "User supplied actions are ill-formed: Attempt to debit an account but found no record of a prior credit.. None" @@ -530,7 +524,7 @@ mod tests { &[pubkey!("1111111111111111111111111111111111111111111")], false, ); - worker.db.store_base_intent(msg.clone()).await.unwrap(); + worker.db.store_intent_bundle(msg.clone()).await.unwrap(); // Start worker let result_subscriber = worker.spawn(); diff --git a/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs b/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs index 6be9108b2..92049346d 100644 --- a/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs +++ b/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs @@ -5,15 +5,13 @@ use solana_pubkey::Pubkey; use thiserror::Error; use tracing::error; -use crate::types::ScheduleIntentBundleWrapper; - pub(crate) const POISONED_INNER_MSG: &str = "Mutex on CommitSchedulerInner is poisoned."; type IntentID = u64; struct IntentMeta { num_keys: usize, - intent: ScheduleIntentBundleWrapper, + intent: ScheduledIntentBundle, } /// A scheduler that ensures mutually exclusive access to pubkeys across intents @@ -80,9 +78,9 @@ impl IntentScheduler { /// otherwise consumes it and enqueues pub fn schedule( &mut self, - intent_bundle: ScheduleIntentBundleWrapper, - ) -> Option { - let intent_id = intent_bundle.inner.id; + intent_bundle: ScheduledIntentBundle, + ) -> Option { + let intent_id = intent_bundle.id; // To check duplicate scheduling its enough to check: // 1. currently blocked @@ -231,7 +229,7 @@ impl IntentScheduler { // Returns [`ScheduledBaseIntent`] that can be executed pub fn pop_next_scheduled_intent( &mut self, - ) -> Option { + ) -> Option { // TODO(edwin): optimize. Create counter im IntentMeta & update let mut execute_candidates: HashMap = HashMap::new(); self.blocked_keys.iter().for_each(|(_, queue)| { @@ -373,7 +371,7 @@ mod completion_simple_test { assert_eq!(scheduler.intents_blocked(), 1); // Complete first intent - assert!(scheduler.complete(&executed.inner).is_ok()); + assert!(scheduler.complete(&executed).is_ok()); let next = scheduler.pop_next_scheduled_intent().unwrap(); assert_eq!(next, msg2); @@ -396,7 +394,7 @@ mod completion_simple_test { assert_eq!(scheduler.intents_blocked(), 2); // Complete first intent - assert!(scheduler.complete(&executed.inner).is_ok()); + assert!(scheduler.complete(&executed).is_ok()); // Second intent should now be available let expected_msg2 = scheduler.pop_next_scheduled_intent().unwrap(); @@ -404,7 +402,7 @@ mod completion_simple_test { assert_eq!(scheduler.intents_blocked(), 1); // Complete second intent - assert!(scheduler.complete(&expected_msg2.inner).is_ok()); + assert!(scheduler.complete(&expected_msg2).is_ok()); // Third intent should now be available let expected_msg3 = scheduler.pop_next_scheduled_intent().unwrap(); @@ -458,20 +456,20 @@ mod complex_blocking_test { assert_eq!(scheduler.intents_blocked(), 2); // Complete msg1 - assert!(scheduler.complete(&msg1.inner).is_ok()); + assert!(scheduler.complete(&msg1).is_ok()); // None of the intents can execute yet // msg3 is blocked msg2 // msg4 is blocked by msg3 assert!(scheduler.pop_next_scheduled_intent().is_none()); // Complete msg2 - assert!(scheduler.complete(&msg2.inner).is_ok()); + assert!(scheduler.complete(&msg2).is_ok()); // Now msg3 is unblocked let next = scheduler.pop_next_scheduled_intent().unwrap(); assert_eq!(next, msg3); assert_eq!(scheduler.intents_blocked(), 1); // Complete msg3 - assert!(scheduler.complete(&next.inner).is_ok()); + assert!(scheduler.complete(&next).is_ok()); // Now msg4 should be available let next = scheduler.pop_next_scheduled_intent().unwrap(); @@ -518,7 +516,7 @@ mod complex_blocking_test { assert_eq!(scheduler.intents_blocked(), 2); // Complete msg1 - assert!(scheduler.complete(&executed_msg1.inner).is_ok()); + assert!(scheduler.complete(&executed_msg1).is_ok()); // Now only msg2 should be available (not msg3) let expected_msg2 = scheduler.pop_next_scheduled_intent().unwrap(); @@ -528,7 +526,7 @@ mod complex_blocking_test { assert_eq!(scheduler.pop_next_scheduled_intent(), None); // Complete msg2 - assert!(scheduler.complete(&expected_msg2.inner).is_ok()); + assert!(scheduler.complete(&expected_msg2).is_ok()); // Now msg3 should be available let expected_msg3 = scheduler.pop_next_scheduled_intent().unwrap(); @@ -560,7 +558,7 @@ mod complex_blocking_test { assert_eq!(scheduler.intents_blocked(), 4); // Complete msg1 - assert!(scheduler.complete(&executed1.inner).is_ok()); + assert!(scheduler.complete(&executed1).is_ok()); // msg2 and msg4 should be available (they don't conflict) let next_msgs = [ @@ -572,7 +570,7 @@ mod complex_blocking_test { assert_eq!(scheduler.intents_blocked(), 2); // Complete msg2 - assert!(scheduler.complete(&msg2.inner).is_ok()); + assert!(scheduler.complete(&msg2).is_ok()); // msg2 and msg4 should be available (they don't conflict) let next_intents = [ scheduler.pop_next_scheduled_intent().unwrap(), @@ -594,7 +592,7 @@ mod edge_cases_test { fn test_intent_without_pubkeys() { let mut scheduler = IntentScheduler::new(); let mut msg = create_test_intent(1, &[], false); - msg.inner.intent_bundle = MagicIntentBundle::default(); + msg.intent_bundle = MagicIntentBundle::default(); // Should execute immediately since it has no pubkeys assert!(scheduler.schedule(msg.clone()).is_some()); @@ -620,7 +618,7 @@ mod complete_error_test { ); // Attempt to complete message that was never scheduled - let result = scheduler.complete(&msg.inner); + let result = scheduler.complete(&msg); assert!(matches!( result, Err(IntentSchedulerError::NonScheduledMessageError) @@ -641,10 +639,10 @@ mod complete_error_test { let msg2 = create_test_intent(2, &[pubkey1], false); assert!(scheduler.schedule(msg2.clone()).is_none()); - msg1.inner.get_commit_intent_accounts_mut().unwrap().pop(); + msg1.get_commit_intent_accounts_mut().unwrap().pop(); // Attempt to complete msg1 - should detect corrupted state - let result = scheduler.complete(&msg1.inner); + let result = scheduler.complete(&msg1); assert!(matches!( result, Err(IntentSchedulerError::CorruptedIntentError) @@ -662,8 +660,7 @@ mod complete_error_test { let mut msg1 = create_test_intent(1, &[pubkey1, pubkey2], false); assert!(scheduler.schedule(msg1.clone()).is_some()); - msg1.inner - .intent_bundle + msg1.intent_bundle .get_commit_intent_accounts_mut() .unwrap() .push(CommittedAccount { @@ -673,7 +670,7 @@ mod complete_error_test { }); // Attempt to complete msg1 - should detect corrupted state - let result = scheduler.complete(&msg1.inner); + let result = scheduler.complete(&msg1); assert!(matches!( result, Err(IntentSchedulerError::CorruptedIntentError) @@ -696,7 +693,7 @@ mod complete_error_test { scheduler.schedule(msg2.clone()); // Attempt to complete - should detect corrupted state - let result = scheduler.complete(&msg2.inner); + let result = scheduler.complete(&msg2); assert!(matches!( result, Err(IntentSchedulerError::CompletingBlockedIntentError) @@ -718,7 +715,7 @@ mod complete_error_test { assert!(scheduler.schedule(msg2.clone()).is_none()); // Attempt to complete msg2 before msg1 - should detect corrupted state - let result = scheduler.complete(&msg2.inner); + let result = scheduler.complete(&msg2); assert!(matches!( result, Err(IntentSchedulerError::CompletingBlockedIntentError) @@ -789,14 +786,14 @@ mod intent_bundle_test { assert_eq!(scheduler.intents_blocked(), 2); // Complete msg1 - assert!(scheduler.complete(&executed1.inner).is_ok()); + assert!(scheduler.complete(&executed1).is_ok()); // Now both msg2 and msg3 are eligible (order doesn't matter) let next1 = scheduler.pop_next_scheduled_intent().unwrap(); assert!(next1 == msg2 || next1 == msg3); assert_eq!(scheduler.intents_blocked(), 1); - assert!(scheduler.complete(&next1.inner).is_ok()); + assert!(scheduler.complete(&next1).is_ok()); let next2 = scheduler.pop_next_scheduled_intent().unwrap(); assert!(next2 == msg2 || next2 == msg3); @@ -811,7 +808,7 @@ pub(crate) fn create_test_intent( id: u64, pubkeys: &[Pubkey], is_undelegate: bool, -) -> ScheduleIntentBundleWrapper { +) -> ScheduledIntentBundle { use magicblock_program::magic_scheduled_base_intent::{ CommitAndUndelegate, CommitType, CommittedAccount, MagicIntentBundle, ScheduledIntentBundle, UndelegateType, @@ -820,8 +817,6 @@ pub(crate) fn create_test_intent( use solana_hash::Hash; use solana_transaction::Transaction; - use crate::types::TriggerType; - let mut intent = ScheduledIntentBundle { id, slot: 0, @@ -854,10 +849,7 @@ pub(crate) fn create_test_intent( } } - ScheduleIntentBundleWrapper { - inner: intent, - trigger_type: TriggerType::OffChain, - } + intent } #[cfg(test)] @@ -865,7 +857,7 @@ pub(crate) fn create_test_intent_bundle( id: u64, commit_pubkeys: &[Pubkey], commit_and_undelegate_pubkeys: &[Pubkey], -) -> ScheduleIntentBundleWrapper { +) -> ScheduledIntentBundle { use magicblock_program::magic_scheduled_base_intent::{ CommitAndUndelegate, CommitType, CommittedAccount, MagicIntentBundle, ScheduledIntentBundle, UndelegateType, @@ -874,8 +866,6 @@ pub(crate) fn create_test_intent_bundle( use solana_hash::Hash; use solana_transaction::Transaction; - use crate::types::TriggerType; - let to_accounts = |keys: &[Pubkey]| -> Vec { keys.iter() .copied() @@ -911,8 +901,5 @@ pub(crate) fn create_test_intent_bundle( }); } - ScheduleIntentBundleWrapper { - inner: intent, - trigger_type: TriggerType::OffChain, - } + intent } diff --git a/magicblock-committor-service/src/lib.rs b/magicblock-committor-service/src/lib.rs index 680e209c2..26a300b63 100644 --- a/magicblock-committor-service/src/lib.rs +++ b/magicblock-committor-service/src/lib.rs @@ -1,21 +1,19 @@ +mod committor_processor; mod compute_budget; pub mod config; mod consts; pub mod error; +pub mod intent_execution_manager; +pub mod intent_executor; pub mod persist; mod pubkeys_provider; mod service; pub mod service_ext; -pub mod transactions; -pub mod types; - -mod committor_processor; -pub mod intent_execution_manager; -pub mod intent_executor; #[cfg(feature = "dev-context-only-utils")] pub mod stubs; pub mod tasks; pub mod transaction_preparator; +pub mod transactions; pub(crate) mod utils; pub use compute_budget::ComputeBudgetConfig; diff --git a/magicblock-committor-service/src/service.rs b/magicblock-committor-service/src/service.rs index 08cfaefc9..4b593ec8f 100644 --- a/magicblock-committor-service/src/service.rs +++ b/magicblock-committor-service/src/service.rs @@ -1,5 +1,6 @@ use std::{path::Path, sync::Arc, time::Instant}; +use magicblock_program::magic_scheduled_base_intent::ScheduledIntentBundle; use solana_keypair::Keypair; use solana_pubkey::Pubkey; use solana_signature::Signature; @@ -22,7 +23,6 @@ use crate::{ intent_execution_manager::BroadcastedIntentExecutionResult, persist::{CommitStatusRow, MessageSignatures}, pubkeys_provider::{provide_committee_pubkeys, provide_common_pubkeys}, - types::ScheduleIntentBundleWrapper, }; #[derive(Debug)] @@ -55,7 +55,7 @@ pub enum CommittorMessage { }, ScheduleIntentBundle { /// The [`ScheduleIntentBundle`]s to commit - intent_bundle: Vec, + intent_bundles: Vec, respond_to: oneshot::Sender>, }, GetCommitStatuses { @@ -164,11 +164,11 @@ impl CommittorActor { }); } ScheduleIntentBundle { - intent_bundle, + intent_bundles, respond_to, } => { let result = - self.processor.schedule_intent_bundle(intent_bundle).await; + self.processor.schedule_intent_bundle(intent_bundles).await; if let Err(e) = respond_to.send(result) { error!("Failed to send response {:?}", e); } @@ -359,13 +359,13 @@ impl BaseIntentCommittor for CommittorService { rx } - fn schedule_base_intent( + fn schedule_intent_bundles( &self, - base_intents: Vec, + intent_bundles: Vec, ) -> oneshot::Receiver> { let (tx, rx) = oneshot::channel(); self.try_send(CommittorMessage::ScheduleIntentBundle { - intent_bundle: base_intents, + intent_bundles, respond_to: tx, }); rx @@ -440,9 +440,9 @@ pub trait BaseIntentCommittor: Send + Sync + 'static { ) -> oneshot::Receiver>; /// Commits the changeset and returns - fn schedule_base_intent( + fn schedule_intent_bundles( &self, - base_intents: Vec, + intent_bundles: Vec, ) -> oneshot::Receiver>; /// Subscribes for results of BaseIntent execution diff --git a/magicblock-committor-service/src/service_ext.rs b/magicblock-committor-service/src/service_ext.rs index 5742580ee..a43208c3e 100644 --- a/magicblock-committor-service/src/service_ext.rs +++ b/magicblock-committor-service/src/service_ext.rs @@ -7,6 +7,7 @@ use std::{ use async_trait::async_trait; use futures_util::future::join_all; +use magicblock_program::magic_scheduled_base_intent::ScheduledIntentBundle; use solana_pubkey::Pubkey; use solana_signature::Signature; use solana_transaction_status_client_types::EncodedConfirmedTransactionWithStatusMeta; @@ -18,7 +19,6 @@ use crate::{ error::{CommittorServiceError, CommittorServiceResult}, intent_execution_manager::BroadcastedIntentExecutionResult, persist::{CommitStatusRow, MessageSignatures}, - types::ScheduleIntentBundleWrapper, BaseIntentCommittor, }; @@ -28,9 +28,9 @@ const POISONED_MUTEX_MSG: &str = #[async_trait] pub trait BaseIntentCommittorExt: BaseIntentCommittor { /// Schedules Base Intents and waits for their results - async fn schedule_base_intents_waiting( + async fn schedule_intent_bundles_waiting( &self, - base_intents: Vec, + intent_bundles: Vec, ) -> BaseIntentCommitorExtResult>; } @@ -114,9 +114,9 @@ impl CommittorServiceExt { impl BaseIntentCommittorExt for CommittorServiceExt { - async fn schedule_base_intents_waiting( + async fn schedule_intent_bundles_waiting( &self, - base_intents: Vec, + base_intents: Vec, ) -> BaseIntentCommitorExtResult> { // Critical section @@ -128,14 +128,14 @@ impl BaseIntentCommittorExt .iter() .map(|intent| { let (sender, receiver) = oneshot::channel(); - match pending_messages.entry(intent.inner.id) { + match pending_messages.entry(intent.id) { Entry::Vacant(vacant) => { vacant.insert(sender); Ok(receiver) } Entry::Occupied(_) => Err( CommittorServiceExtError::RepeatingMessageError( - intent.inner.id, + intent.id, ), ), } @@ -143,7 +143,7 @@ impl BaseIntentCommittorExt .collect::, _>>()? }; - self.schedule_base_intent(base_intents).await??; + self.schedule_intent_bundles(base_intents).await??; let results = join_all(receivers.into_iter()) .await .into_iter() @@ -162,11 +162,11 @@ impl BaseIntentCommittor for CommittorServiceExt { self.inner.reserve_pubkeys_for_committee(committee, owner) } - fn schedule_base_intent( + fn schedule_intent_bundles( &self, - base_intents: Vec, + intent_bundles: Vec, ) -> oneshot::Receiver> { - self.inner.schedule_base_intent(base_intents) + self.inner.schedule_intent_bundles(intent_bundles) } fn subscribe_for_results( diff --git a/magicblock-committor-service/src/stubs/changeset_committor_stub.rs b/magicblock-committor-service/src/stubs/changeset_committor_stub.rs index a0885d8df..3c9525065 100644 --- a/magicblock-committor-service/src/stubs/changeset_committor_stub.rs +++ b/magicblock-committor-service/src/stubs/changeset_committor_stub.rs @@ -5,6 +5,7 @@ use std::{ }; use async_trait::async_trait; +use magicblock_program::magic_scheduled_base_intent::ScheduledIntentBundle; use solana_account::Account; use solana_pubkey::Pubkey; use solana_signature::Signature; @@ -21,7 +22,6 @@ use crate::{ intent_executor::ExecutionOutput, persist::{CommitStatusRow, IntentPersisterImpl, MessageSignatures}, service_ext::{BaseIntentCommitorExtResult, BaseIntentCommittorExt}, - types::{ScheduleIntentBundleWrapper, TriggerType}, BaseIntentCommittor, }; @@ -30,7 +30,7 @@ pub struct ChangesetCommittorStub { cancellation_token: CancellationToken, reserved_pubkeys_for_committee: Arc>>, #[allow(clippy::type_complexity)] - committed_changesets: Arc>>, + committed_changesets: Arc>>, committed_accounts: Arc>>, } @@ -64,9 +64,9 @@ impl BaseIntentCommittor for ChangesetCommittorStub { rx } - fn schedule_base_intent( + fn schedule_intent_bundles( &self, - base_intents: Vec, + intent_bundles: Vec, ) -> oneshot::Receiver> { let (sender, receiver) = oneshot::channel(); let _ = sender.send(Ok(())); @@ -74,7 +74,7 @@ impl BaseIntentCommittor for ChangesetCommittorStub { { let mut committed_accounts = self.committed_accounts.lock().unwrap(); - base_intents.iter().for_each(|intent| { + intent_bundles.iter().for_each(|intent| { intent .get_all_committed_accounts() .iter() @@ -87,8 +87,8 @@ impl BaseIntentCommittor for ChangesetCommittorStub { { let mut changesets = self.committed_changesets.lock().unwrap(); - base_intents.into_iter().for_each(|intent| { - changesets.insert(intent.inner.id, intent); + intent_bundles.into_iter().for_each(|intent| { + changesets.insert(intent.id, intent); }); } @@ -187,22 +187,22 @@ impl BaseIntentCommittor for ChangesetCommittorStub { #[async_trait] impl BaseIntentCommittorExt for ChangesetCommittorStub { - async fn schedule_base_intents_waiting( + async fn schedule_intent_bundles_waiting( &self, - base_intents: Vec, + intent_bundles: Vec, ) -> BaseIntentCommitorExtResult> { - self.schedule_base_intent(base_intents.clone()).await??; - let res = base_intents + self.schedule_intent_bundles(intent_bundles.clone()) + .await??; + let res = intent_bundles .into_iter() .map(|message| BroadcastedIntentExecutionResult { - id: message.inner.id, + id: message.id, inner: Ok(ExecutionOutput::TwoStage { commit_signature: Signature::new_unique(), finalize_signature: Signature::new_unique(), }), patched_errors: Arc::new(vec![]), - trigger_type: TriggerType::OnChain, }) .collect::>(); diff --git a/magicblock-committor-service/src/types.rs b/magicblock-committor-service/src/types.rs deleted file mode 100644 index 029630cd3..000000000 --- a/magicblock-committor-service/src/types.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::ops::Deref; - -use magicblock_metrics::metrics::LabelValue; -use magicblock_program::magic_scheduled_base_intent::ScheduledIntentBundle; - -// TODO: should be removed once cranks are supported -// Ideally even now OffChain/"Manual" commits should be triggered via Tx -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum TriggerType { - OnChain, - OffChain, -} - -// TODO(edwin): can be removed? -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct ScheduleIntentBundleWrapper { - pub inner: ScheduledIntentBundle, - pub trigger_type: TriggerType, -} - -impl Deref for ScheduleIntentBundleWrapper { - type Target = ScheduledIntentBundle; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl LabelValue for ScheduleIntentBundleWrapper { - fn value(&self) -> &str { - "intent_bundle" - } -} diff --git a/programs/magicblock/src/schedule_transactions/transaction_scheduler.rs b/programs/magicblock/src/schedule_transactions/transaction_scheduler.rs index 31c2cc7be..553e96316 100644 --- a/programs/magicblock/src/schedule_transactions/transaction_scheduler.rs +++ b/programs/magicblock/src/schedule_transactions/transaction_scheduler.rs @@ -18,7 +18,7 @@ use crate::{ #[derive(Clone)] pub struct TransactionScheduler { - scheduled_base_intents: Arc>>, + scheduled_intent_bundles: Arc>>, } impl Default for TransactionScheduler { @@ -31,7 +31,7 @@ impl Default for TransactionScheduler { Default::default(); } Self { - scheduled_base_intents: SCHEDULED_ACTION.clone(), + scheduled_intent_bundles: SCHEDULED_ACTION.clone(), } } } @@ -61,7 +61,7 @@ impl TransactionScheduler { &self, base_intents: Vec, ) { - self.scheduled_base_intents + self.scheduled_intent_bundles .write() .expect("scheduled_action lock poisoned") .extend(base_intents); @@ -72,7 +72,7 @@ impl TransactionScheduler { payer: &Pubkey, ) -> Vec { let commits = self - .scheduled_base_intents + .scheduled_intent_bundles .read() .expect("scheduled_action lock poisoned"); @@ -83,9 +83,9 @@ impl TransactionScheduler { .collect::>() } - pub fn take_scheduled_actions(&self) -> Vec { + pub fn take_scheduled_intent_bundles(&self) -> Vec { let mut lock = self - .scheduled_base_intents + .scheduled_intent_bundles .write() .expect("scheduled_action lock poisoned"); mem::take(&mut *lock) @@ -93,7 +93,7 @@ impl TransactionScheduler { pub fn scheduled_actions_len(&self) -> usize { let lock = self - .scheduled_base_intents + .scheduled_intent_bundles .read() .expect("scheduled_action lock poisoned"); @@ -102,7 +102,7 @@ impl TransactionScheduler { pub fn clear_scheduled_actions(&self) { let mut lock = self - .scheduled_base_intents + .scheduled_intent_bundles .write() .expect("scheduled_action lock poisoned"); lock.clear(); diff --git a/test-integration/test-committor-service/tests/test_ix_commit_local.rs b/test-integration/test-committor-service/tests/test_ix_commit_local.rs index 799ef7f25..9086dbe95 100644 --- a/test-integration/test-committor-service/tests/test_ix_commit_local.rs +++ b/test-integration/test-committor-service/tests/test_ix_commit_local.rs @@ -719,7 +719,7 @@ async fn ix_commit_local( expected_strategies: ExpectedStrategies, ) { let execution_outputs = service - .schedule_base_intents_waiting(intent_bundles.clone()) + .schedule_intent_bundles_waiting(intent_bundles.clone()) .await .unwrap() .into_iter() From c9f86c41425a0a5a617870c4ea202893dd3bafbe Mon Sep 17 00:00:00 2001 From: taco-paco Date: Tue, 20 Jan 2026 19:49:56 +0700 Subject: [PATCH 22/36] fix: lint --- .../src/scheduled_commits_processor.rs | 2 +- .../src/committor_processor.rs | 8 +--- .../src/persist/commit_persister.rs | 4 +- .../src/stubs/changeset_committor_stub.rs | 3 +- .../src/tasks/task_builder.rs | 15 +++---- .../src/magic_scheduled_base_intent.rs | 24 +++++----- .../tests/test_ix_commit_local.rs | 45 +++++++------------ 7 files changed, 38 insertions(+), 63 deletions(-) diff --git a/magicblock-accounts/src/scheduled_commits_processor.rs b/magicblock-accounts/src/scheduled_commits_processor.rs index 1972d8496..a883ab80e 100644 --- a/magicblock-accounts/src/scheduled_commits_processor.rs +++ b/magicblock-accounts/src/scheduled_commits_processor.rs @@ -277,7 +277,7 @@ impl ScheduledCommitsProcessor for ScheduledCommitsProcessorImpl { intent_bundles.iter().for_each(|intent| { intent_metas - .insert(intent.id, ScheduledBaseIntentMeta::new(&intent)); + .insert(intent.id, ScheduledBaseIntentMeta::new(intent)); if let Some(undelegate) = intent.get_undelegate_intent_pubkeys() { pubkeys_being_undelegated.extend(undelegate); diff --git a/magicblock-committor-service/src/committor_processor.rs b/magicblock-committor-service/src/committor_processor.rs index cd5e69e1f..713067623 100644 --- a/magicblock-committor-service/src/committor_processor.rs +++ b/magicblock-committor-service/src/committor_processor.rs @@ -125,13 +125,7 @@ impl CommittorProcessor { &self, intent_bundles: Vec, ) -> CommittorServiceResult<()> { - let base_intent_bundles = intent_bundles - .iter() - .map(|base_intent| base_intent.clone()) - .collect::>(); - if let Err(err) = - self.persister.start_base_intents(&base_intent_bundles) - { + if let Err(err) = self.persister.start_base_intents(&intent_bundles) { // We will still try to perform the commits, but the fact that we cannot // persist the intent is very serious and we should probably restart the // valiator diff --git a/magicblock-committor-service/src/persist/commit_persister.rs b/magicblock-committor-service/src/persist/commit_persister.rs index c3ff10988..59a2dbcff 100644 --- a/magicblock-committor-service/src/persist/commit_persister.rs +++ b/magicblock-committor-service/src/persist/commit_persister.rs @@ -544,7 +544,7 @@ mod tests { let rows = IntentPersisterImpl::create_commit_rows(&message); assert_eq!(rows.len(), 2); - assert!(rows.iter().all(|r| r.undelegate == false)); + assert!(rows.iter().all(|r| !r.undelegate)); let empty_account = rows.iter().find(|r| r.data.is_none()).unwrap(); assert_eq!(empty_account.commit_type, types::CommitType::EmptyAccount); @@ -562,7 +562,7 @@ mod tests { let rows = IntentPersisterImpl::create_commit_rows(&message); assert_eq!(rows.len(), 2); - assert!(rows.iter().all(|r| r.undelegate == true)); + assert!(rows.iter().all(|r| !r.undelegate)); } #[test] diff --git a/magicblock-committor-service/src/stubs/changeset_committor_stub.rs b/magicblock-committor-service/src/stubs/changeset_committor_stub.rs index 3c9525065..a0e3fc461 100644 --- a/magicblock-committor-service/src/stubs/changeset_committor_stub.rs +++ b/magicblock-committor-service/src/stubs/changeset_committor_stub.rs @@ -121,8 +121,7 @@ impl BaseIntentCommittor for ChangesetCommittorStub { return rx; }; - let status_rows = - IntentPersisterImpl::create_commit_rows(&base_intent.inner); + let status_rows = IntentPersisterImpl::create_commit_rows(&base_intent); tx.send(Ok(status_rows)).unwrap_or_else(|_| { tracing::error!("Failed to send commit status response"); }); diff --git a/magicblock-committor-service/src/tasks/task_builder.rs b/magicblock-committor-service/src/tasks/task_builder.rs index 7173a823c..05a5f76ec 100644 --- a/magicblock-committor-service/src/tasks/task_builder.rs +++ b/magicblock-committor-service/src/tasks/task_builder.rs @@ -98,7 +98,7 @@ impl TaskBuilderImpl { min_context_slot: u64, ) -> TaskInfoFetcherResult> { let committed_pubkeys = accounts - .into_iter() + .iter() .map(|(_, account)| account.pubkey) .collect::>(); @@ -135,12 +135,9 @@ impl TasksBuilder for TaskBuilderImpl { persister: &Option

, ) -> TaskBuilderResult>> { let mut tasks = Vec::new(); - tasks.extend( - Self::create_action_tasks( - intent_bundle.standalone_actions().as_slice(), - ) - .into_iter(), - ); + tasks.extend(Self::create_action_tasks( + intent_bundle.standalone_actions().as_slice(), + )); let committed_accounts = intent_bundle.get_commit_intent_accounts().cloned(); @@ -273,13 +270,13 @@ impl TasksBuilder for TaskBuilderImpl { let mut tasks = Vec::new(); if let Some(ref value) = intent_bundle.intent_bundle.commit { - tasks.extend(process_commit(value).into_iter()); + tasks.extend(process_commit(value)); } if let Some(ref value) = intent_bundle.intent_bundle.commit_and_undelegate { - tasks.extend(process_commit(&value.commit_action).into_iter()); + tasks.extend(process_commit(&value.commit_action)); // Get rent reimbursments for undelegated accounts let accounts = value.get_committed_accounts(); diff --git a/programs/magicblock/src/magic_scheduled_base_intent.rs b/programs/magicblock/src/magic_scheduled_base_intent.rs index 9165aeeaf..35d983e23 100644 --- a/programs/magicblock/src/magic_scheduled_base_intent.rs +++ b/programs/magicblock/src/magic_scheduled_base_intent.rs @@ -215,7 +215,7 @@ impl MagicIntentBundle { /// Cross intent validation: /// 1. Set of committed accounts shall not overlap with - /// set of undelegated accounts + /// set of undelegated accounts /// 2. None for now :) fn validate(args: &MagicIntentBundleArgs) -> Result<(), InstructionError> { let committed_set: Option> = @@ -228,16 +228,16 @@ impl MagicIntentBundle { args.commit_and_undelegate .as_ref() - .and_then(|el| { + .map(|el| { let has_cross_reference = el .committed_accounts_indices() .iter() .any(|ind| committed_set.contains(ind)); if has_cross_reference { - Some(Ok(())) + Ok(()) } else { // TODO(edwin): add msg here? - Some(Err(InstructionError::InvalidInstructionData)) + Err(InstructionError::InvalidInstructionData) } }) .unwrap_or(Ok(())) @@ -245,7 +245,7 @@ impl MagicIntentBundle { /// Post cross intent validation: /// 1. Validates that all committed accounts across the entire intent bundle - /// are globally unique by pubkey. + /// are globally unique by pubkey. fn post_validation( &self, context: &ConstructionContext<'_, '_>, @@ -284,11 +284,11 @@ impl MagicIntentBundle { pub fn has_committed_accounts(&self) -> bool { let has_commit_intent_accounts = self .get_commit_intent_accounts() - .and_then(|el| Some(!el.is_empty())) + .map(|el| !el.is_empty()) .unwrap_or(false); let has_undelegate_intent_accounts = self .get_undelegate_intent_accounts() - .and_then(|el| Some(!el.is_empty())) + .map(|el| !el.is_empty()) .unwrap_or(false); has_commit_intent_accounts || has_undelegate_intent_accounts @@ -324,9 +324,9 @@ impl MagicIntentBundle { let undelegated = self.get_undelegate_intent_accounts(); [committed, undelegated] .into_iter() - .filter_map(|el| el) - .cloned() .flatten() + .flatten() + .cloned() .collect() } @@ -336,7 +336,7 @@ impl MagicIntentBundle { self.get_undelegate_intent_pubkeys(), ] .into_iter() - .filter_map(|el| el) + .flatten() .flatten() .collect() } @@ -344,13 +344,13 @@ impl MagicIntentBundle { pub fn get_commit_intent_pubkeys(&self) -> Option> { self.commit .as_ref() - .and_then(|value| Some(value.get_committed_pubkeys())) + .map(|value| value.get_committed_pubkeys()) } pub fn get_undelegate_intent_pubkeys(&self) -> Option> { self.commit_and_undelegate .as_ref() - .and_then(|value| Some(value.get_committed_pubkeys())) + .map(|value| value.get_committed_pubkeys()) } pub fn is_empty(&self) -> bool { diff --git a/test-integration/test-committor-service/tests/test_ix_commit_local.rs b/test-integration/test-committor-service/tests/test_ix_commit_local.rs index 9086dbe95..0dce0a501 100644 --- a/test-integration/test-committor-service/tests/test_ix_commit_local.rs +++ b/test-integration/test-committor-service/tests/test_ix_commit_local.rs @@ -10,7 +10,6 @@ use magicblock_committor_service::{ intent_executor::ExecutionOutput, persist::CommitStrategy, service_ext::{BaseIntentCommittorExt, CommittorServiceExt}, - types::{ScheduleIntentBundleWrapper, TriggerType}, BaseIntentCommittor, CommittorService, ComputeBudgetConfig, }; use magicblock_program::magic_scheduled_base_intent::{ @@ -188,16 +187,13 @@ async fn commit_single_account( MagicBaseIntent::Commit(CommitType::Standalone(vec![account])) }; - let intent = ScheduleIntentBundleWrapper { - trigger_type: TriggerType::OnChain, - inner: ScheduledIntentBundle { - id: 0, - slot: 10, - blockhash: Hash::new_unique(), - intent_bundle_sent_transaction: Transaction::default(), - payer: counter_auth.pubkey(), - intent_bundle: base_intent.into(), - }, + let intent = ScheduledIntentBundle { + id: 0, + slot: 10, + blockhash: Hash::new_unique(), + intent_bundle_sent_transaction: Transaction::default(), + payer: counter_auth.pubkey(), + intent_bundle: base_intent.into(), }; // We should always be able to Commit & Finalize 1 account either with Args or Buffers @@ -255,16 +251,13 @@ async fn commit_book_order_account( MagicBaseIntent::Commit(CommitType::Standalone(vec![account])) }; - let intent = ScheduleIntentBundleWrapper { - trigger_type: TriggerType::OnChain, - inner: ScheduledIntentBundle { - id: 0, - slot: 10, - blockhash: Hash::new_unique(), - intent_bundle_sent_transaction: Transaction::default(), - payer: payer.pubkey(), - intent_bundle: base_intent.into(), - }, + let intent = ScheduledIntentBundle { + id: 0, + slot: 10, + blockhash: Hash::new_unique(), + intent_bundle_sent_transaction: Transaction::default(), + payer: payer.pubkey(), + intent_bundle: base_intent.into(), }; ix_commit_local( @@ -627,10 +620,6 @@ async fn commit_multiple_accounts( payer: Pubkey::new_unique(), intent_bundle: base_intent.into(), }) - .map(|intent| ScheduleIntentBundleWrapper { - trigger_type: TriggerType::OnChain, - inner: intent, - }) .collect::>(); ix_commit_local(service, intents, expected_strategies).await; @@ -679,10 +668,6 @@ async fn execute_intent_bundle( payer: Pubkey::new_unique(), intent_bundle, }; - let intent_bundle = ScheduleIntentBundleWrapper { - trigger_type: TriggerType::OnChain, - inner: intent_bundle, - }; ix_commit_local(service, vec![intent_bundle], expected_strategies).await; } @@ -715,7 +700,7 @@ async fn execute_intent_bundle( // ----------------- async fn ix_commit_local( service: CommittorServiceExt, - intent_bundles: Vec, + intent_bundles: Vec, expected_strategies: ExpectedStrategies, ) { let execution_outputs = service From c226198c7cde191f1b6d7b16c79ec19d077c0c7b Mon Sep 17 00:00:00 2001 From: taco-paco Date: Wed, 21 Jan 2026 16:58:40 +0700 Subject: [PATCH 23/36] fix: code rabbit comments --- .../intent_execution_engine.rs | 2 +- .../src/persist/commit_persister.rs | 2 +- .../src/tasks/task_builder.rs | 19 ++++++++---- .../src/magic_scheduled_base_intent.rs | 30 +++++++++++-------- .../tests/test_intent_executor.rs | 1 - .../tests/test_ix_commit_local.rs | 1 - 6 files changed, 32 insertions(+), 23 deletions(-) diff --git a/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs b/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs index 82ad36460..3f889daf4 100644 --- a/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs +++ b/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs @@ -181,7 +181,7 @@ where } } - /// Returns [`ScheduleIntentBundleWrapper`] or None if all intents are blocked + /// Returns [`ScheduledIntentBundle`] or None if all intents are blocked #[instrument(skip(self))] async fn next_scheduled_intent( &mut self, diff --git a/magicblock-committor-service/src/persist/commit_persister.rs b/magicblock-committor-service/src/persist/commit_persister.rs index afef5130b..f4faddc02 100644 --- a/magicblock-committor-service/src/persist/commit_persister.rs +++ b/magicblock-committor-service/src/persist/commit_persister.rs @@ -566,7 +566,7 @@ mod tests { let rows = IntentPersisterImpl::create_commit_rows(&message); assert_eq!(rows.len(), 2); - assert!(rows.iter().all(|r| !r.undelegate)); + assert!(rows.iter().all(|r| r.undelegate)); } #[test] diff --git a/magicblock-committor-service/src/tasks/task_builder.rs b/magicblock-committor-service/src/tasks/task_builder.rs index b2ed4e4ea..84075e41f 100644 --- a/magicblock-committor-service/src/tasks/task_builder.rs +++ b/magicblock-committor-service/src/tasks/task_builder.rs @@ -189,12 +189,18 @@ impl TasksBuilder for TaskBuilderImpl { }); // Create commit tasks - let commit_tasks_iter = flagged_accounts - .into_iter() - .map(|(allow_undelegation, account)| { - let commit_id = *commit_ids + let commit_tasks_iter = flagged_accounts.into_iter().map( + |(allow_undelegation, account)| { + let commit_id = commit_ids .get(&account.pubkey) - .expect("CommitIdFetcher must provide commit ids for all listed pubkeys, or error!"); + .copied() + .unwrap_or_else(|| { + // This shall not ever happen since TaskInfoFetcher + // returns commit ids for all pubkeys or throws + // If it does occur, it will be patched and retried by IntentExecutor + error!(pubkey = %account.pubkey, "Commit id absent for pubkey"); + 0 + }); let base_account = base_accounts.remove(&account.pubkey); Box::new(Self::create_commit_task( @@ -203,7 +209,8 @@ impl TasksBuilder for TaskBuilderImpl { account.clone(), base_account, )) as Box - }); + }, + ); tasks.extend(commit_tasks_iter); Ok(tasks) diff --git a/programs/magicblock/src/magic_scheduled_base_intent.rs b/programs/magicblock/src/magic_scheduled_base_intent.rs index 35d983e23..9e8a75ef7 100644 --- a/programs/magicblock/src/magic_scheduled_base_intent.rs +++ b/programs/magicblock/src/magic_scheduled_base_intent.rs @@ -64,7 +64,6 @@ pub struct ScheduledIntentBundle { pub intent_bundle_sent_transaction: Transaction, pub payer: Pubkey, /// Scheduled intent bundle - // TODO(edwin): rename pub intent_bundle: MagicIntentBundle, } @@ -187,7 +186,7 @@ impl MagicIntentBundle { args: MagicIntentBundleArgs, context: &ConstructionContext<'_, '_>, ) -> Result { - Self::validate(&args)?; + Self::validate(&args, context)?; let commit = args .commit @@ -217,7 +216,10 @@ impl MagicIntentBundle { /// 1. Set of committed accounts shall not overlap with /// set of undelegated accounts /// 2. None for now :) - fn validate(args: &MagicIntentBundleArgs) -> Result<(), InstructionError> { + fn validate( + args: &MagicIntentBundleArgs, + context: &ConstructionContext<'_, '_>, + ) -> Result<(), InstructionError> { let committed_set: Option> = args.commit.as_ref().map(|el| { el.committed_accounts_indices().iter().copied().collect() @@ -234,10 +236,13 @@ impl MagicIntentBundle { .iter() .any(|ind| committed_set.contains(ind)); if has_cross_reference { - Ok(()) - } else { - // TODO(edwin): add msg here? + ic_msg!( + context.invoke_context, + "ScheduleCommit ERR: duplicate committed account across bundle", + ); Err(InstructionError::InvalidInstructionData) + } else { + Ok(()) } }) .unwrap_or(Ok(())) @@ -251,16 +256,15 @@ impl MagicIntentBundle { context: &ConstructionContext<'_, '_>, ) -> Result<(), InstructionError> { let mut seen = HashSet::::new(); - let mut check = |accounts: &Vec| -> Result<(), InstructionError> { - for a in accounts { - if !seen.insert(a.pubkey) { + for el in accounts { + if !seen.insert(el.pubkey) { ic_msg!( - context.invoke_context, - "ScheduleCommit ERR: duplicate committed account pubkey across bundle: {}", - a.pubkey - ); + context.invoke_context, + "ScheduleCommit ERR: duplicate committed account pubkey across bundle: {}", + el.pubkey + ); return Err(InstructionError::InvalidInstructionData); } } diff --git a/test-integration/test-committor-service/tests/test_intent_executor.rs b/test-integration/test-committor-service/tests/test_intent_executor.rs index 456f2dae0..e6623ca68 100644 --- a/test-integration/test-committor-service/tests/test_intent_executor.rs +++ b/test-integration/test-committor-service/tests/test_intent_executor.rs @@ -112,7 +112,6 @@ impl TestEnv { } } -// TODO(edwin): add tests #[tokio::test] async fn test_commit_id_error_parsing() { const COUNTER_SIZE: u64 = 70; diff --git a/test-integration/test-committor-service/tests/test_ix_commit_local.rs b/test-integration/test-committor-service/tests/test_ix_commit_local.rs index 0dce0a501..18464de20 100644 --- a/test-integration/test-committor-service/tests/test_ix_commit_local.rs +++ b/test-integration/test-committor-service/tests/test_ix_commit_local.rs @@ -516,7 +516,6 @@ async fn commit_5_accounts_1kb( .await; } -// TODO(edwin): add tests to cover intent bundles async fn commit_8_accounts_1kb( bundle_size: usize, expected_strategies: ExpectedStrategies, From 1d233a56ee41b7a42cae7891b9c2e12f44e07c97 Mon Sep 17 00:00:00 2001 From: taco-paco Date: Wed, 21 Jan 2026 17:22:25 +0700 Subject: [PATCH 24/36] fix: sdk version --- test-integration/Cargo.lock | 17 ++++++++--------- test-integration/Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/test-integration/Cargo.lock b/test-integration/Cargo.lock index 432353beb..9531732bd 100644 --- a/test-integration/Cargo.lock +++ b/test-integration/Cargo.lock @@ -1690,7 +1690,7 @@ dependencies = [ [[package]] name = "ephemeral-rollups-sdk" version = "0.6.5" -source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=36f9485#36f94850e7763a1f02bf5ec36e1f2e8691dec8f8" +source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=9deb6fb5a3#9deb6fb5a3bbaf29a38f35b6a03d0fb9f69fa66f" dependencies = [ "base64ct", "borsh 1.6.0", @@ -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.3.1", + "magicblock-magic-program-api 0.6.1 (git+https://github.com/magicblock-labs/magicblock-validator.git?rev=c226198c7c)", "solana-account", "solana-account-info", "solana-cpi", @@ -1715,7 +1715,7 @@ dependencies = [ [[package]] name = "ephemeral-rollups-sdk-attribute-action" version = "0.6.5" -source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=36f9485#36f94850e7763a1f02bf5ec36e1f2e8691dec8f8" +source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=9deb6fb5a3#9deb6fb5a3bbaf29a38f35b6a03d0fb9f69fa66f" dependencies = [ "quote", "syn 1.0.109", @@ -1724,7 +1724,7 @@ dependencies = [ [[package]] name = "ephemeral-rollups-sdk-attribute-commit" version = "0.6.5" -source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=36f9485#36f94850e7763a1f02bf5ec36e1f2e8691dec8f8" +source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=9deb6fb5a3#9deb6fb5a3bbaf29a38f35b6a03d0fb9f69fa66f" dependencies = [ "quote", "syn 1.0.109", @@ -1733,7 +1733,7 @@ dependencies = [ [[package]] name = "ephemeral-rollups-sdk-attribute-delegate" version = "0.6.5" -source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=36f9485#36f94850e7763a1f02bf5ec36e1f2e8691dec8f8" +source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=9deb6fb5a3#9deb6fb5a3bbaf29a38f35b6a03d0fb9f69fa66f" dependencies = [ "proc-macro2", "quote", @@ -1743,7 +1743,7 @@ dependencies = [ [[package]] name = "ephemeral-rollups-sdk-attribute-ephemeral" version = "0.6.5" -source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=36f9485#36f94850e7763a1f02bf5ec36e1f2e8691dec8f8" +source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=9deb6fb5a3#9deb6fb5a3bbaf29a38f35b6a03d0fb9f69fa66f" dependencies = [ "proc-macro2", "quote", @@ -3655,9 +3655,7 @@ dependencies = [ [[package]] name = "magicblock-magic-program-api" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6ec80d63618a71bb4db324ec5fdca8f85552d0469a51aa063d290256d425f5" +version = "0.6.1" dependencies = [ "bincode", "serde", @@ -3667,6 +3665,7 @@ dependencies = [ [[package]] name = "magicblock-magic-program-api" version = "0.6.1" +source = "git+https://github.com/magicblock-labs/magicblock-validator.git?rev=c226198c7c#c226198c7cde191f1b6d7b16c79ec19d077c0c7b" dependencies = [ "bincode", "serde", diff --git a/test-integration/Cargo.toml b/test-integration/Cargo.toml index f975fccba..bc49aabea 100644 --- a/test-integration/Cargo.toml +++ b/test-integration/Cargo.toml @@ -36,7 +36,7 @@ chrono = "0.4" cleanass = "0.0.1" color-backtrace = { version = "0.7" } ctrlc = "3.4.7" -ephemeral-rollups-sdk = { git = "https://github.com/magicblock-labs/ephemeral-rollups-sdk.git", rev = "36f9485", default-features = false, features = [ +ephemeral-rollups-sdk = { git = "https://github.com/magicblock-labs/ephemeral-rollups-sdk.git", rev = "9deb6fb5a3", default-features = false, features = [ "modular-sdk", ] } futures = "0.3.31" From 2bf9cd997095e729539c7bd8fc8daf604b1518ca Mon Sep 17 00:00:00 2001 From: taco-paco Date: Wed, 21 Jan 2026 21:06:22 +0700 Subject: [PATCH 25/36] feat: claude code added tests for me :) Not working tho --- .../src/instruction.rs | 40 ++- .../programs/flexi-counter/src/instruction.rs | 99 ++++++ .../programs/flexi-counter/src/processor.rs | 16 +- .../src/processor/schedule_intent.rs | 191 ++++++++++- .../tests/test_schedule_intents.rs | 297 +++++++++++++++++- 5 files changed, 637 insertions(+), 6 deletions(-) diff --git a/magicblock-magic-program-api/src/instruction.rs b/magicblock-magic-program-api/src/instruction.rs index 8b4976ed6..e1ad809e0 100644 --- a/magicblock-magic-program-api/src/instruction.rs +++ b/magicblock-magic-program-api/src/instruction.rs @@ -74,10 +74,46 @@ pub enum MagicBlockInstruction { /// Args: (intent_id, bump) - bump is needed in order to guarantee unique transactions ScheduledCommitSent((u64, u64)), - /// TODO(edwin): + /// Schedules execution of a single *base intent*. + /// + /// A "base intent" is an atomic unit of work executed by the validator on the Base layer, + /// such as: + /// - executing standalone base actions (`BaseActions`) + /// - committing a set of accounts (`Commit`) + /// - committing and undelegating accounts, optionally with post-actions (`CommitAndUndelegate`) + /// + /// This instruction is the legacy/single-intent variant of scheduling. For batching multiple + /// independent intents into a single instruction, see [`MagicBlockInstruction::ScheduleIntentBundle`]. + /// + /// # Account references + /// - **0.** `[WRITE, SIGNER]` Payer requesting the intent to be scheduled + /// - **1.** `[WRITE]` Magic Context account + /// - **2..n** `[]` Accounts referenced by the intent (including action accounts) + /// + /// # Data + /// The embedded [`MagicBaseIntentArgs`] encodes account references by indices into the + /// accounts array (compact representation). ScheduleBaseIntent(MagicBaseIntentArgs), - /// TODO(edwin): + /// Schedules execution of a *bundle* of intents in a single instruction. + /// + /// A "intent bundle" is an atomic unit of work executed by the validator on the Base layer, + /// such as: + /// - standalone base actions + /// - an optional `Commit` + /// - an optional `CommitAndUndelegate` + /// + /// This is the recommended scheduling path when the caller wants to submit multiple + /// independent intents while paying account overhead only once. + /// + /// # Account references + /// - **0.** `[WRITE, SIGNER]` Payer requesting the bundle to be scheduled + /// - **1.** `[WRITE]` Magic Context account + /// - **2..n** `[]` All accounts referenced by any intent in the bundle + /// + /// # Data + /// The embedded [`MagicIntentBundleArgs`] encodes account references by indices into the + /// accounts array. ScheduleIntentBundle(MagicIntentBundleArgs), /// Schedule a new task for execution diff --git a/test-integration/programs/flexi-counter/src/instruction.rs b/test-integration/programs/flexi-counter/src/instruction.rs index 8986f4850..53e10098b 100644 --- a/test-integration/programs/flexi-counter/src/instruction.rs +++ b/test-integration/programs/flexi-counter/src/instruction.rs @@ -184,6 +184,40 @@ pub enum FlexiCounterInstruction { /// 1. `[signer]` The payer that created and is cancelling the task. /// 2. `[write]` Task context account. Cancel(CancelArgs), + + /// Creates an intent bundle that can contain both Commit and CommitAndUndelegate + /// intents simultaneously using MagicIntentBundleBuilder. + /// + /// This instruction tests the new IntentBundle feature where: + /// - commit_only_payers: accounts that will only be committed (Commit intent) + /// - undelegate_payers: accounts that will be committed and undelegated (CommitAndUndelegate intent) + /// + /// Accounts: + /// 0. `[]` Destination program + /// 1. `[]` MagicContext (used to record scheduled commit) + /// 2. `[]` MagicBlock Program (used to schedule commit) + /// 3. `[write]` Transfer destination during action + /// 4. `[]` System program + /// -- Commit only accounts -- + /// 5. `[signer]` Escrow authority for commit only + /// ... + /// 5+n-1 `[signer]` Escrow authority for commit only + /// 5+n `[write]` Counter pda for commit only + /// ... + /// 5+2n-1 `[write]` Counter pda for commit only + /// -- CommitAndUndelegate accounts -- + /// 5+2n `[signer]` Escrow authority for undelegate + /// ... + /// 5+2n+m-1 `[signer]` Escrow authority for undelegate + /// 5+2n+m `[write]` Counter pda for undelegate + /// ... + /// 5+2n+2m-1 `[write]` Counter pda for undelegate + CreateIntentBundle { + num_commit_only: u8, + num_undelegate: u8, + counter_diffs: Vec, + compute_units: u32, + }, } pub fn create_init_ix(payer: Pubkey, label: String) -> Instruction { @@ -461,3 +495,68 @@ pub fn create_cancel_task_ix(payer: Pubkey, task_id: i64) -> Instruction { accounts, ) } + +/// Creates an instruction for CreateIntentBundle that tests both Commit and +/// CommitAndUndelegate intents simultaneously. +/// +/// # Arguments +/// * `commit_only_payers` - Payers whose counters will only be committed +/// * `undelegate_payers` - Payers whose counters will be committed and undelegated +/// * `transfer_destination` - Destination for prize transfers during actions +/// * `counter_diffs` - Diffs to apply to undelegate counters after undelegation +/// * `compute_units` - Compute units for action handlers +pub fn create_intent_bundle_ix( + commit_only_payers: Vec, + undelegate_payers: Vec, + transfer_destination: Pubkey, + counter_diffs: Vec, + compute_units: u32, +) -> Instruction { + let program_id = &crate::id(); + + // Build account metas + let mut accounts = vec![ + AccountMeta::new_readonly(crate::id(), false), + AccountMeta::new(MAGIC_CONTEXT_ID, false), + AccountMeta::new_readonly(MAGIC_PROGRAM_ID, false), + AccountMeta::new_readonly(transfer_destination, false), + AccountMeta::new_readonly(system_program::id(), false), + ]; + + // Add commit-only payers (escrow authorities) + accounts.extend( + commit_only_payers + .iter() + .map(|payer| AccountMeta::new_readonly(*payer, true)), + ); + // Add commit-only counters + accounts.extend( + commit_only_payers + .iter() + .map(|payer| AccountMeta::new(FlexiCounter::pda(payer).0, false)), + ); + + // Add undelegate payers (escrow authorities) + accounts.extend( + undelegate_payers + .iter() + .map(|payer| AccountMeta::new_readonly(*payer, true)), + ); + // Add undelegate counters + accounts.extend( + undelegate_payers + .iter() + .map(|payer| AccountMeta::new(FlexiCounter::pda(payer).0, false)), + ); + + Instruction::new_with_borsh( + *program_id, + &FlexiCounterInstruction::CreateIntentBundle { + num_commit_only: commit_only_payers.len() as u8, + num_undelegate: undelegate_payers.len() as u8, + counter_diffs, + compute_units, + }, + accounts, + ) +} diff --git a/test-integration/programs/flexi-counter/src/processor.rs b/test-integration/programs/flexi-counter/src/processor.rs index 47ad47cff..8327e95e3 100644 --- a/test-integration/programs/flexi-counter/src/processor.rs +++ b/test-integration/programs/flexi-counter/src/processor.rs @@ -35,7 +35,9 @@ use crate::{ call_handler::{ process_commit_action_handler, process_undelegate_action_handler, }, - schedule_intent::process_create_intent, + schedule_intent::{ + process_create_intent, process_create_intent_bundle, + }, }, state::{FlexiCounter, FAIL_UNDELEGATION_CODE, FAIL_UNDELEGATION_LABEL}, utils::assert_keys_equal, @@ -94,6 +96,18 @@ pub fn process( } => process_undelegate_action_handler(accounts, amount, counter_diff), Schedule(args) => process_schedule_task(accounts, args), Cancel(args) => process_cancel_task(accounts, args), + CreateIntentBundle { + num_commit_only, + num_undelegate, + counter_diffs, + compute_units, + } => process_create_intent_bundle( + accounts, + num_commit_only, + num_undelegate, + counter_diffs, + compute_units, + ), }?; Ok(()) } diff --git a/test-integration/programs/flexi-counter/src/processor/schedule_intent.rs b/test-integration/programs/flexi-counter/src/processor/schedule_intent.rs index fa93187ae..843d50fce 100644 --- a/test-integration/programs/flexi-counter/src/processor/schedule_intent.rs +++ b/test-integration/programs/flexi-counter/src/processor/schedule_intent.rs @@ -1,8 +1,9 @@ use borsh::to_vec; use ephemeral_rollups_sdk::{ ephem::{ - CallHandler, CommitAndUndelegate, CommitType, MagicAction, - MagicInstructionBuilder, UndelegateType, + CallHandler, CommitAndUndelegate, CommitAndUndelegateIntentBuilder, + CommitIntentBuilder, CommitType, MagicAction, MagicInstructionBuilder, + MagicIntentBundleBuilder, UndelegateType, }, ActionArgs, ShortAccountMeta, }; @@ -138,3 +139,189 @@ pub fn process_create_intent( } .build_and_invoke() } + +/// Process CreateIntentBundle instruction that creates an IntentBundle containing +/// both Commit and CommitAndUndelegate intents simultaneously using MagicIntentBundleBuilder. +/// +/// This tests the new SDK feature where a single bundle can contain multiple intent types. +pub fn process_create_intent_bundle( + accounts: &[AccountInfo], + num_commit_only: u8, + num_undelegate: u8, + counter_diffs: Vec, + compute_units: u32, +) -> ProgramResult { + msg!( + "Process create intent bundle: {} commit-only, {} undelegate", + num_commit_only, + num_undelegate + ); + + let num_commit_only = num_commit_only as usize; + let num_undelegate = num_undelegate as usize; + + // Expected accounts: + // 5 fixed + 2*num_commit_only (escrow + counter) + 2*num_undelegate (escrow + counter) + let expected_accounts = 5 + 2 * num_commit_only + 2 * num_undelegate; + let actual_accounts = accounts.len(); + if actual_accounts != expected_accounts { + msg!( + "Invalid number of accounts expected: {}, got: {}", + expected_accounts, + actual_accounts + ); + return Err(ProgramError::NotEnoughAccountKeys); + } + + let account_info_iter = &mut accounts.iter(); + + // Fixed accounts + let destination_program = next_account_info(account_info_iter)?; + let magic_context = next_account_info(account_info_iter)?; + let magic_program = next_account_info(account_info_iter)?; + let transfer_destination = next_account_info(account_info_iter)?; + let system_program = next_account_info(account_info_iter)?; + + // Commit-only accounts + let commit_only_escrows = + next_account_infos(account_info_iter, num_commit_only)?; + let commit_only_counters = + next_account_infos(account_info_iter, num_commit_only)?; + + // CommitAndUndelegate accounts + let undelegate_escrows = + next_account_infos(account_info_iter, num_undelegate)?; + let undelegate_counters = + next_account_infos(account_info_iter, num_undelegate)?; + + // Get the first available payer for the builder + let payer = if !commit_only_escrows.is_empty() { + commit_only_escrows[0].clone() + } else if !undelegate_escrows.is_empty() { + undelegate_escrows[0].clone() + } else { + msg!("No payers provided"); + return Err(ProgramError::NotEnoughAccountKeys); + }; + + // Start building the intent bundle + let mut builder = MagicIntentBundleBuilder::new( + payer, + magic_context.clone(), + magic_program.clone(), + ); + + // Build Commit intent (commit-only accounts) + if !commit_only_counters.is_empty() { + let commit_action = + FlexiCounterInstruction::CommitActionHandler { amount: PRIZE }; + let call_handlers = commit_only_counters + .iter() + .zip(commit_only_escrows.iter().cloned()) + .map(|(counter, escrow_authority)| { + let other_accounts = vec![ + counter.into(), + ShortAccountMeta { + pubkey: *transfer_destination.key, + is_writable: true, + }, + system_program.into(), + ]; + + CallHandler { + args: ActionArgs { + data: to_vec(&commit_action).unwrap(), + escrow_index: ACTOR_ESCROW_INDEX, + }, + compute_units, + escrow_authority, + destination_program: *destination_program.key, + accounts: other_accounts, + } + }) + .collect::>(); + + let commit_intent = CommitIntentBuilder::new(commit_only_counters) + .add_post_commit_actions(call_handlers) + .build(); + + builder = builder.add_commit_intent(commit_intent); + } + + // Build CommitAndUndelegate intent + if !undelegate_counters.is_empty() { + // Post-commit actions for CommitAndUndelegate + let commit_action = + FlexiCounterInstruction::CommitActionHandler { amount: PRIZE }; + let commit_handlers = undelegate_counters + .iter() + .zip(undelegate_escrows.iter().cloned()) + .map(|(counter, escrow_authority)| { + let other_accounts = vec![ + counter.into(), + ShortAccountMeta { + pubkey: *transfer_destination.key, + is_writable: true, + }, + system_program.into(), + ]; + + CallHandler { + args: ActionArgs { + data: to_vec(&commit_action).unwrap(), + escrow_index: ACTOR_ESCROW_INDEX, + }, + compute_units, + escrow_authority, + destination_program: *destination_program.key, + accounts: other_accounts, + } + }) + .collect::>(); + + // Post-undelegate actions + let undelegate_handlers = undelegate_counters + .iter() + .zip(undelegate_escrows.iter().cloned()) + .zip(counter_diffs.iter().copied()) + .map(|((counter, escrow_authority), counter_diff)| { + let undelegate_action = + FlexiCounterInstruction::UndelegateActionHandler { + counter_diff, + amount: PRIZE, + }; + + let other_accounts = vec![ + counter.into(), + ShortAccountMeta { + pubkey: *transfer_destination.key, + is_writable: true, + }, + system_program.into(), + ]; + + CallHandler { + args: ActionArgs { + data: to_vec(&undelegate_action).unwrap(), + escrow_index: ACTOR_ESCROW_INDEX, + }, + compute_units, + escrow_authority, + destination_program: *destination_program.key, + accounts: other_accounts, + } + }) + .collect::>(); + + let cau_intent = + CommitAndUndelegateIntentBuilder::new(undelegate_counters) + .add_post_commit_actions(commit_handlers) + .add_post_undelegate_actions(undelegate_handlers) + .build(); + + builder = builder.add_commit_and_undelegate_intent(cau_intent); + } + + // Build and invoke the intent bundle + builder.build_and_invoke() +} diff --git a/test-integration/test-schedule-intent/tests/test_schedule_intents.rs b/test-integration/test-schedule-intent/tests/test_schedule_intents.rs index fa30b64f0..9ee6f9d38 100644 --- a/test-integration/test-schedule-intent/tests/test_schedule_intents.rs +++ b/test-integration/test-schedule-intent/tests/test_schedule_intents.rs @@ -3,7 +3,8 @@ use integration_test_tools::IntegrationTestContext; use program_flexi_counter::{ delegation_program_id, instruction::{ - create_add_ix, create_delegate_ix, create_init_ix, create_intent_ix, + create_add_ix, create_delegate_ix, create_init_ix, + create_intent_bundle_ix, create_intent_ix, }, state::FlexiCounter, }; @@ -291,6 +292,237 @@ fn test_redelegation_intent() { // redelegate_intent(&ctx, &payer); } +/// Tests the new MagicIntentBundleBuilder feature where a single IntentBundle +/// can contain both Commit and CommitAndUndelegate intents simultaneously. +/// +/// Setup: +/// - 2 payers for commit-only (their counters will just be committed) +/// - 2 payers for commit-and-undelegate (their counters will be committed and undelegated) +/// +/// Expected behavior: +/// - All 4 counters should be committed to base layer +/// - Only the undelegate payers' counters should be undelegated +/// - Commit-only payers' counters remain delegated +#[test] +fn test_intent_bundle_commit_and_undelegate_simultaneously() { + init_logger!(); + + // Init context + let ctx = IntegrationTestContext::try_new().unwrap(); + + // Create 2 payers for commit-only and 2 for undelegate + let commit_only_payers: Vec = + (0..2).map(|_| setup_payer(&ctx)).collect(); + let undelegate_payers: Vec = + (0..2).map(|_| setup_payer(&ctx)).collect(); + + debug!( + "Created {} commit-only payers and {} undelegate payers", + commit_only_payers.len(), + undelegate_payers.len() + ); + + // Init and delegate counters for commit-only payers + let commit_values: [u8; 2] = [50, 75]; + for (idx, payer) in commit_only_payers.iter().enumerate() { + init_counter(&ctx, payer); + delegate_counter(&ctx, payer); + add_to_counter(&ctx, payer, commit_values[idx]); + debug!( + "Commit-only payer {} initialized with value {}", + payer.pubkey(), + commit_values[idx] + ); + } + + // Init and delegate counters for undelegate payers + let undelegate_values: [u8; 2] = [100, 150]; + for (idx, payer) in undelegate_payers.iter().enumerate() { + init_counter(&ctx, payer); + delegate_counter(&ctx, payer); + add_to_counter(&ctx, payer, undelegate_values[idx]); + debug!( + "Undelegate payer {} initialized with value {}", + payer.pubkey(), + undelegate_values[idx] + ); + } + + // Schedule intent bundle with both Commit and CommitAndUndelegate + // Counter diffs: -10 for first undelegate payer, +25 for second + let counter_diffs = vec![-10i64, 25i64]; + schedule_intent_bundle( + &ctx, + &commit_only_payers.iter().collect::>(), + &undelegate_payers.iter().collect::>(), + counter_diffs, + ); + debug!("Scheduled intent bundle"); + + // Assert commit-only counters have their values committed + // (commit-only payers: 50, 75) + assert_counters( + &ctx, + &[ + ExpectedCounter { + pda: FlexiCounter::pda(&commit_only_payers[0].pubkey()).0, + expected: 50, + }, + ExpectedCounter { + pda: FlexiCounter::pda(&commit_only_payers[1].pubkey()).0, + expected: 75, + }, + ], + true, + ); + debug!("Verified commit-only counters on base layer"); + + // Assert undelegate counters have their values committed + counter_diff applied + // (undelegate payers: 100 + (-10) = 90, 150 + 25 = 175) + assert_counters( + &ctx, + &[ + ExpectedCounter { + pda: FlexiCounter::pda(&undelegate_payers[0].pubkey()).0, + expected: 90, + }, + ExpectedCounter { + pda: FlexiCounter::pda(&undelegate_payers[1].pubkey()).0, + expected: 175, + }, + ], + true, + ); + debug!("Verified undelegate counters on base layer"); + + // Verify that only undelegate payers' accounts are undelegated + verify_undelegation_in_ephem_via_owner( + &undelegate_payers + .iter() + .map(|p| p.pubkey()) + .collect::>(), + &ctx, + ); + debug!("Verified undelegation for undelegate payers"); + + // Verify that commit-only payers' accounts are still delegated + for payer in &commit_only_payers { + let counter_pda = FlexiCounter::pda(&payer.pubkey()).0; + let owner = ctx.fetch_ephem_account_owner(counter_pda).unwrap(); + assert_eq!( + owner, + delegation_program_id(), + "Commit-only counter should still be delegated" + ); + } + debug!("Verified commit-only counters are still delegated"); +} + +/// Tests IntentBundle with only Commit intent (no undelegate). +/// This ensures the new API works for commit-only scenarios. +#[test] +fn test_intent_bundle_commit_only() { + init_logger!(); + + let ctx = IntegrationTestContext::try_new().unwrap(); + + let commit_only_payers: Vec = + (0..2).map(|_| setup_payer(&ctx)).collect(); + + // Init and delegate counters + let values: [u8; 2] = [42, 88]; + for (idx, payer) in commit_only_payers.iter().enumerate() { + init_counter(&ctx, payer); + delegate_counter(&ctx, payer); + add_to_counter(&ctx, payer, values[idx]); + } + + // Schedule bundle with only commit intent (no undelegate payers) + schedule_intent_bundle( + &ctx, + &commit_only_payers.iter().collect::>(), + &[], // No undelegate payers + vec![], + ); + + // Verify commits + assert_counters( + &ctx, + &[ + ExpectedCounter { + pda: FlexiCounter::pda(&commit_only_payers[0].pubkey()).0, + expected: 42, + }, + ExpectedCounter { + pda: FlexiCounter::pda(&commit_only_payers[1].pubkey()).0, + expected: 88, + }, + ], + true, + ); + + // Verify still delegated + for payer in &commit_only_payers { + let counter_pda = FlexiCounter::pda(&payer.pubkey()).0; + let owner = ctx.fetch_ephem_account_owner(counter_pda).unwrap(); + assert_eq!(owner, delegation_program_id()); + } +} + +/// Tests IntentBundle with only CommitAndUndelegate intent (no commit-only). +/// This ensures the new API works for undelegate-only scenarios. +#[test] +fn test_intent_bundle_undelegate_only() { + init_logger!(); + + let ctx = IntegrationTestContext::try_new().unwrap(); + + let undelegate_payers: Vec = + (0..2).map(|_| setup_payer(&ctx)).collect(); + + // Init and delegate counters + let values: [u8; 2] = [200, 250]; + for (idx, payer) in undelegate_payers.iter().enumerate() { + init_counter(&ctx, payer); + delegate_counter(&ctx, payer); + add_to_counter(&ctx, payer, values[idx]); + } + + // Schedule bundle with only undelegate intent (no commit-only payers) + let counter_diffs = vec![50i64, -100i64]; + schedule_intent_bundle( + &ctx, + &[], // No commit-only payers + &undelegate_payers.iter().collect::>(), + counter_diffs, + ); + + // Verify values (200 + 50 = 250, 250 - 100 = 150) + assert_counters( + &ctx, + &[ + ExpectedCounter { + pda: FlexiCounter::pda(&undelegate_payers[0].pubkey()).0, + expected: 250, + }, + ExpectedCounter { + pda: FlexiCounter::pda(&undelegate_payers[1].pubkey()).0, + expected: 150, + }, + ], + true, + ); + + // Verify undelegation + verify_undelegation_in_ephem_via_owner( + &undelegate_payers + .iter() + .map(|p| p.pubkey()) + .collect::>(), + &ctx, + ); +} + fn setup_payer(ctx: &IntegrationTestContext) -> Keypair { // Airdrop to payer on chain let payer = Keypair::new(); @@ -457,6 +689,69 @@ fn schedule_intent( ); } +/// Schedule an intent bundle using the new MagicIntentBundleBuilder API. +/// This creates an IntentBundle that can contain both Commit and CommitAndUndelegate intents. +fn schedule_intent_bundle( + ctx: &IntegrationTestContext, + commit_only_payers: &[&Keypair], + undelegate_payers: &[&Keypair], + counter_diffs: Vec, +) { + ctx.wait_for_next_slot_ephem().unwrap(); + + let transfer_destination = Keypair::new(); + let commit_only_pubkeys: Vec = + commit_only_payers.iter().map(|p| p.pubkey()).collect(); + let undelegate_pubkeys: Vec = + undelegate_payers.iter().map(|p| p.pubkey()).collect(); + + let ix = create_intent_bundle_ix( + commit_only_pubkeys, + undelegate_pubkeys, + transfer_destination.pubkey(), + counter_diffs, + 100_000, + ); + + // Collect all signers - need at least one + let all_payers: Vec<&Keypair> = commit_only_payers + .iter() + .chain(undelegate_payers.iter()) + .copied() + .collect(); + + assert!( + !all_payers.is_empty(), + "At least one payer required for intent bundle" + ); + + let mut tx = + Transaction::new_with_payer(&[ix], Some(&all_payers[0].pubkey())); + let (sig, confirmed) = ctx + .send_and_confirm_transaction_ephem(&mut tx, &all_payers) + .unwrap(); + assert!(confirmed); + + // Confirm was sent on Base Layer + let commit_result = ctx + .fetch_schedule_commit_result::(sig) + .unwrap(); + commit_result + .confirm_commit_transactions_on_chain(ctx) + .unwrap(); + + // Verify Prize = 1_000_000 is transferred for each action + // - commit-only payers: 1 action each (commit) + // - undelegate payers: 2 actions each (commit + undelegate) + let transfer_destination_balance = ctx + .fetch_chain_account_balance(&transfer_destination.pubkey()) + .unwrap(); + + let expected_balance = (commit_only_payers.len() as u64 * 1_000_000) + + (undelegate_payers.len() as u64 * 2 * 1_000_000); + assert_eq!(transfer_destination_balance, expected_balance); +} + fn verify_undelegation_in_ephem_via_owner( pubkeys: &[Pubkey], ctx: &IntegrationTestContext, From 6463066db9935a62e34c68e991d3074ba285c07b Mon Sep 17 00:00:00 2001 From: taco-paco Date: Wed, 21 Jan 2026 21:43:27 +0700 Subject: [PATCH 26/36] fix: test --- test-integration/Cargo.lock | 10 +++++----- test-integration/Cargo.toml | 2 +- .../tests/test_schedule_intents.rs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test-integration/Cargo.lock b/test-integration/Cargo.lock index 9531732bd..b03f88478 100644 --- a/test-integration/Cargo.lock +++ b/test-integration/Cargo.lock @@ -1690,7 +1690,7 @@ dependencies = [ [[package]] name = "ephemeral-rollups-sdk" version = "0.6.5" -source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=9deb6fb5a3#9deb6fb5a3bbaf29a38f35b6a03d0fb9f69fa66f" +source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=8e84ebdba#8e84ebdba2322fbe5ac01cf853d01e2a431ba6f1" dependencies = [ "base64ct", "borsh 1.6.0", @@ -1715,7 +1715,7 @@ dependencies = [ [[package]] name = "ephemeral-rollups-sdk-attribute-action" version = "0.6.5" -source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=9deb6fb5a3#9deb6fb5a3bbaf29a38f35b6a03d0fb9f69fa66f" +source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=8e84ebdba#8e84ebdba2322fbe5ac01cf853d01e2a431ba6f1" dependencies = [ "quote", "syn 1.0.109", @@ -1724,7 +1724,7 @@ dependencies = [ [[package]] name = "ephemeral-rollups-sdk-attribute-commit" version = "0.6.5" -source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=9deb6fb5a3#9deb6fb5a3bbaf29a38f35b6a03d0fb9f69fa66f" +source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=8e84ebdba#8e84ebdba2322fbe5ac01cf853d01e2a431ba6f1" dependencies = [ "quote", "syn 1.0.109", @@ -1733,7 +1733,7 @@ dependencies = [ [[package]] name = "ephemeral-rollups-sdk-attribute-delegate" version = "0.6.5" -source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=9deb6fb5a3#9deb6fb5a3bbaf29a38f35b6a03d0fb9f69fa66f" +source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=8e84ebdba#8e84ebdba2322fbe5ac01cf853d01e2a431ba6f1" dependencies = [ "proc-macro2", "quote", @@ -1743,7 +1743,7 @@ dependencies = [ [[package]] name = "ephemeral-rollups-sdk-attribute-ephemeral" version = "0.6.5" -source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=9deb6fb5a3#9deb6fb5a3bbaf29a38f35b6a03d0fb9f69fa66f" +source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=8e84ebdba#8e84ebdba2322fbe5ac01cf853d01e2a431ba6f1" dependencies = [ "proc-macro2", "quote", diff --git a/test-integration/Cargo.toml b/test-integration/Cargo.toml index bc49aabea..7b2815987 100644 --- a/test-integration/Cargo.toml +++ b/test-integration/Cargo.toml @@ -36,7 +36,7 @@ chrono = "0.4" cleanass = "0.0.1" color-backtrace = { version = "0.7" } ctrlc = "3.4.7" -ephemeral-rollups-sdk = { git = "https://github.com/magicblock-labs/ephemeral-rollups-sdk.git", rev = "9deb6fb5a3", default-features = false, features = [ +ephemeral-rollups-sdk = { git = "https://github.com/magicblock-labs/ephemeral-rollups-sdk.git", rev = "8e84ebdba", default-features = false, features = [ "modular-sdk", ] } futures = "0.3.31" diff --git a/test-integration/test-schedule-intent/tests/test_schedule_intents.rs b/test-integration/test-schedule-intent/tests/test_schedule_intents.rs index 9ee6f9d38..fbe2d2cc6 100644 --- a/test-integration/test-schedule-intent/tests/test_schedule_intents.rs +++ b/test-integration/test-schedule-intent/tests/test_schedule_intents.rs @@ -464,7 +464,7 @@ fn test_intent_bundle_commit_only() { // Verify still delegated for payer in &commit_only_payers { let counter_pda = FlexiCounter::pda(&payer.pubkey()).0; - let owner = ctx.fetch_ephem_account_owner(counter_pda).unwrap(); + let owner = ctx.fetch_chain_account_owner(counter_pda).unwrap(); assert_eq!(owner, delegation_program_id()); } } From 476795692069f32fa8b2da1855771eb9026dc5a9 Mon Sep 17 00:00:00 2001 From: taco-paco Date: Wed, 21 Jan 2026 21:51:50 +0700 Subject: [PATCH 27/36] fix: tests --- .../tests/test_schedule_intents.rs | 72 ++++++++----------- 1 file changed, 28 insertions(+), 44 deletions(-) diff --git a/test-integration/test-schedule-intent/tests/test_schedule_intents.rs b/test-integration/test-schedule-intent/tests/test_schedule_intents.rs index fbe2d2cc6..eae1459a6 100644 --- a/test-integration/test-schedule-intent/tests/test_schedule_intents.rs +++ b/test-integration/test-schedule-intent/tests/test_schedule_intents.rs @@ -310,16 +310,14 @@ fn test_intent_bundle_commit_and_undelegate_simultaneously() { // Init context let ctx = IntegrationTestContext::try_new().unwrap(); - // Create 2 payers for commit-only and 2 for undelegate + // Create 2 payers for commit-only and 1 for undelegate let commit_only_payers: Vec = (0..2).map(|_| setup_payer(&ctx)).collect(); - let undelegate_payers: Vec = - (0..2).map(|_| setup_payer(&ctx)).collect(); + let undelegate_payer = setup_payer(&ctx); debug!( - "Created {} commit-only payers and {} undelegate payers", - commit_only_payers.len(), - undelegate_payers.len() + "Created {} commit-only payers and 1 undelegate payer", + commit_only_payers.len() ); // Init and delegate counters for commit-only payers @@ -335,26 +333,24 @@ fn test_intent_bundle_commit_and_undelegate_simultaneously() { ); } - // Init and delegate counters for undelegate payers - let undelegate_values: [u8; 2] = [100, 150]; - for (idx, payer) in undelegate_payers.iter().enumerate() { - init_counter(&ctx, payer); - delegate_counter(&ctx, payer); - add_to_counter(&ctx, payer, undelegate_values[idx]); - debug!( - "Undelegate payer {} initialized with value {}", - payer.pubkey(), - undelegate_values[idx] - ); - } + // Init and delegate counter for undelegate payer + let undelegate_value: u8 = 100; + init_counter(&ctx, &undelegate_payer); + delegate_counter(&ctx, &undelegate_payer); + add_to_counter(&ctx, &undelegate_payer, undelegate_value); + debug!( + "Undelegate payer {} initialized with value {}", + undelegate_payer.pubkey(), + undelegate_value + ); // Schedule intent bundle with both Commit and CommitAndUndelegate - // Counter diffs: -10 for first undelegate payer, +25 for second - let counter_diffs = vec![-10i64, 25i64]; + // Counter diff: -10 for undelegate payer + let counter_diffs = vec![-10i64]; schedule_intent_bundle( &ctx, &commit_only_payers.iter().collect::>(), - &undelegate_payers.iter().collect::>(), + &[&undelegate_payer], counter_diffs, ); debug!("Scheduled intent bundle"); @@ -377,38 +373,26 @@ fn test_intent_bundle_commit_and_undelegate_simultaneously() { ); debug!("Verified commit-only counters on base layer"); - // Assert undelegate counters have their values committed + counter_diff applied - // (undelegate payers: 100 + (-10) = 90, 150 + 25 = 175) + // Assert undelegate counter has its value committed + counter_diff applied + // (undelegate payer: 100 + (-10) = 90) assert_counters( &ctx, - &[ - ExpectedCounter { - pda: FlexiCounter::pda(&undelegate_payers[0].pubkey()).0, - expected: 90, - }, - ExpectedCounter { - pda: FlexiCounter::pda(&undelegate_payers[1].pubkey()).0, - expected: 175, - }, - ], + &[ExpectedCounter { + pda: FlexiCounter::pda(&undelegate_payer.pubkey()).0, + expected: 90, + }], true, ); - debug!("Verified undelegate counters on base layer"); + debug!("Verified undelegate counter on base layer"); - // Verify that only undelegate payers' accounts are undelegated - verify_undelegation_in_ephem_via_owner( - &undelegate_payers - .iter() - .map(|p| p.pubkey()) - .collect::>(), - &ctx, - ); - debug!("Verified undelegation for undelegate payers"); + // Verify that only undelegate payer's account is undelegated + verify_undelegation_in_ephem_via_owner(&[undelegate_payer.pubkey()], &ctx); + debug!("Verified undelegation for undelegate payer"); // Verify that commit-only payers' accounts are still delegated for payer in &commit_only_payers { let counter_pda = FlexiCounter::pda(&payer.pubkey()).0; - let owner = ctx.fetch_ephem_account_owner(counter_pda).unwrap(); + let owner = ctx.fetch_chain_account_owner(counter_pda).unwrap(); assert_eq!( owner, delegation_program_id(), From ec4fad9a7da39a168f28c94adbf701b12100f9e8 Mon Sep 17 00:00:00 2001 From: taco-paco Date: Wed, 21 Jan 2026 21:53:18 +0700 Subject: [PATCH 28/36] fix: bug --- programs/magicblock/src/magic_scheduled_base_intent.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/programs/magicblock/src/magic_scheduled_base_intent.rs b/programs/magicblock/src/magic_scheduled_base_intent.rs index 9e8a75ef7..0c16d9f2e 100644 --- a/programs/magicblock/src/magic_scheduled_base_intent.rs +++ b/programs/magicblock/src/magic_scheduled_base_intent.rs @@ -362,12 +362,12 @@ impl MagicIntentBundle { .commit .as_ref() .map(|el| el.is_empty()) - .unwrap_or(false); + .unwrap_or(true); let no_committed_and_undelegated = self .commit_and_undelegate .as_ref() .map(|el| el.is_empty()) - .unwrap_or(false); + .unwrap_or(true); let no_actions = self.standalone_actions.is_empty(); no_committed && no_committed_and_undelegated && no_actions From 747f518f638dbae6c8f695ecf67a501667c026e7 Mon Sep 17 00:00:00 2001 From: taco-paco Date: Thu, 22 Jan 2026 12:20:59 +0700 Subject: [PATCH 29/36] fix: coderabbit coments --- .../intent_execution_manager/intent_execution_engine.rs | 2 +- programs/magicblock/src/magic_scheduled_base_intent.rs | 7 ++----- .../test-schedule-intent/tests/test_schedule_intents.rs | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs b/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs index 3f889daf4..239202c52 100644 --- a/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs +++ b/magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs @@ -234,7 +234,7 @@ where Ok(intent) } - /// Returns [`ScheduleIntentBundleWrapper`] from external channel + /// Returns [`ScheduledIntentBundle`] from external channel async fn get_new_intent( receiver: &mut mpsc::Receiver, db: &Arc, diff --git a/programs/magicblock/src/magic_scheduled_base_intent.rs b/programs/magicblock/src/magic_scheduled_base_intent.rs index 0c16d9f2e..63670844a 100644 --- a/programs/magicblock/src/magic_scheduled_base_intent.rs +++ b/programs/magicblock/src/magic_scheduled_base_intent.rs @@ -358,11 +358,8 @@ impl MagicIntentBundle { } pub fn is_empty(&self) -> bool { - let no_committed = self - .commit - .as_ref() - .map(|el| el.is_empty()) - .unwrap_or(true); + let no_committed = + self.commit.as_ref().map(|el| el.is_empty()).unwrap_or(true); let no_committed_and_undelegated = self .commit_and_undelegate .as_ref() diff --git a/test-integration/test-schedule-intent/tests/test_schedule_intents.rs b/test-integration/test-schedule-intent/tests/test_schedule_intents.rs index eae1459a6..124766fb0 100644 --- a/test-integration/test-schedule-intent/tests/test_schedule_intents.rs +++ b/test-integration/test-schedule-intent/tests/test_schedule_intents.rs @@ -297,7 +297,7 @@ fn test_redelegation_intent() { /// /// Setup: /// - 2 payers for commit-only (their counters will just be committed) -/// - 2 payers for commit-and-undelegate (their counters will be committed and undelegated) +/// - 1 payer for commit-and-undelegate (their counters will be committed and undelegated) /// /// Expected behavior: /// - All 4 counters should be committed to base layer From e8837cae1682bfb58ae65aa658ccf5b060eb932a Mon Sep 17 00:00:00 2001 From: taco-paco Date: Thu, 22 Jan 2026 13:46:00 +0700 Subject: [PATCH 30/36] feat: update sdk revision --- test-integration/Cargo.lock | 10 +++++----- test-integration/Cargo.toml | 2 +- .../flexi-counter/src/processor/schedule_intent.rs | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test-integration/Cargo.lock b/test-integration/Cargo.lock index b03f88478..cc0989324 100644 --- a/test-integration/Cargo.lock +++ b/test-integration/Cargo.lock @@ -1690,7 +1690,7 @@ dependencies = [ [[package]] name = "ephemeral-rollups-sdk" version = "0.6.5" -source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=8e84ebdba#8e84ebdba2322fbe5ac01cf853d01e2a431ba6f1" +source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=bc030e379#bc030e37960615d8af0fac03dad35acc06d3555a" dependencies = [ "base64ct", "borsh 1.6.0", @@ -1715,7 +1715,7 @@ dependencies = [ [[package]] name = "ephemeral-rollups-sdk-attribute-action" version = "0.6.5" -source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=8e84ebdba#8e84ebdba2322fbe5ac01cf853d01e2a431ba6f1" +source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=bc030e379#bc030e37960615d8af0fac03dad35acc06d3555a" dependencies = [ "quote", "syn 1.0.109", @@ -1724,7 +1724,7 @@ dependencies = [ [[package]] name = "ephemeral-rollups-sdk-attribute-commit" version = "0.6.5" -source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=8e84ebdba#8e84ebdba2322fbe5ac01cf853d01e2a431ba6f1" +source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=bc030e379#bc030e37960615d8af0fac03dad35acc06d3555a" dependencies = [ "quote", "syn 1.0.109", @@ -1733,7 +1733,7 @@ dependencies = [ [[package]] name = "ephemeral-rollups-sdk-attribute-delegate" version = "0.6.5" -source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=8e84ebdba#8e84ebdba2322fbe5ac01cf853d01e2a431ba6f1" +source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=bc030e379#bc030e37960615d8af0fac03dad35acc06d3555a" dependencies = [ "proc-macro2", "quote", @@ -1743,7 +1743,7 @@ dependencies = [ [[package]] name = "ephemeral-rollups-sdk-attribute-ephemeral" version = "0.6.5" -source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=8e84ebdba#8e84ebdba2322fbe5ac01cf853d01e2a431ba6f1" +source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=bc030e379#bc030e37960615d8af0fac03dad35acc06d3555a" dependencies = [ "proc-macro2", "quote", diff --git a/test-integration/Cargo.toml b/test-integration/Cargo.toml index 7b2815987..25975438a 100644 --- a/test-integration/Cargo.toml +++ b/test-integration/Cargo.toml @@ -36,7 +36,7 @@ chrono = "0.4" cleanass = "0.0.1" color-backtrace = { version = "0.7" } ctrlc = "3.4.7" -ephemeral-rollups-sdk = { git = "https://github.com/magicblock-labs/ephemeral-rollups-sdk.git", rev = "8e84ebdba", default-features = false, features = [ +ephemeral-rollups-sdk = { git = "https://github.com/magicblock-labs/ephemeral-rollups-sdk.git", rev = "bc030e379", default-features = false, features = [ "modular-sdk", ] } futures = "0.3.31" diff --git a/test-integration/programs/flexi-counter/src/processor/schedule_intent.rs b/test-integration/programs/flexi-counter/src/processor/schedule_intent.rs index 843d50fce..022a43a26 100644 --- a/test-integration/programs/flexi-counter/src/processor/schedule_intent.rs +++ b/test-integration/programs/flexi-counter/src/processor/schedule_intent.rs @@ -245,7 +245,7 @@ pub fn process_create_intent_bundle( .add_post_commit_actions(call_handlers) .build(); - builder = builder.add_commit_intent(commit_intent); + builder = builder.add_commit(commit_intent); } // Build CommitAndUndelegate intent @@ -319,7 +319,7 @@ pub fn process_create_intent_bundle( .add_post_undelegate_actions(undelegate_handlers) .build(); - builder = builder.add_commit_and_undelegate_intent(cau_intent); + builder = builder.add_commit_and_undelegate(cau_intent); } // Build and invoke the intent bundle From 0cf1c0b85dabe5f31b91a4fbd616f479c54a3411 Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Thu, 22 Jan 2026 19:10:06 +0100 Subject: [PATCH 31/36] Update test-integration/test-schedule-intent/tests/test_schedule_intents.rs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../test-schedule-intent/tests/test_schedule_intents.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-integration/test-schedule-intent/tests/test_schedule_intents.rs b/test-integration/test-schedule-intent/tests/test_schedule_intents.rs index 124766fb0..fb8406eac 100644 --- a/test-integration/test-schedule-intent/tests/test_schedule_intents.rs +++ b/test-integration/test-schedule-intent/tests/test_schedule_intents.rs @@ -300,7 +300,7 @@ fn test_redelegation_intent() { /// - 1 payer for commit-and-undelegate (their counters will be committed and undelegated) /// /// Expected behavior: -/// - All 4 counters should be committed to base layer +/// - All 3 counters should be committed to base layer /// - Only the undelegate payers' counters should be undelegated /// - Commit-only payers' counters remain delegated #[test] From ce01ce5c812ff7b2ec65f54bfa9e9d79206699f6 Mon Sep 17 00:00:00 2001 From: taco-paco Date: Sun, 25 Jan 2026 14:18:27 +0700 Subject: [PATCH 32/36] fix: address comments --- .../src/scheduled_commits_processor.rs | 4 +--- .../intent_execution_manager/intent_scheduler.rs | 4 ++-- .../src/persist/commit_persister.rs | 16 ++++++++-------- .../src/magic_scheduled_base_intent.rs | 12 ++++++++++-- .../process_schedule_commit.rs | 2 +- .../process_schedule_commit_tests.rs | 2 +- .../process_schedule_intent_bundle.rs | 3 +-- .../tests/test_intent_executor.rs | 2 +- .../tests/test_ix_commit_local.rs | 8 ++++---- 9 files changed, 29 insertions(+), 24 deletions(-) diff --git a/magicblock-accounts/src/scheduled_commits_processor.rs b/magicblock-accounts/src/scheduled_commits_processor.rs index e472873cb..88c4ed4ed 100644 --- a/magicblock-accounts/src/scheduled_commits_processor.rs +++ b/magicblock-accounts/src/scheduled_commits_processor.rs @@ -331,9 +331,7 @@ impl ScheduledBaseIntentMeta { blockhash: intent.blockhash, payer: intent.payer, included_pubkeys: intent.get_all_committed_pubkeys(), - intent_sent_transaction: intent - .intent_bundle_sent_transaction - .clone(), + intent_sent_transaction: intent.sent_transaction.clone(), requested_undelegation: intent.has_undelegate_intent(), } } diff --git a/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs b/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs index f3729006b..57f497131 100644 --- a/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs +++ b/magicblock-committor-service/src/intent_execution_manager/intent_scheduler.rs @@ -866,7 +866,7 @@ pub(crate) fn create_test_intent( id, slot: 0, blockhash: Hash::default(), - intent_bundle_sent_transaction: Transaction::default(), + sent_transaction: Transaction::default(), payer: Pubkey::default(), intent_bundle: MagicIntentBundle::default(), }; @@ -926,7 +926,7 @@ pub(crate) fn create_test_intent_bundle( id, slot: 0, blockhash: Hash::default(), - intent_bundle_sent_transaction: Transaction::default(), + sent_transaction: Transaction::default(), payer: Pubkey::default(), intent_bundle: MagicIntentBundle::default(), }; diff --git a/magicblock-committor-service/src/persist/commit_persister.rs b/magicblock-committor-service/src/persist/commit_persister.rs index f4faddc02..c9399bff9 100644 --- a/magicblock-committor-service/src/persist/commit_persister.rs +++ b/magicblock-committor-service/src/persist/commit_persister.rs @@ -490,7 +490,7 @@ mod tests { ] } - fn commit_only_budle() -> MagicIntentBundle { + fn commit_only_bundle() -> MagicIntentBundle { MagicIntentBundle { commit: Some(CommitType::Standalone(test_accounts())), commit_and_undelegate: None, @@ -536,7 +536,7 @@ mod tests { id, slot: 100, blockhash: Hash::new_unique(), - intent_bundle_sent_transaction: Transaction::default(), + sent_transaction: Transaction::default(), payer: Pubkey::new_unique(), intent_bundle, } @@ -544,7 +544,7 @@ mod tests { #[test] fn test_create_commit_rows_commit_only() { - let message = create_test_message(1, commit_only_budle()); + let message = create_test_message(1, commit_only_bundle()); let rows = IntentPersisterImpl::create_commit_rows(&message); assert_eq!(rows.len(), 2); @@ -587,7 +587,7 @@ mod tests { #[test] fn test_start_base_message_commit_only() { let (persister, _temp_file) = create_test_persister(); - let message = create_test_message(1, commit_only_budle()); + let message = create_test_message(1, commit_only_bundle()); persister.start_base_intent(&message).unwrap(); @@ -603,7 +603,7 @@ mod tests { #[test] fn test_start_base_messages_mixed() { let (persister, _temp_file) = create_test_persister(); - let message1 = create_test_message(1, commit_only_budle()); // 2 rows + let message1 = create_test_message(1, commit_only_bundle()); // 2 rows let message2 = create_test_message(2, undelegate_only_bundle()); // 2 rows let message3 = create_test_message(3, bundle_both()); // 4 rows let message4 = create_test_message(4, bundle_none()); // 0 rows @@ -633,7 +633,7 @@ mod tests { #[test] fn test_update_status() { let (persister, _temp_file) = create_test_persister(); - let message = create_test_message(1, commit_only_budle()); + let message = create_test_message(1, commit_only_bundle()); persister.start_base_intent(&message).unwrap(); let pubkey = message.get_all_committed_pubkeys()[0]; @@ -672,7 +672,7 @@ mod tests { #[test] fn test_set_commit_strategy() { let (persister, _temp_file) = create_test_persister(); - let message = create_test_message(1, commit_only_budle()); + let message = create_test_message(1, commit_only_bundle()); persister.start_base_intent(&message).unwrap(); let pubkey = message.get_all_committed_pubkeys()[0]; @@ -692,7 +692,7 @@ mod tests { #[test] fn test_get_signatures() { let (persister, _temp_file) = create_test_persister(); - let message = create_test_message(1, commit_only_budle()); + let message = create_test_message(1, commit_only_bundle()); persister.start_base_intent(&message).unwrap(); let statuses = persister.get_commit_statuses_by_message(1).unwrap(); diff --git a/programs/magicblock/src/magic_scheduled_base_intent.rs b/programs/magicblock/src/magic_scheduled_base_intent.rs index 63670844a..5e90a8d2d 100644 --- a/programs/magicblock/src/magic_scheduled_base_intent.rs +++ b/programs/magicblock/src/magic_scheduled_base_intent.rs @@ -61,7 +61,7 @@ pub struct ScheduledIntentBundle { pub id: u64, pub slot: Slot, pub blockhash: Hash, - pub intent_bundle_sent_transaction: Transaction, + pub sent_transaction: Transaction, pub payer: Pubkey, /// Scheduled intent bundle pub intent_bundle: MagicIntentBundle, @@ -85,7 +85,7 @@ impl ScheduledIntentBundle { slot, blockhash, payer: *payer_pubkey, - intent_bundle_sent_transaction, + sent_transaction: intent_bundle_sent_transaction, intent_bundle, }) } @@ -255,6 +255,14 @@ impl MagicIntentBundle { &self, context: &ConstructionContext<'_, '_>, ) -> Result<(), InstructionError> { + if self.is_empty() { + ic_msg!( + context.invoke_context, + "ScheduleCommit ERR: intent bundle must not be empty.", + ); + return Err(InstructionError::InvalidInstructionData); + } + let mut seen = HashSet::::new(); let mut check = |accounts: &Vec| -> Result<(), InstructionError> { diff --git a/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs b/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs index 12901f140..77f57de3e 100644 --- a/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs +++ b/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs @@ -273,7 +273,7 @@ pub(crate) fn process_schedule_commit( id: intent_id, slot: clock.slot, blockhash, - intent_bundle_sent_transaction: action_sent_transaction, + sent_transaction: action_sent_transaction, payer: *payer_pubkey, intent_bundle: base_intent, }; diff --git a/programs/magicblock/src/schedule_transactions/process_schedule_commit_tests.rs b/programs/magicblock/src/schedule_transactions/process_schedule_commit_tests.rs index 8cee50f88..128e777b8 100644 --- a/programs/magicblock/src/schedule_transactions/process_schedule_commit_tests.rs +++ b/programs/magicblock/src/schedule_transactions/process_schedule_commit_tests.rs @@ -232,7 +232,7 @@ fn assert_first_commit( slot, payer: actual_payer, blockhash: _, - intent_bundle_sent_transaction: _, + sent_transaction: _, intent_bundle, } => { assert!(id >= &0); diff --git a/programs/magicblock/src/schedule_transactions/process_schedule_intent_bundle.rs b/programs/magicblock/src/schedule_transactions/process_schedule_intent_bundle.rs index cd927141c..70e27f973 100644 --- a/programs/magicblock/src/schedule_transactions/process_schedule_intent_bundle.rs +++ b/programs/magicblock/src/schedule_transactions/process_schedule_intent_bundle.rs @@ -155,8 +155,7 @@ pub(crate) fn process_schedule_intent_bundle( ); } - let action_sent_signature = - scheduled_intent.intent_bundle_sent_transaction.signatures[0]; + let action_sent_signature = scheduled_intent.sent_transaction.signatures[0]; context.add_scheduled_action(scheduled_intent); context_data.set_state(&context)?; diff --git a/test-integration/test-committor-service/tests/test_intent_executor.rs b/test-integration/test-committor-service/tests/test_intent_executor.rs index e6623ca68..9dab142ea 100644 --- a/test-integration/test-committor-service/tests/test_intent_executor.rs +++ b/test-integration/test-committor-service/tests/test_intent_executor.rs @@ -1200,7 +1200,7 @@ fn create_scheduled_intent( id: INTENT_ID.fetch_add(1, Ordering::Relaxed), slot: 10, blockhash: Hash::new_unique(), - intent_bundle_sent_transaction: Transaction::default(), + sent_transaction: Transaction::default(), payer: Pubkey::new_unique(), intent_bundle: base_intent.into(), } diff --git a/test-integration/test-committor-service/tests/test_ix_commit_local.rs b/test-integration/test-committor-service/tests/test_ix_commit_local.rs index 18464de20..0c0722cdc 100644 --- a/test-integration/test-committor-service/tests/test_ix_commit_local.rs +++ b/test-integration/test-committor-service/tests/test_ix_commit_local.rs @@ -191,7 +191,7 @@ async fn commit_single_account( id: 0, slot: 10, blockhash: Hash::new_unique(), - intent_bundle_sent_transaction: Transaction::default(), + sent_transaction: Transaction::default(), payer: counter_auth.pubkey(), intent_bundle: base_intent.into(), }; @@ -255,7 +255,7 @@ async fn commit_book_order_account( id: 0, slot: 10, blockhash: Hash::new_unique(), - intent_bundle_sent_transaction: Transaction::default(), + sent_transaction: Transaction::default(), payer: payer.pubkey(), intent_bundle: base_intent.into(), }; @@ -615,7 +615,7 @@ async fn commit_multiple_accounts( id: id as u64, slot: 0, blockhash: Hash::new_unique(), - intent_bundle_sent_transaction: Transaction::default(), + sent_transaction: Transaction::default(), payer: Pubkey::new_unique(), intent_bundle: base_intent.into(), }) @@ -663,7 +663,7 @@ async fn execute_intent_bundle( id: 0, slot: 0, blockhash: Hash::new_unique(), - intent_bundle_sent_transaction: Transaction::default(), + sent_transaction: Transaction::default(), payer: Pubkey::new_unique(), intent_bundle, }; From 348783e8509035ec5140aa10f482c9640b3a0528 Mon Sep 17 00:00:00 2001 From: taco-paco Date: Sun, 25 Jan 2026 14:27:07 +0700 Subject: [PATCH 33/36] fix: fmt --- magicblock-accounts/src/scheduled_commits_processor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/magicblock-accounts/src/scheduled_commits_processor.rs b/magicblock-accounts/src/scheduled_commits_processor.rs index bc172340f..88c4ed4ed 100644 --- a/magicblock-accounts/src/scheduled_commits_processor.rs +++ b/magicblock-accounts/src/scheduled_commits_processor.rs @@ -5,7 +5,7 @@ use std::{ use async_trait::async_trait; use magicblock_account_cloner::ChainlinkCloner; -use magicblock_accounts_db::{traits::AccountsBank, AccountsDb}; +use magicblock_accounts_db::AccountsDb; use magicblock_chainlink::{ remote_account_provider::{ chain_rpc_client::ChainRpcClientImpl, From 4d8cded9d77772baf05462741dee59421e2e413a Mon Sep 17 00:00:00 2001 From: taco-paco Date: Sun, 25 Jan 2026 14:48:16 +0700 Subject: [PATCH 34/36] fix: backwards compatible ix --- .../src/instruction.rs | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/magicblock-magic-program-api/src/instruction.rs b/magicblock-magic-program-api/src/instruction.rs index e1ad809e0..859cc9768 100644 --- a/magicblock-magic-program-api/src/instruction.rs +++ b/magicblock-magic-program-api/src/instruction.rs @@ -95,27 +95,6 @@ pub enum MagicBlockInstruction { /// accounts array (compact representation). ScheduleBaseIntent(MagicBaseIntentArgs), - /// Schedules execution of a *bundle* of intents in a single instruction. - /// - /// A "intent bundle" is an atomic unit of work executed by the validator on the Base layer, - /// such as: - /// - standalone base actions - /// - an optional `Commit` - /// - an optional `CommitAndUndelegate` - /// - /// This is the recommended scheduling path when the caller wants to submit multiple - /// independent intents while paying account overhead only once. - /// - /// # Account references - /// - **0.** `[WRITE, SIGNER]` Payer requesting the bundle to be scheduled - /// - **1.** `[WRITE]` Magic Context account - /// - **2..n** `[]` All accounts referenced by any intent in the bundle - /// - /// # Data - /// The embedded [`MagicIntentBundleArgs`] encodes account references by indices into the - /// accounts array. - ScheduleIntentBundle(MagicIntentBundleArgs), - /// Schedule a new task for execution /// /// # Account references @@ -147,6 +126,27 @@ pub enum MagicBlockInstruction { /// Noop instruction Noop(u64), + + /// Schedules execution of a *bundle* of intents in a single instruction. + /// + /// A "intent bundle" is an atomic unit of work executed by the validator on the Base layer, + /// such as: + /// - standalone base actions + /// - an optional `Commit` + /// - an optional `CommitAndUndelegate` + /// + /// This is the recommended scheduling path when the caller wants to submit multiple + /// independent intents while paying account overhead only once. + /// + /// # Account references + /// - **0.** `[WRITE, SIGNER]` Payer requesting the bundle to be scheduled + /// - **1.** `[WRITE]` Magic Context account + /// - **2..n** `[]` All accounts referenced by any intent in the bundle + /// + /// # Data + /// The embedded [`MagicIntentBundleArgs`] encodes account references by indices into the + /// accounts array. + ScheduleIntentBundle(MagicIntentBundleArgs), } impl MagicBlockInstruction { From 30bfa72df3d9bf6822605c04c9194041d3ff08e5 Mon Sep 17 00:00:00 2001 From: taco-paco Date: Tue, 27 Jan 2026 15:14:44 +0700 Subject: [PATCH 35/36] fix: pass --ignore-rust-version to build-sbf for bincode 2 MSRV --- Cargo.lock | 116 ++++-- magicblock-magic-program-api/Cargo.toml | 7 +- .../src/instruction.rs | 329 ++++++++++++++- programs/magicblock/Cargo.toml | 3 +- programs/magicblock/src/magic_context.rs | 388 +++++++++++++++++- .../magicblock/src/magicblock_processor.rs | 6 +- .../process_accept_scheduled_commits.rs | 46 ++- .../process_schedule_commit_tests.rs | 16 +- test-integration/Cargo.lock | 146 ++++--- test-integration/Makefile | 14 +- 10 files changed, 934 insertions(+), 137 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7129b1aea..c73b9436a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -547,6 +547,26 @@ dependencies = [ "serde", ] +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", +] + [[package]] name = "bindgen" version = "0.69.5" @@ -1838,7 +1858,7 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" name = "guinea" version = "0.6.1" dependencies = [ - "bincode", + "bincode 1.3.3", "magicblock-magic-program-api", "serde", "solana-program", @@ -2781,7 +2801,7 @@ name = "magicblock-account-cloner" version = "0.6.1" dependencies = [ "async-trait", - "bincode", + "bincode 1.3.3", "magicblock-accounts-db", "magicblock-chainlink", "magicblock-committor-service", @@ -2854,7 +2874,7 @@ dependencies = [ "agave-geyser-plugin-interface", "arc-swap", "base64 0.21.7", - "bincode", + "bincode 1.3.3", "bs58", "fastwebsockets", "futures", @@ -2962,7 +2982,7 @@ dependencies = [ "arc-swap", "assert_matches", "async-trait", - "bincode", + "bincode 1.3.3", "futures-util", "helius-laserstream", "lru", @@ -3028,7 +3048,7 @@ version = "0.6.1" dependencies = [ "async-trait", "base64 0.21.7", - "bincode", + "bincode 1.3.3", "borsh 1.6.0", "dyn-clone", "futures-util", @@ -3121,7 +3141,7 @@ name = "magicblock-delegation-program" version = "1.1.3" source = "git+https://github.com/magicblock-labs/delegation-program.git?rev=1874b4f5f5f55cb9ab54b64de2cc0d41107d1435#1874b4f5f5f55cb9ab54b64de2cc0d41107d1435" dependencies = [ - "bincode", + "bincode 1.3.3", "borsh 1.6.0", "bytemuck", "num_enum", @@ -3143,7 +3163,7 @@ name = "magicblock-ledger" version = "0.6.1" dependencies = [ "arc-swap", - "bincode", + "bincode 1.3.3", "byteorder", "fs_extra", "libc", @@ -3185,7 +3205,8 @@ dependencies = [ name = "magicblock-magic-program-api" version = "0.6.1" dependencies = [ - "bincode", + "bincode 1.3.3", + "bincode 2.0.1", "serde", "solana-program", ] @@ -3208,7 +3229,7 @@ dependencies = [ name = "magicblock-processor" version = "0.6.1" dependencies = [ - "bincode", + "bincode 1.3.3", "guinea", "magicblock-accounts-db", "magicblock-core", @@ -3250,7 +3271,8 @@ name = "magicblock-program" version = "0.6.1" dependencies = [ "assert_matches", - "bincode", + "bincode 1.3.3", + "bincode 2.0.1", "lazy_static", "magicblock-chainlink", "magicblock-core", @@ -3333,7 +3355,7 @@ dependencies = [ name = "magicblock-task-scheduler" version = "0.6.1" dependencies = [ - "bincode", + "bincode 1.3.3", "chrono", "futures-util", "magicblock-config", @@ -5299,7 +5321,7 @@ name = "solana-account" version = "2.2.1" source = "git+https://github.com/magicblock-labs/solana-account.git?rev=2246929#2246929c6614f60d9909fdd117aab3bc454a9775" dependencies = [ - "bincode", + "bincode 1.3.3", "qualifier_attr", "serde", "serde_bytes", @@ -5320,7 +5342,7 @@ checksum = "c472eebf9ec7ee72c8d25e990a2eaf6b0b783619ef84d7954c408d6442ad5e57" dependencies = [ "Inflector", "base64 0.22.1", - "bincode", + "bincode 1.3.3", "bs58", "bv", "lazy_static", @@ -5373,7 +5395,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0c17d606a298a205fae325489fbed88ee6dc4463c111672172327e741c8905d" dependencies = [ - "bincode", + "bincode 1.3.3", "serde", "solana-program-error", "solana-program-memory", @@ -5386,7 +5408,7 @@ version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1673f67efe870b64a65cb39e6194be5b26527691ce5922909939961a6e6b395" dependencies = [ - "bincode", + "bincode 1.3.3", "bytemuck", "serde", "serde_derive", @@ -5403,7 +5425,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c758a82a60e5fcc93b3ee00615b0e244295aa8b2308475ea2b48f4900862a2e0" dependencies = [ - "bincode", + "bincode 1.3.3", "bytemuck", "log", "num-derive", @@ -5448,7 +5470,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19a3787b8cf9c9fe3dd360800e8b70982b9e5a8af9e11c354b6665dd4a003adc" dependencies = [ - "bincode", + "bincode 1.3.3", "serde", "solana-instruction", ] @@ -5496,7 +5518,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cbc2581d0f39cd7698e46baa06fc5e8928b323a85ed3a4fdbdfe0d7ea9fc152" dependencies = [ - "bincode", + "bincode 1.3.3", "libsecp256k1", "qualifier_attr", "scopeguard", @@ -5677,7 +5699,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab5647203179631940e0659a635e5d3f514ba60f6457251f8f8fbf3830e56b0" dependencies = [ - "bincode", + "bincode 1.3.3", "chrono", "serde", "serde_derive", @@ -5839,7 +5861,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f9c7fbf3e58b64a667c5f35e90af580538a95daea7001ff7806c0662d301bdf" dependencies = [ - "bincode", + "bincode 1.3.3", "serde", "serde_derive", "solana-account", @@ -5917,7 +5939,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "968dabd2b92d57131473eddbd475339da530e14f54397386abf303de3a2595a2" dependencies = [ - "bincode", + "bincode 1.3.3", "chrono", "memmap2 0.5.10", "serde", @@ -5996,7 +6018,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce496a475e5062ba5de97215ab39d9c358f9c9df4bb7f3a45a1f1a8bd9065ed" dependencies = [ - "bincode", + "bincode 1.3.3", "borsh 1.6.0", "getrandom 0.2.16", "js-sys", @@ -6173,7 +6195,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "268486ba8a294ed22a4d7c1ec05f540c3dbe71cfa7c6c54b6d4d13668d895678" dependencies = [ - "bincode", + "bincode 1.3.3", "blake3", "lazy_static", "serde", @@ -6271,7 +6293,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "004f2d2daf407b3ec1a1ca5ec34b3ccdfd6866dd2d3c7d0715004a96e4b6d127" dependencies = [ - "bincode", + "bincode 1.3.3", "bitflags 2.10.0", "cfg_eval", "serde", @@ -6345,7 +6367,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "586469467e93ceb79048f8d8e3a619bf61d05396ee7de95cb40280301a589d05" dependencies = [ - "bincode", + "bincode 1.3.3", "blake3", "borsh 0.10.4", "borsh 1.6.0", @@ -6479,7 +6501,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c3d36fed5548b1a8625eb071df6031a95aa69f884e29bf244821e53c49372bc" dependencies = [ "base64 0.22.1", - "bincode", + "bincode 1.3.3", "enum-iterator", "itertools 0.12.1", "log", @@ -6646,7 +6668,7 @@ checksum = "7cb874b757d9d3c646f031132b20d43538309060a32d02b4aebb0f8fc2cd159a" dependencies = [ "async-trait", "base64 0.22.1", - "bincode", + "bincode 1.3.3", "bs58", "indicatif", "log", @@ -6736,7 +6758,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4808e8d7f3c931657e615042d4176b423e66f64dc99e3dc3c735a197e512029b" dependencies = [ - "bincode", + "bincode 1.3.3", "bs58", "getrandom 0.1.16", "js-sys", @@ -6828,7 +6850,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0a1caa972414cc78122c32bdae65ac5fe89df7db598585a5cde19d16a20280a" dependencies = [ - "bincode", + "bincode 1.3.3", "digest 0.10.7", "libsecp256k1", "serde", @@ -7044,7 +7066,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dabc713c25ff999424ec68ac4572f2ff6bfd6317922c7864435ccaf9c76504a8" dependencies = [ - "bincode", + "bincode 1.3.3", "log", "solana-account", "solana-bincode", @@ -7071,7 +7093,7 @@ dependencies = [ name = "solana-storage-proto" version = "0.6.1" dependencies = [ - "bincode", + "bincode 1.3.3", "bs58", "enum-iterator", "prost 0.11.9", @@ -7180,7 +7202,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43c8f684977e4439031b3a27b954ab05a6bdf697d581692aaf8888cf92b73b9e" dependencies = [ - "bincode", + "bincode 1.3.3", "log", "serde", "serde_derive", @@ -7222,7 +7244,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf6b44740d7f0c9f375d045c165bc0aab4a90658f92d6835aeb0649afaeaff9a" dependencies = [ "base64 0.22.1", - "bincode", + "bincode 1.3.3", "bytemuck", "bytemuck_derive", "lazy_static", @@ -7285,7 +7307,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "753b3e9afed170e4cfc0ea1e87b5dfdc6d4a50270869414edd24c6ea1f529b29" dependencies = [ - "bincode", + "bincode 1.3.3", "serde", "serde_derive", "solana-bincode", @@ -7313,7 +7335,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5022de04cbba05377f68bf848c8c1322ead733f88a657bf792bb40f3257b8218" dependencies = [ - "bincode", + "bincode 1.3.3", "serde", "serde_derive", "solana-account", @@ -7343,7 +7365,7 @@ checksum = "64f739fb4230787b010aa4a49d3feda8b53aac145a9bc3ac2dd44337c6ecb544" dependencies = [ "Inflector", "base64 0.22.1", - "bincode", + "bincode 1.3.3", "borsh 1.6.0", "bs58", "lazy_static", @@ -7383,7 +7405,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5ac91c8f0465c566164044ad7b3d18d15dfabab1b8b4a4a01cb83c047efdaae" dependencies = [ "base64 0.22.1", - "bincode", + "bincode 1.3.3", "bs58", "serde", "serde_derive", @@ -7435,7 +7457,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4507bb9d071fb81cfcf676f12fba3db4098f764524ef0b5567d671a81d41f3e" dependencies = [ - "bincode", + "bincode 1.3.3", "num-derive", "num-traits", "serde", @@ -7459,7 +7481,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab654bb2622d85b2ca0c36cb89c99fa1286268e0d784efec03a3d42e9c6a55f4" dependencies = [ - "bincode", + "bincode 1.3.3", "log", "num-derive", "num-traits", @@ -7494,7 +7516,7 @@ checksum = "c70bffb28540a216443ba302ab017d18a0e03f5300772929db79608870ee1c6e" dependencies = [ "aes-gcm-siv", "base64 0.22.1", - "bincode", + "bincode 1.3.3", "bytemuck", "bytemuck_derive", "curve25519-dalek 4.1.3", @@ -8780,6 +8802,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + [[package]] name = "uriparse" version = "0.6.4" @@ -8855,6 +8883,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + [[package]] name = "void" version = "1.0.2" @@ -9435,7 +9469,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cec3b1c61e97383dc6f7c66240243c8981c16ba519c8bdf0310560db2a18876d" dependencies = [ "anyhow", - "bincode", + "bincode 1.3.3", "prost 0.13.5", "prost-types 0.13.5", "protobuf-src", diff --git a/magicblock-magic-program-api/Cargo.toml b/magicblock-magic-program-api/Cargo.toml index dabafc8d4..8ecf9e777 100644 --- a/magicblock-magic-program-api/Cargo.toml +++ b/magicblock-magic-program-api/Cargo.toml @@ -10,5 +10,8 @@ edition.workspace = true [dependencies] solana-program = ">=1.16, <3.0.0" -bincode = "^1.3.3" -serde = { version = "^1.0.228", features = ["derive"] } +bincode = { version = "2.0.1", default-features = false, features = ["alloc", "serde"] } +serde = { version = "^1.0.228", default-features = false, features = ["derive", "alloc"] } + +[dev-dependencies] +bincode1 = { package = "bincode", version = "1.3.3" } diff --git a/magicblock-magic-program-api/src/instruction.rs b/magicblock-magic-program-api/src/instruction.rs index 859cc9768..dc419e899 100644 --- a/magicblock-magic-program-api/src/instruction.rs +++ b/magicblock-magic-program-api/src/instruction.rs @@ -150,8 +150,8 @@ pub enum MagicBlockInstruction { } impl MagicBlockInstruction { - pub fn try_to_vec(&self) -> Result, bincode::Error> { - bincode::serialize(self) + pub fn try_to_vec(&self) -> Result, bincode::error::EncodeError> { + bincode::serde::encode_to_vec(self, bincode::config::legacy()) } } @@ -177,3 +177,328 @@ pub struct AccountModificationForInstruction { pub confined: Option, pub remote_slot: Option, } + +#[cfg(test)] +mod tests { + use solana_program::instruction::{AccountMeta, Instruction}; + + use super::*; + use crate::args::{ + ActionArgs, BaseActionArgs, CommitAndUndelegateArgs, CommitTypeArgs, + MagicBaseIntentArgs, MagicIntentBundleArgs, ScheduleTaskArgs, + ShortAccountMeta, UndelegateTypeArgs, + }; + + /// Helper to verify bincode 2 (legacy config) produces identical bytes to bincode 1.3 + fn assert_bincode_compatible(value: &T, name: &str) { + let bincode2_bytes = + bincode::serde::encode_to_vec(value, bincode::config::legacy()) + .unwrap(); + let bincode1_bytes = bincode1::serialize(value).unwrap(); + assert_eq!( + bincode2_bytes, bincode1_bytes, + "{} serialization mismatch:\nbincode2: {:?}\nbincode1: {:?}", + name, bincode2_bytes, bincode1_bytes + ); + } + + /// Helper to verify bincode 2 can deserialize bincode 1 serialized data + fn assert_bincode_deserialize_compatible(value: &T, name: &str) + where + T: Serialize + for<'de> Deserialize<'de> + PartialEq + std::fmt::Debug, + { + let bincode1_bytes = bincode1::serialize(value).unwrap(); + let bincode2_result: T = bincode::serde::decode_from_slice( + &bincode1_bytes, + bincode::config::legacy(), + ) + .unwrap() + .0; + assert_eq!(&bincode2_result, value, "{} round-trip mismatch", name); + } + + fn test_pubkey() -> Pubkey { + Pubkey::new_from_array([1u8; 32]) + } + + fn test_pubkey2() -> Pubkey { + Pubkey::new_from_array([2u8; 32]) + } + + #[test] + fn test_noop_compatibility() { + let instruction = MagicBlockInstruction::Noop(42); + assert_bincode_compatible(&instruction, "Noop"); + assert_bincode_deserialize_compatible(&instruction, "Noop"); + } + + #[test] + fn test_schedule_commit_compatibility() { + let instruction = MagicBlockInstruction::ScheduleCommit; + assert_bincode_compatible(&instruction, "ScheduleCommit"); + assert_bincode_deserialize_compatible(&instruction, "ScheduleCommit"); + } + + #[test] + fn test_schedule_commit_and_undelegate_compatibility() { + let instruction = MagicBlockInstruction::ScheduleCommitAndUndelegate; + assert_bincode_compatible(&instruction, "ScheduleCommitAndUndelegate"); + assert_bincode_deserialize_compatible( + &instruction, + "ScheduleCommitAndUndelegate", + ); + } + + #[test] + fn test_accept_schedule_commits_compatibility() { + let instruction = MagicBlockInstruction::AcceptScheduleCommits; + assert_bincode_compatible(&instruction, "AcceptScheduleCommits"); + assert_bincode_deserialize_compatible( + &instruction, + "AcceptScheduleCommits", + ); + } + + #[test] + fn test_scheduled_commit_sent_compatibility() { + let instruction = + MagicBlockInstruction::ScheduledCommitSent((123, 456)); + assert_bincode_compatible(&instruction, "ScheduledCommitSent"); + assert_bincode_deserialize_compatible( + &instruction, + "ScheduledCommitSent", + ); + } + + #[test] + fn test_cancel_task_compatibility() { + let instruction = MagicBlockInstruction::CancelTask { task_id: 999 }; + assert_bincode_compatible(&instruction, "CancelTask"); + assert_bincode_deserialize_compatible(&instruction, "CancelTask"); + } + + #[test] + fn test_modify_accounts_compatibility() { + let mut accounts = HashMap::new(); + accounts.insert( + test_pubkey(), + AccountModificationForInstruction { + lamports: Some(1000), + owner: Some(test_pubkey2()), + executable: Some(false), + data_key: Some(42), + delegated: Some(true), + confined: Some(false), + remote_slot: Some(100), + }, + ); + let instruction = MagicBlockInstruction::ModifyAccounts { + accounts, + message: Some("test message".to_string()), + }; + assert_bincode_compatible(&instruction, "ModifyAccounts"); + assert_bincode_deserialize_compatible(&instruction, "ModifyAccounts"); + } + + #[test] + fn test_schedule_base_intent_base_actions_compatibility() { + let base_action = BaseActionArgs { + args: ActionArgs::new(vec![1, 2, 3]), + compute_units: 1000, + escrow_authority: 0, + destination_program: test_pubkey(), + accounts: vec![ShortAccountMeta { + pubkey: test_pubkey2(), + is_writable: true, + }], + }; + let instruction = MagicBlockInstruction::ScheduleBaseIntent( + MagicBaseIntentArgs::BaseActions(vec![base_action]), + ); + assert_bincode_compatible( + &instruction, + "ScheduleBaseIntent::BaseActions", + ); + assert_bincode_deserialize_compatible( + &instruction, + "ScheduleBaseIntent::BaseActions", + ); + } + + #[test] + fn test_schedule_base_intent_commit_standalone_compatibility() { + let instruction = MagicBlockInstruction::ScheduleBaseIntent( + MagicBaseIntentArgs::Commit(CommitTypeArgs::Standalone(vec![ + 0, 1, 2, + ])), + ); + assert_bincode_compatible( + &instruction, + "ScheduleBaseIntent::Commit::Standalone", + ); + assert_bincode_deserialize_compatible( + &instruction, + "ScheduleBaseIntent::Commit::Standalone", + ); + } + + #[test] + fn test_schedule_base_intent_commit_with_actions_compatibility() { + let base_action = BaseActionArgs { + args: ActionArgs::new(vec![4, 5, 6]).with_escrow_index(1), + compute_units: 2000, + escrow_authority: 2, + destination_program: test_pubkey(), + accounts: vec![], + }; + let instruction = MagicBlockInstruction::ScheduleBaseIntent( + MagicBaseIntentArgs::Commit(CommitTypeArgs::WithBaseActions { + committed_accounts: vec![0, 1], + base_actions: vec![base_action], + }), + ); + assert_bincode_compatible( + &instruction, + "ScheduleBaseIntent::Commit::WithBaseActions", + ); + assert_bincode_deserialize_compatible( + &instruction, + "ScheduleBaseIntent::Commit::WithBaseActions", + ); + } + + #[test] + fn test_schedule_base_intent_commit_and_undelegate_compatibility() { + let instruction = MagicBlockInstruction::ScheduleBaseIntent( + MagicBaseIntentArgs::CommitAndUndelegate(CommitAndUndelegateArgs { + commit_type: CommitTypeArgs::Standalone(vec![0]), + undelegate_type: UndelegateTypeArgs::Standalone, + }), + ); + assert_bincode_compatible( + &instruction, + "ScheduleBaseIntent::CommitAndUndelegate", + ); + assert_bincode_deserialize_compatible( + &instruction, + "ScheduleBaseIntent::CommitAndUndelegate", + ); + } + + #[test] + fn test_schedule_intent_bundle_compatibility() { + let base_action = BaseActionArgs { + args: ActionArgs::new(vec![7, 8, 9]), + compute_units: 3000, + escrow_authority: 1, + destination_program: test_pubkey2(), + accounts: vec![ShortAccountMeta { + pubkey: test_pubkey(), + is_writable: false, + }], + }; + let instruction = MagicBlockInstruction::ScheduleIntentBundle( + MagicIntentBundleArgs { + commit: Some(CommitTypeArgs::Standalone(vec![0, 1])), + commit_and_undelegate: Some(CommitAndUndelegateArgs { + commit_type: CommitTypeArgs::Standalone(vec![2]), + undelegate_type: UndelegateTypeArgs::WithBaseActions { + base_actions: vec![base_action.clone()], + }, + }), + standalone_actions: vec![base_action], + }, + ); + assert_bincode_compatible(&instruction, "ScheduleIntentBundle"); + assert_bincode_deserialize_compatible( + &instruction, + "ScheduleIntentBundle", + ); + } + + #[test] + fn test_schedule_task_compatibility() { + let instruction = + MagicBlockInstruction::ScheduleTask(ScheduleTaskArgs { + task_id: 12345, + execution_interval_millis: 1000, + iterations: 10, + instructions: vec![Instruction { + program_id: test_pubkey(), + accounts: vec![AccountMeta::new(test_pubkey2(), true)], + data: vec![1, 2, 3, 4], + }], + }); + assert_bincode_compatible(&instruction, "ScheduleTask"); + assert_bincode_deserialize_compatible(&instruction, "ScheduleTask"); + } + + #[test] + fn test_account_modification_compatibility() { + let modification = AccountModification { + pubkey: test_pubkey(), + lamports: Some(5000), + owner: Some(test_pubkey2()), + executable: Some(true), + data: Some(vec![0xde, 0xad, 0xbe, 0xef]), + delegated: Some(true), + confined: Some(false), + remote_slot: Some(999), + }; + assert_bincode_compatible(&modification, "AccountModification"); + assert_bincode_deserialize_compatible( + &modification, + "AccountModification", + ); + } + + #[test] + fn test_short_account_meta_compatibility() { + let meta = ShortAccountMeta { + pubkey: test_pubkey(), + is_writable: true, + }; + assert_bincode_compatible(&meta, "ShortAccountMeta"); + assert_bincode_deserialize_compatible(&meta, "ShortAccountMeta"); + } + + #[test] + fn test_action_args_compatibility() { + let args = ActionArgs::new(vec![10, 20, 30]).with_escrow_index(5); + assert_bincode_compatible(&args, "ActionArgs"); + assert_bincode_deserialize_compatible(&args, "ActionArgs"); + } + + #[test] + fn test_base_action_args_compatibility() { + let args = BaseActionArgs { + args: ActionArgs::new(vec![100, 200]), + compute_units: 50000, + escrow_authority: 3, + destination_program: test_pubkey(), + accounts: vec![ + ShortAccountMeta { + pubkey: test_pubkey(), + is_writable: true, + }, + ShortAccountMeta { + pubkey: test_pubkey2(), + is_writable: false, + }, + ], + }; + assert_bincode_compatible(&args, "BaseActionArgs"); + assert_bincode_deserialize_compatible(&args, "BaseActionArgs"); + } + + #[test] + fn test_magic_intent_bundle_args_compatibility() { + let args = MagicIntentBundleArgs { + commit: Some(CommitTypeArgs::Standalone(vec![0])), + commit_and_undelegate: None, + standalone_actions: vec![], + }; + assert_bincode_compatible(&args, "MagicIntentBundleArgs"); + assert_bincode_deserialize_compatible(&args, "MagicIntentBundleArgs"); + } +} diff --git a/programs/magicblock/Cargo.toml b/programs/magicblock/Cargo.toml index 0ad4396b9..4f288af98 100644 --- a/programs/magicblock/Cargo.toml +++ b/programs/magicblock/Cargo.toml @@ -8,7 +8,7 @@ license = { workspace = true } edition = { workspace = true } [dependencies] -bincode = { workspace = true } +bincode = "2.0.1" lazy_static = { workspace = true } magicblock-core = { workspace = true } magicblock-magic-program-api = { workspace = true } @@ -36,6 +36,7 @@ solana-transaction-context = { workspace = true } thiserror = { workspace = true } [dev-dependencies] +bincode1 = { package = "bincode", version = "1.3.3" } test-kit = { workspace = true } assert_matches = { workspace = true } rand = { workspace = true } diff --git a/programs/magicblock/src/magic_context.rs b/programs/magicblock/src/magic_context.rs index 84a0e1619..3a19acc5f 100644 --- a/programs/magicblock/src/magic_context.rs +++ b/programs/magicblock/src/magic_context.rs @@ -7,6 +7,7 @@ use solana_account::{AccountSharedData, ReadableAccount}; use crate::magic_scheduled_base_intent::ScheduledIntentBundle; #[derive(Debug, Default, Serialize, Deserialize)] +#[cfg_attr(test, derive(PartialEq))] pub struct MagicContext { pub intent_id: u64, pub scheduled_base_intents: Vec, @@ -17,11 +18,15 @@ impl MagicContext { pub const ZERO: [u8; Self::SIZE] = [0; Self::SIZE]; pub(crate) fn deserialize( data: &AccountSharedData, - ) -> Result { + ) -> Result { if data.data().is_empty() { Ok(Self::default()) } else { - data.deserialize_data() + Ok(bincode::serde::decode_from_slice( + data.data(), + bincode::config::legacy(), + )? + .0) } } @@ -65,3 +70,382 @@ fn is_zeroed(buf: &[u8]) -> bool { && chunks.remainder() == &ZEROS[..chunks.remainder().len()] } } + +#[cfg(test)] +mod bincode_compatibility_tests { + use magicblock_magic_program_api::args::ShortAccountMeta; + use solana_account::Account; + use solana_hash::Hash; + use solana_pubkey::Pubkey; + use solana_transaction::Transaction; + + use super::*; + use crate::magic_scheduled_base_intent::{ + BaseAction, CommitAndUndelegate, CommitType, CommittedAccount, + MagicBaseIntent, MagicIntentBundle, ProgramArgs, ScheduledIntentBundle, + UndelegateType, + }; + + /// Helper to verify bincode 2 (legacy config) produces identical bytes to bincode 1.3 + fn assert_bincode_compatible(value: &T, name: &str) { + let bincode2_bytes = + bincode::serde::encode_to_vec(value, bincode::config::legacy()) + .unwrap(); + let bincode1_bytes = bincode1::serialize(value).unwrap(); + assert_eq!( + bincode2_bytes, bincode1_bytes, + "{} serialization mismatch:\nbincode2: {:?}\nbincode1: {:?}", + name, bincode2_bytes, bincode1_bytes + ); + } + + /// Helper to verify bincode 2 can deserialize bincode 1 serialized data + fn assert_bincode_deserialize_compatible(value: &T, name: &str) + where + T: Serialize + for<'de> Deserialize<'de> + PartialEq + std::fmt::Debug, + { + let bincode1_bytes = bincode1::serialize(value).unwrap(); + let bincode2_result: T = bincode::serde::decode_from_slice( + &bincode1_bytes, + bincode::config::legacy(), + ) + .unwrap() + .0; + assert_eq!(&bincode2_result, value, "{} round-trip mismatch", name); + } + + fn test_pubkey() -> Pubkey { + Pubkey::new_from_array([1u8; 32]) + } + + fn test_pubkey2() -> Pubkey { + Pubkey::new_from_array([2u8; 32]) + } + + fn test_hash() -> Hash { + Hash::new_from_array([3u8; 32]) + } + + fn test_account() -> Account { + Account { + lamports: 1000, + data: vec![1, 2, 3, 4], + owner: test_pubkey(), + executable: false, + rent_epoch: 0, + } + } + + fn test_committed_account() -> CommittedAccount { + CommittedAccount { + pubkey: test_pubkey(), + account: test_account(), + remote_slot: 100, + } + } + + fn test_base_action() -> BaseAction { + BaseAction { + compute_units: 5000, + destination_program: test_pubkey2(), + escrow_authority: test_pubkey(), + data_per_program: ProgramArgs { + escrow_index: 1, + data: vec![10, 20, 30], + }, + account_metas_per_program: vec![ShortAccountMeta { + pubkey: test_pubkey(), + is_writable: true, + }], + } + } + + #[test] + fn test_magic_context_empty_compatibility() { + let context = MagicContext::default(); + assert_bincode_compatible(&context, "MagicContext::default"); + assert_bincode_deserialize_compatible( + &context, + "MagicContext::default", + ); + } + + #[test] + fn test_magic_context_with_intent_id_compatibility() { + let context = MagicContext { + intent_id: 42, + scheduled_base_intents: vec![], + }; + assert_bincode_compatible(&context, "MagicContext with intent_id"); + assert_bincode_deserialize_compatible( + &context, + "MagicContext with intent_id", + ); + } + + #[test] + fn test_committed_account_compatibility() { + let account = test_committed_account(); + assert_bincode_compatible(&account, "CommittedAccount"); + assert_bincode_deserialize_compatible(&account, "CommittedAccount"); + } + + #[test] + fn test_program_args_compatibility() { + let args = ProgramArgs { + escrow_index: 5, + data: vec![100, 200, 255], + }; + assert_bincode_compatible(&args, "ProgramArgs"); + assert_bincode_deserialize_compatible(&args, "ProgramArgs"); + } + + #[test] + fn test_base_action_compatibility() { + let action = test_base_action(); + assert_bincode_compatible(&action, "BaseAction"); + assert_bincode_deserialize_compatible(&action, "BaseAction"); + } + + #[test] + fn test_commit_type_standalone_compatibility() { + let commit = CommitType::Standalone(vec![test_committed_account()]); + assert_bincode_compatible(&commit, "CommitType::Standalone"); + assert_bincode_deserialize_compatible( + &commit, + "CommitType::Standalone", + ); + } + + #[test] + fn test_commit_type_with_base_actions_compatibility() { + let commit = CommitType::WithBaseActions { + committed_accounts: vec![test_committed_account()], + base_actions: vec![test_base_action()], + }; + assert_bincode_compatible(&commit, "CommitType::WithBaseActions"); + assert_bincode_deserialize_compatible( + &commit, + "CommitType::WithBaseActions", + ); + } + + #[test] + fn test_undelegate_type_standalone_compatibility() { + let undelegate = UndelegateType::Standalone; + assert_bincode_compatible(&undelegate, "UndelegateType::Standalone"); + assert_bincode_deserialize_compatible( + &undelegate, + "UndelegateType::Standalone", + ); + } + + #[test] + fn test_undelegate_type_with_base_actions_compatibility() { + let undelegate = + UndelegateType::WithBaseActions(vec![test_base_action()]); + assert_bincode_compatible( + &undelegate, + "UndelegateType::WithBaseActions", + ); + assert_bincode_deserialize_compatible( + &undelegate, + "UndelegateType::WithBaseActions", + ); + } + + #[test] + fn test_commit_and_undelegate_compatibility() { + let cau = CommitAndUndelegate { + commit_action: CommitType::Standalone(vec![ + test_committed_account(), + ]), + undelegate_action: UndelegateType::Standalone, + }; + assert_bincode_compatible(&cau, "CommitAndUndelegate"); + assert_bincode_deserialize_compatible(&cau, "CommitAndUndelegate"); + } + + #[test] + fn test_magic_base_intent_base_actions_compatibility() { + let intent = MagicBaseIntent::BaseActions(vec![test_base_action()]); + assert_bincode_compatible(&intent, "MagicBaseIntent::BaseActions"); + assert_bincode_deserialize_compatible( + &intent, + "MagicBaseIntent::BaseActions", + ); + } + + #[test] + fn test_magic_base_intent_commit_compatibility() { + let intent = MagicBaseIntent::Commit(CommitType::Standalone(vec![ + test_committed_account(), + ])); + assert_bincode_compatible(&intent, "MagicBaseIntent::Commit"); + assert_bincode_deserialize_compatible( + &intent, + "MagicBaseIntent::Commit", + ); + } + + #[test] + fn test_magic_base_intent_commit_and_undelegate_compatibility() { + let intent = + MagicBaseIntent::CommitAndUndelegate(CommitAndUndelegate { + commit_action: CommitType::Standalone(vec![ + test_committed_account(), + ]), + undelegate_action: UndelegateType::WithBaseActions(vec![ + test_base_action(), + ]), + }); + assert_bincode_compatible( + &intent, + "MagicBaseIntent::CommitAndUndelegate", + ); + assert_bincode_deserialize_compatible( + &intent, + "MagicBaseIntent::CommitAndUndelegate", + ); + } + + #[test] + fn test_magic_intent_bundle_empty_compatibility() { + let bundle = MagicIntentBundle::default(); + assert_bincode_compatible(&bundle, "MagicIntentBundle::default"); + assert_bincode_deserialize_compatible( + &bundle, + "MagicIntentBundle::default", + ); + } + + #[test] + fn test_magic_intent_bundle_full_compatibility() { + let bundle = MagicIntentBundle { + commit: Some(CommitType::Standalone( + vec![test_committed_account()], + )), + commit_and_undelegate: Some(CommitAndUndelegate { + commit_action: CommitType::WithBaseActions { + committed_accounts: vec![test_committed_account()], + base_actions: vec![test_base_action()], + }, + undelegate_action: UndelegateType::Standalone, + }), + standalone_actions: vec![test_base_action()], + }; + assert_bincode_compatible(&bundle, "MagicIntentBundle full"); + assert_bincode_deserialize_compatible( + &bundle, + "MagicIntentBundle full", + ); + } + + #[test] + fn test_scheduled_intent_bundle_compatibility() { + let bundle = ScheduledIntentBundle { + id: 12345, + slot: 67890, + blockhash: test_hash(), + sent_transaction: Transaction::default(), + payer: test_pubkey(), + intent_bundle: MagicIntentBundle { + commit: Some(CommitType::Standalone(vec![ + test_committed_account(), + ])), + commit_and_undelegate: None, + standalone_actions: vec![], + }, + }; + assert_bincode_compatible(&bundle, "ScheduledIntentBundle"); + assert_bincode_deserialize_compatible(&bundle, "ScheduledIntentBundle"); + } + + #[test] + fn test_magic_context_with_scheduled_intents_compatibility() { + let context = MagicContext { + intent_id: 999, + scheduled_base_intents: vec![ScheduledIntentBundle { + id: 1, + slot: 100, + blockhash: test_hash(), + sent_transaction: Transaction::default(), + payer: test_pubkey(), + intent_bundle: MagicIntentBundle { + commit: Some(CommitType::Standalone(vec![ + test_committed_account(), + ])), + commit_and_undelegate: Some(CommitAndUndelegate { + commit_action: CommitType::Standalone(vec![ + test_committed_account(), + ]), + undelegate_action: UndelegateType::Standalone, + }), + standalone_actions: vec![test_base_action()], + }, + }], + }; + assert_bincode_compatible( + &context, + "MagicContext with scheduled intents", + ); + assert_bincode_deserialize_compatible( + &context, + "MagicContext with scheduled intents", + ); + } + + /// Test that bincode 2 can deserialize data that was serialized with bincode 1 + /// This is crucial for backwards compatibility with existing deployed contracts + #[test] + fn test_backwards_compatibility_magic_context() { + let context = MagicContext { + intent_id: 42, + scheduled_base_intents: vec![ScheduledIntentBundle { + id: 1, + slot: 100, + blockhash: test_hash(), + sent_transaction: Transaction::default(), + payer: test_pubkey(), + intent_bundle: MagicIntentBundle::default(), + }], + }; + + // Simulate data serialized by old bincode-based contracts + let bincode1_serialized = bincode1::serialize(&context).unwrap(); + + // Verify bincode 2 can deserialize it + let deserialized: MagicContext = bincode::serde::decode_from_slice( + &bincode1_serialized, + bincode::config::legacy(), + ) + .unwrap() + .0; + assert_eq!(deserialized.intent_id, context.intent_id); + assert_eq!( + deserialized.scheduled_base_intents.len(), + context.scheduled_base_intents.len() + ); + } + + /// Test that data serialized with bincode 2 can be deserialized with bincode 1 + /// This ensures new code is compatible with any remaining bincode 1 users + #[test] + fn test_forwards_compatibility_magic_context() { + let context = MagicContext { + intent_id: 123, + scheduled_base_intents: vec![], + }; + + // Serialize with bincode 2 (new code) + let bincode2_serialized = + bincode::serde::encode_to_vec(&context, bincode::config::legacy()) + .unwrap(); + + // Verify bincode 1 can deserialize it (old code compatibility) + let deserialized: MagicContext = + bincode1::deserialize(&bincode2_serialized).unwrap(); + assert_eq!(deserialized.intent_id, context.intent_id); + assert!(deserialized.scheduled_base_intents.is_empty()); + } +} diff --git a/programs/magicblock/src/magicblock_processor.rs b/programs/magicblock/src/magicblock_processor.rs index 30bbaec9a..0997f56b5 100644 --- a/programs/magicblock/src/magicblock_processor.rs +++ b/programs/magicblock/src/magicblock_processor.rs @@ -19,15 +19,17 @@ declare_process_instruction!( DEFAULT_COMPUTE_UNITS, |invoke_context| { use MagicBlockInstruction::*; - let instruction: MagicBlockInstruction = bincode::deserialize( + let instruction: MagicBlockInstruction = bincode::serde::decode_from_slice( invoke_context .transaction_context .get_current_instruction_context()? .get_instruction_data(), + bincode::config::legacy(), ) .map_err(|_| { solana_instruction::error::InstructionError::InvalidInstructionData - })?; + })? + .0; let transaction_context = &invoke_context.transaction_context; let instruction_context = diff --git a/programs/magicblock/src/schedule_transactions/process_accept_scheduled_commits.rs b/programs/magicblock/src/schedule_transactions/process_accept_scheduled_commits.rs index fc3f7c764..d88d54f3c 100644 --- a/programs/magicblock/src/schedule_transactions/process_accept_scheduled_commits.rs +++ b/programs/magicblock/src/schedule_transactions/process_accept_scheduled_commits.rs @@ -1,6 +1,6 @@ use std::collections::HashSet; -use solana_account::ReadableAccount; +use solana_account::{ReadableAccount, WritableAccount}; use solana_instruction::error::InstructionError; use solana_log_collector::ic_msg; use solana_program_runtime::invoke_context::InvokeContext; @@ -35,16 +35,19 @@ pub fn process_accept_scheduled_commits( transaction_context, MAGIC_CONTEXT_IDX, )?; - let mut magic_context = - bincode::deserialize::(magic_context_acc.borrow().data()) - .map_err(|err| { - ic_msg!( - invoke_context, - "Failed to deserialize MagicContext: {}", - err - ); - InstructionError::InvalidAccountData - })?; + let mut magic_context: MagicContext = bincode::serde::decode_from_slice( + magic_context_acc.borrow().data(), + bincode::config::legacy(), + ) + .map_err(|err| { + ic_msg!( + invoke_context, + "Failed to deserialize MagicContext: {}", + err + ); + InstructionError::InvalidAccountData + })? + .0; if magic_context.scheduled_base_intents.is_empty() { ic_msg!( invoke_context, @@ -96,17 +99,16 @@ pub fn process_accept_scheduled_commits( .borrow_mut() .set_data_from_slice(&MagicContext::ZERO); - magic_context_acc - .borrow_mut() - .serialize_data(&magic_context) - .map_err(|err| { - ic_msg!( - invoke_context, - "Failed to serialize MagicContext: {}", - err - ); - InstructionError::GenericError - })?; + let encoded = bincode::serde::encode_to_vec( + &magic_context, + bincode::config::legacy(), + ) + .map_err(|err| { + ic_msg!(invoke_context, "Failed to serialize MagicContext: {}", err); + InstructionError::GenericError + })?; + magic_context_acc.borrow_mut().data_as_mut_slice()[..encoded.len()] + .copy_from_slice(&encoded); Ok(()) } diff --git a/programs/magicblock/src/schedule_transactions/process_schedule_commit_tests.rs b/programs/magicblock/src/schedule_transactions/process_schedule_commit_tests.rs index 128e777b8..050adb8a3 100644 --- a/programs/magicblock/src/schedule_transactions/process_schedule_commit_tests.rs +++ b/programs/magicblock/src/schedule_transactions/process_schedule_commit_tests.rs @@ -154,8 +154,12 @@ fn assert_non_accepted_actions<'a>( ) -> &'a AccountSharedData { let magic_context_acc = find_magic_context_account(processed_scheduled) .expect("magic context account not found"); - let magic_context = - bincode::deserialize::(magic_context_acc.data()).unwrap(); + let magic_context: MagicContext = bincode::serde::decode_from_slice( + magic_context_acc.data(), + bincode::config::legacy(), + ) + .unwrap() + .0; let accepted_scheduled_actions = TransactionScheduler::default().get_scheduled_actions_by_payer(payer); @@ -175,8 +179,12 @@ fn assert_accepted_actions( ) -> Vec { let magic_context_acc = find_magic_context_account(processed_accepted) .expect("magic context account not found"); - let magic_context = - bincode::deserialize::(magic_context_acc.data()).unwrap(); + let magic_context: MagicContext = bincode::serde::decode_from_slice( + magic_context_acc.data(), + bincode::config::legacy(), + ) + .unwrap() + .0; let scheduled_actions = TransactionScheduler::default().get_scheduled_actions_by_payer(payer); diff --git a/test-integration/Cargo.lock b/test-integration/Cargo.lock index 32f699198..b4e0312d3 100644 --- a/test-integration/Cargo.lock +++ b/test-integration/Cargo.lock @@ -622,6 +622,26 @@ dependencies = [ "serde", ] +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", +] + [[package]] name = "bindgen" version = "0.69.5" @@ -2243,7 +2263,7 @@ dependencies = [ name = "guinea" version = "0.6.1" dependencies = [ - "bincode", + "bincode 1.3.3", "magicblock-magic-program-api 0.6.1", "serde", "solana-program", @@ -3260,7 +3280,7 @@ name = "magicblock-account-cloner" version = "0.6.1" dependencies = [ "async-trait", - "bincode", + "bincode 1.3.3", "magicblock-accounts-db", "magicblock-chainlink", "magicblock-committor-service", @@ -3331,7 +3351,7 @@ dependencies = [ "agave-geyser-plugin-interface", "arc-swap", "base64 0.21.7", - "bincode", + "bincode 1.3.3", "bs58", "fastwebsockets", "futures", @@ -3433,7 +3453,7 @@ version = "0.6.1" dependencies = [ "arc-swap", "async-trait", - "bincode", + "bincode 1.3.3", "futures-util", "helius-laserstream", "lru", @@ -3498,7 +3518,7 @@ version = "0.6.1" dependencies = [ "async-trait", "base64 0.21.7", - "bincode", + "bincode 1.3.3", "borsh 1.6.0", "dyn-clone", "futures-util", @@ -3596,7 +3616,7 @@ name = "magicblock-delegation-program" version = "1.1.3" source = "git+https://github.com/magicblock-labs/delegation-program.git?rev=1874b4f5f5f55cb9ab54b64de2cc0d41107d1435#1874b4f5f5f55cb9ab54b64de2cc0d41107d1435" dependencies = [ - "bincode", + "bincode 1.3.3", "borsh 1.6.0", "bytemuck", "num_enum", @@ -3618,7 +3638,7 @@ name = "magicblock-ledger" version = "0.6.1" dependencies = [ "arc-swap", - "bincode", + "bincode 1.3.3", "byteorder", "fs_extra", "libc", @@ -3657,7 +3677,7 @@ dependencies = [ name = "magicblock-magic-program-api" version = "0.6.1" dependencies = [ - "bincode", + "bincode 2.0.1", "serde", "solana-program", ] @@ -3667,7 +3687,7 @@ name = "magicblock-magic-program-api" version = "0.6.1" source = "git+https://github.com/magicblock-labs/magicblock-validator.git?rev=c226198c7c#c226198c7cde191f1b6d7b16c79ec19d077c0c7b" dependencies = [ - "bincode", + "bincode 1.3.3", "serde", "solana-program", ] @@ -3690,7 +3710,7 @@ dependencies = [ name = "magicblock-processor" version = "0.6.1" dependencies = [ - "bincode", + "bincode 1.3.3", "magicblock-accounts-db", "magicblock-core", "magicblock-ledger", @@ -3726,7 +3746,7 @@ dependencies = [ name = "magicblock-program" version = "0.6.1" dependencies = [ - "bincode", + "bincode 2.0.1", "lazy_static", "magicblock-core", "magicblock-magic-program-api 0.6.1", @@ -3806,7 +3826,7 @@ dependencies = [ name = "magicblock-task-scheduler" version = "0.6.1" dependencies = [ - "bincode", + "bincode 1.3.3", "chrono", "futures-util", "magicblock-config", @@ -4744,7 +4764,7 @@ dependencies = [ name = "program-flexi-counter" version = "0.0.0" dependencies = [ - "bincode", + "bincode 1.3.3", "borsh 1.6.0", "ephemeral-rollups-sdk", "magicblock-magic-program-api 0.6.1", @@ -6226,7 +6246,7 @@ name = "solana-account" version = "2.2.1" source = "git+https://github.com/magicblock-labs/solana-account.git?rev=2246929#2246929c6614f60d9909fdd117aab3bc454a9775" dependencies = [ - "bincode", + "bincode 1.3.3", "qualifier_attr", "serde", "serde_bytes", @@ -6247,7 +6267,7 @@ checksum = "c472eebf9ec7ee72c8d25e990a2eaf6b0b783619ef84d7954c408d6442ad5e57" dependencies = [ "Inflector", "base64 0.22.1", - "bincode", + "bincode 1.3.3", "bs58", "bv", "lazy_static", @@ -6300,7 +6320,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0c17d606a298a205fae325489fbed88ee6dc4463c111672172327e741c8905d" dependencies = [ - "bincode", + "bincode 1.3.3", "serde", "solana-program-error", "solana-program-memory", @@ -6314,7 +6334,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d65a1a23a53cae19cb92bab2cbdd9e289e5210bb12175ce27642c94adf74b220" dependencies = [ "ahash 0.8.12", - "bincode", + "bincode 1.3.3", "blake3", "bv", "bytemuck", @@ -6362,7 +6382,7 @@ version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1673f67efe870b64a65cb39e6194be5b26527691ce5922909939961a6e6b395" dependencies = [ - "bincode", + "bincode 1.3.3", "bytemuck", "serde", "serde_derive", @@ -6379,7 +6399,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c758a82a60e5fcc93b3ee00615b0e244295aa8b2308475ea2b48f4900862a2e0" dependencies = [ - "bincode", + "bincode 1.3.3", "bytemuck", "log", "num-derive", @@ -6442,7 +6462,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea32797f631ff60b3eb3c793b0fddd104f5ffdf534bf6efcc59fbe30cd23b15" dependencies = [ - "bincode", + "bincode 1.3.3", "crossbeam-channel", "futures", "solana-banks-interface", @@ -6475,7 +6495,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19a3787b8cf9c9fe3dd360800e8b70982b9e5a8af9e11c354b6665dd4a003adc" dependencies = [ - "bincode", + "bincode 1.3.3", "serde", "solana-instruction", ] @@ -6523,7 +6543,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cbc2581d0f39cd7698e46baa06fc5e8928b323a85ed3a4fdbdfe0d7ea9fc152" dependencies = [ - "bincode", + "bincode 1.3.3", "libsecp256k1", "qualifier_attr", "scopeguard", @@ -6638,7 +6658,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e25b7073890561a6b7875a921572fc4a9a2c78b3e60fb8e0a7ee4911961f8bd" dependencies = [ "async-trait", - "bincode", + "bincode 1.3.3", "dashmap", "futures", "futures-util", @@ -6792,7 +6812,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab5647203179631940e0659a635e5d3f514ba60f6457251f8f8fbf3830e56b0" dependencies = [ - "bincode", + "bincode 1.3.3", "chrono", "serde", "serde_derive", @@ -6817,7 +6837,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0392439ea05772166cbce3bebf7816bdcc3088967039c7ce050cea66873b1c50" dependencies = [ "async-trait", - "bincode", + "bincode 1.3.3", "crossbeam-channel", "futures-util", "indexmap 2.12.1", @@ -7007,7 +7027,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f9c7fbf3e58b64a667c5f35e90af580538a95daea7001ff7806c0662d301bdf" dependencies = [ - "bincode", + "bincode 1.3.3", "serde", "serde_derive", "solana-account", @@ -7085,7 +7105,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "968dabd2b92d57131473eddbd475339da530e14f54397386abf303de3a2595a2" dependencies = [ - "bincode", + "bincode 1.3.3", "chrono", "memmap2 0.5.10", "serde", @@ -7164,7 +7184,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce496a475e5062ba5de97215ab39d9c358f9c9df4bb7f3a45a1f1a8bd9065ed" dependencies = [ - "bincode", + "bincode 1.3.3", "borsh 1.6.0", "getrandom 0.2.16", "js-sys", @@ -7365,7 +7385,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "268486ba8a294ed22a4d7c1ec05f540c3dbe71cfa7c6c54b6d4d13668d895678" dependencies = [ - "bincode", + "bincode 1.3.3", "blake3", "lazy_static", "serde", @@ -7422,7 +7442,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0752a7103c1a5bdbda04aa5abc78281232f2eda286be6edf8e44e27db0cca2a1" dependencies = [ "anyhow", - "bincode", + "bincode 1.3.3", "bytes", "crossbeam-channel", "itertools 0.12.1", @@ -7491,7 +7511,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "004f2d2daf407b3ec1a1ca5ec34b3ccdfd6866dd2d3c7d0715004a96e4b6d127" dependencies = [ - "bincode", + "bincode 1.3.3", "bitflags 2.10.0", "cfg_eval", "serde", @@ -7506,7 +7526,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f0962d3818fc942a888f7c2d530896aeaf6f2da2187592a67bbdc8cf8a54192" dependencies = [ "ahash 0.8.12", - "bincode", + "bincode 1.3.3", "bv", "caps", "curve25519-dalek 4.1.3", @@ -7597,7 +7617,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "586469467e93ceb79048f8d8e3a619bf61d05396ee7de95cb40280301a589d05" dependencies = [ - "bincode", + "bincode 1.3.3", "blake3", "borsh 0.10.4", "borsh 1.6.0", @@ -7731,7 +7751,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c3d36fed5548b1a8625eb071df6031a95aa69f884e29bf244821e53c49372bc" dependencies = [ "base64 0.22.1", - "bincode", + "bincode 1.3.3", "enum-iterator", "itertools 0.12.1", "log", @@ -7774,7 +7794,7 @@ dependencies = [ "assert_matches", "async-trait", "base64 0.22.1", - "bincode", + "bincode 1.3.3", "chrono-humanize", "crossbeam-channel", "log", @@ -7976,7 +7996,7 @@ checksum = "7cb874b757d9d3c646f031132b20d43538309060a32d02b4aebb0f8fc2cd159a" dependencies = [ "async-trait", "base64 0.22.1", - "bincode", + "bincode 1.3.3", "bs58", "indicatif", "log", @@ -8064,7 +8084,7 @@ dependencies = [ "aquamarine", "arrayref", "base64 0.22.1", - "bincode", + "bincode 1.3.3", "blake3", "bv", "bytemuck", @@ -8190,7 +8210,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4808e8d7f3c931657e615042d4176b423e66f64dc99e3dc3c735a197e512029b" dependencies = [ - "bincode", + "bincode 1.3.3", "bs58", "getrandom 0.1.16", "js-sys", @@ -8282,7 +8302,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0a1caa972414cc78122c32bdae65ac5fe89df7db598585a5cde19d16a20280a" dependencies = [ - "bincode", + "bincode 1.3.3", "digest 0.10.7", "libsecp256k1", "serde", @@ -8517,7 +8537,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dabc713c25ff999424ec68ac4572f2ff6bfd6317922c7864435ccaf9c76504a8" dependencies = [ - "bincode", + "bincode 1.3.3", "log", "solana-account", "solana-bincode", @@ -8544,7 +8564,7 @@ dependencies = [ name = "solana-storage-proto" version = "0.6.1" dependencies = [ - "bincode", + "bincode 1.3.3", "bs58", "prost 0.11.9", "protobuf-src", @@ -8744,7 +8764,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43c8f684977e4439031b3a27b954ab05a6bdf697d581692aaf8888cf92b73b9e" dependencies = [ - "bincode", + "bincode 1.3.3", "log", "serde", "serde_derive", @@ -8786,7 +8806,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf6b44740d7f0c9f375d045c165bc0aab4a90658f92d6835aeb0649afaeaff9a" dependencies = [ "base64 0.22.1", - "bincode", + "bincode 1.3.3", "bytemuck", "bytemuck_derive", "lazy_static", @@ -8832,7 +8852,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "721a034e94fcfaf8bde1ae4980e7eb58bfeb0c9a243b032b0761fdd19018afbf" dependencies = [ - "bincode", + "bincode 1.3.3", "log", "rayon", "solana-account", @@ -8892,7 +8912,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aaceb9e9349de58740021f826ae72319513eca84ebb6d30326e2604fdad4cefb" dependencies = [ "async-trait", - "bincode", + "bincode 1.3.3", "futures-util", "indexmap 2.12.1", "indicatif", @@ -8925,7 +8945,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "753b3e9afed170e4cfc0ea1e87b5dfdc6d4a50270869414edd24c6ea1f529b29" dependencies = [ - "bincode", + "bincode 1.3.3", "serde", "serde_derive", "solana-bincode", @@ -8953,7 +8973,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5022de04cbba05377f68bf848c8c1322ead733f88a657bf792bb40f3257b8218" dependencies = [ - "bincode", + "bincode 1.3.3", "serde", "serde_derive", "solana-account", @@ -8982,7 +9002,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9256ea8a6cead9e03060fd8fdc24d400a57a719364db48a3e4d1776b09c2365" dependencies = [ "base64 0.22.1", - "bincode", + "bincode 1.3.3", "lazy_static", "log", "rand 0.8.5", @@ -9000,7 +9020,7 @@ checksum = "64f739fb4230787b010aa4a49d3feda8b53aac145a9bc3ac2dd44337c6ecb544" dependencies = [ "Inflector", "base64 0.22.1", - "bincode", + "bincode 1.3.3", "borsh 1.6.0", "bs58", "lazy_static", @@ -9040,7 +9060,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5ac91c8f0465c566164044ad7b3d18d15dfabab1b8b4a4a01cb83c047efdaae" dependencies = [ "base64 0.22.1", - "bincode", + "bincode 1.3.3", "bs58", "serde", "serde_derive", @@ -9146,7 +9166,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4507bb9d071fb81cfcf676f12fba3db4098f764524ef0b5567d671a81d41f3e" dependencies = [ - "bincode", + "bincode 1.3.3", "num-derive", "num-traits", "serde", @@ -9170,7 +9190,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab654bb2622d85b2ca0c36cb89c99fa1286268e0d784efec03a3d42e9c6a55f4" dependencies = [ - "bincode", + "bincode 1.3.3", "log", "num-derive", "num-traits", @@ -9221,7 +9241,7 @@ checksum = "d8318220b73552a2765c6545a4be04fc87fe21b6dd0cb8c2b545a66121bf5b8a" dependencies = [ "aes-gcm-siv", "base64 0.22.1", - "bincode", + "bincode 1.3.3", "bytemuck", "bytemuck_derive", "curve25519-dalek 4.1.3", @@ -9275,7 +9295,7 @@ checksum = "b3cf301f8d8e02ef58fc2ce85868f5c760720e1ce74ee4b3c3dcb64c8da7bcff" dependencies = [ "aes-gcm-siv", "base64 0.22.1", - "bincode", + "bincode 1.3.3", "bytemuck", "bytemuck_derive", "curve25519-dalek 4.1.3", @@ -9985,7 +10005,7 @@ checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" name = "test-chainlink" version = "0.0.0" dependencies = [ - "bincode", + "bincode 1.3.3", "futures", "integration-test-tools", "magicblock-chainlink", @@ -10346,7 +10366,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "911a61637386b789af998ee23f50aa30d5fd7edcec8d6d3dedae5e5815205466" dependencies = [ - "bincode", + "bincode 1.3.3", "bytes", "educe", "futures-core", @@ -10801,6 +10821,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + [[package]] name = "ureq" version = "2.12.1" @@ -10886,6 +10912,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + [[package]] name = "void" version = "1.0.2" @@ -11597,7 +11629,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cec3b1c61e97383dc6f7c66240243c8981c16ba519c8bdf0310560db2a18876d" dependencies = [ "anyhow", - "bincode", + "bincode 1.3.3", "prost 0.13.5", "prost-types 0.13.5", "protobuf-src", diff --git a/test-integration/Makefile b/test-integration/Makefile index 95ff8551e..e1e78e8eb 100644 --- a/test-integration/Makefile +++ b/test-integration/Makefile @@ -187,18 +187,24 @@ setup-task-scheduler-devnet: SETUP_ONLY=devnet \ $(MAKE) test +# --ignore-rust-version: bincode 2.0.1 declares MSRV 1.85 but compiles fine with +# Solana's build-sbf toolchain (rustc 1.84). No .cargo/config.toml equivalent exists. $(FLEXI_COUNTER_SO): $(FLEXI_COUNTER_SRC) cargo build-sbf --manifest-path $(FLEXI_COUNTER_DIR)/Cargo.toml \ - --sbf-out-dir $(DEPLOY_DIR) + --sbf-out-dir $(DEPLOY_DIR) \ + -- --ignore-rust-version $(SCHEDULECOMMIT_SO): $(SCHEDULECOMMIT_SRC) cargo build-sbf --manifest-path $(SCHEDULECOMMIT_DIR)/Cargo.toml \ - --sbf-out-dir $(DEPLOY_DIR) + --sbf-out-dir $(DEPLOY_DIR) \ + -- --ignore-rust-version $(SCHEDULECOMMIT_SECURITY_SO): $(SCHEDULECOMMIT_SECURITY_SRC) cargo build-sbf --manifest-path $(SCHEDULECOMMIT_SECURITY_DIR)/Cargo.toml \ - --sbf-out-dir $(DEPLOY_DIR) + --sbf-out-dir $(DEPLOY_DIR) \ + -- --ignore-rust-version $(COMMITTOR_PROGRAM_SO): $(COMMITTOR_PROGRAM_SRC) cargo build-sbf --manifest-path $(COMMITTOR_PROGRAM_DIR)/Cargo.toml \ - --sbf-out-dir $(ROOT_DEPLOY_DIR)/ + --sbf-out-dir $(ROOT_DEPLOY_DIR)/ \ + -- --ignore-rust-version deploy-flexi-counter: $(FLEXI_COUNTER_SO) solana program deploy \ From 9495c99650722047d3d9a5d4280d98994c32b489 Mon Sep 17 00:00:00 2001 From: taco-paco Date: Tue, 27 Jan 2026 15:17:13 +0700 Subject: [PATCH 36/36] feat: added todo --- test-integration/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/test-integration/Makefile b/test-integration/Makefile index e1e78e8eb..285554720 100644 --- a/test-integration/Makefile +++ b/test-integration/Makefile @@ -189,6 +189,7 @@ setup-task-scheduler-devnet: # --ignore-rust-version: bincode 2.0.1 declares MSRV 1.85 but compiles fine with # Solana's build-sbf toolchain (rustc 1.84). No .cargo/config.toml equivalent exists. +# TODO: remove once solana 2.2 is raised $(FLEXI_COUNTER_SO): $(FLEXI_COUNTER_SRC) cargo build-sbf --manifest-path $(FLEXI_COUNTER_DIR)/Cargo.toml \ --sbf-out-dir $(DEPLOY_DIR) \