diff --git a/Cargo.lock b/Cargo.lock index b3f236410..172b494f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3729,7 +3729,7 @@ dependencies = [ "serde", "solana-keypair", "solana-pubkey", - "strum", + "strum 0.24.1", "thiserror 1.0.69", "toml 0.8.23", "url 2.5.4", @@ -3777,8 +3777,7 @@ dependencies = [ [[package]] name = "magicblock-delegation-program" -version = "1.1.0" -source = "git+https://github.com/magicblock-labs/delegation-program.git?rev=aa1de56d90c#aa1de56d90c8a242377accd59899f272f0131f8c" +version = "1.1.2" dependencies = [ "bincode", "borsh 1.5.7", @@ -3792,6 +3791,7 @@ dependencies = [ "solana-curve25519", "solana-program", "solana-security-txt", + "strum 0.27.2", "thiserror 1.0.69", ] @@ -7478,8 +7478,8 @@ dependencies = [ "spl-token", "spl-token-2022 7.0.0", "static_assertions", - "strum", - "strum_macros", + "strum 0.24.1", + "strum_macros 0.24.3", "tar", "tempfile", "thiserror 2.0.12", @@ -8476,8 +8476,8 @@ dependencies = [ "solana-vote", "solana-vote-program", "static_assertions", - "strum", - "strum_macros", + "strum 0.24.1", + "strum_macros 0.24.3", "symlink", "tar", "tempfile", @@ -10141,7 +10141,16 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" dependencies = [ - "strum_macros", + "strum_macros 0.24.3", +] + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros 0.27.2", ] [[package]] @@ -10157,6 +10166,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "subtle" version = "2.6.1" diff --git a/Cargo.toml b/Cargo.toml index fa58bda4a..0ea744e65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,10 +108,8 @@ magicblock-config = { path = "./magicblock-config" } magicblock-config-helpers = { path = "./magicblock-config-helpers" } magicblock-config-macro = { path = "./magicblock-config-macro" } magicblock-core = { path = "./magicblock-core" } -magicblock-delegation-program = { git = "https://github.com/magicblock-labs/delegation-program.git", rev = "aa1de56d90c", features = [ - "no-entrypoint", -] } magicblock-aperture = { path = "./magicblock-aperture" } +magicblock-delegation-program = { path="../delegation-program", features = ["no-entrypoint"] } magicblock-geyser-plugin = { path = "./magicblock-geyser-plugin" } magicblock-ledger = { path = "./magicblock-ledger" } magicblock-metrics = { path = "./magicblock-metrics" } diff --git a/magicblock-accounts/src/scheduled_commits_processor.rs b/magicblock-accounts/src/scheduled_commits_processor.rs index 83a03c064..c9beb8336 100644 --- a/magicblock-accounts/src/scheduled_commits_processor.rs +++ b/magicblock-accounts/src/scheduled_commits_processor.rs @@ -343,6 +343,7 @@ impl ScheduledCommitsProcessorImpl { included_pubkeys: intent_meta.included_pubkeys, excluded_pubkeys: intent_meta.excluded_pubkeys, requested_undelegation: intent_meta.requested_undelegation, + commit_diff: intent_meta.commit_diff, } } } @@ -412,6 +413,7 @@ struct ScheduledBaseIntentMeta { excluded_pubkeys: Vec, intent_sent_transaction: Transaction, requested_undelegation: bool, + commit_diff: bool, } impl ScheduledBaseIntentMeta { @@ -429,6 +431,7 @@ impl ScheduledBaseIntentMeta { excluded_pubkeys, intent_sent_transaction: intent.action_sent_transaction.clone(), requested_undelegation: intent.is_undelegate(), + commit_diff: intent.is_commit_diff(), } } } diff --git a/magicblock-committor-service/src/tasks/args_task.rs b/magicblock-committor-service/src/tasks/args_task.rs index 301db3b63..8c9e739dc 100644 --- a/magicblock-committor-service/src/tasks/args_task.rs +++ b/magicblock-committor-service/src/tasks/args_task.rs @@ -1,20 +1,33 @@ -use dlp::args::{CallHandlerArgs, CommitStateArgs}; +use dlp::{ + args::{CallHandlerArgs, CommitDiffArgs, CommitStateArgs}, + compute_diff, +}; +use solana_account::ReadableAccount; use solana_pubkey::Pubkey; -use solana_sdk::instruction::{AccountMeta, Instruction}; +use solana_rpc_client::rpc_client::RpcClient; +use solana_sdk::{ + commitment_config::CommitmentConfig, + instruction::{AccountMeta, Instruction}, +}; #[cfg(test)] use crate::tasks::TaskStrategy; -use crate::tasks::{ - buffer_task::{BufferTask, BufferTaskType}, - visitor::Visitor, - BaseActionTask, BaseTask, BaseTaskError, BaseTaskResult, CommitTask, - FinalizeTask, PreparationState, TaskType, UndelegateTask, +use crate::{ + config::ChainConfig, + tasks::{ + buffer_task::{BufferTask, BufferTaskType}, + visitor::Visitor, + BaseActionTask, BaseTask, BaseTaskError, BaseTaskResult, CommitTask, + FinalizeTask, PreparationState, TaskType, UndelegateTask, + }, + ComputeBudgetConfig, }; /// Task that will be executed on Base layer via arguments #[derive(Clone)] pub enum ArgsTaskType { Commit(CommitTask), + CommitDiff(CommitTask), Finalize(FinalizeTask), Undelegate(UndelegateTask), // Special action really BaseAction(BaseActionTask), @@ -58,6 +71,55 @@ impl BaseTask for ArgsTask { args, ) } + ArgsTaskType::CommitDiff(value) => { + let chain_config = + ChainConfig::local(ComputeBudgetConfig::new(1_000_000)); + + let rpc_client = RpcClient::new_with_commitment( + chain_config.rpc_uri.to_string(), + CommitmentConfig { + commitment: chain_config.commitment, + }, + ); + + let account = match rpc_client + .get_account(&value.committed_account.pubkey) + { + Ok(account) => account, + Err(e) => { + log::warn!("Fallback to commit_state and send full-bytes, as rpc failed to fetch the delegated-account from base chain: {}", e); + let args = CommitStateArgs { + nonce: value.commit_id, + lamports: value.committed_account.account.lamports, + data: value.committed_account.account.data.clone(), + allow_undelegation: value.allow_undelegation, + }; + return dlp::instruction_builder::commit_state( + *validator, + value.committed_account.pubkey, + value.committed_account.account.owner, + args, + ); + } + }; + + let args = CommitDiffArgs { + nonce: value.commit_id, + lamports: value.committed_account.account.lamports, + diff: compute_diff( + account.data(), + value.committed_account.account.data(), + ), + allow_undelegation: value.allow_undelegation, + }; + log::warn!("DIFF computed: {:?}", args.diff); + dlp::instruction_builder::commit_diff( + *validator, + value.committed_account.pubkey, + value.committed_account.account.owner, + args, + ) + } ArgsTaskType::Finalize(value) => { dlp::instruction_builder::finalize( *validator, @@ -106,6 +168,9 @@ impl BaseTask for ArgsTask { BufferTaskType::Commit(value), ))) } + ArgsTaskType::CommitDiff(_) => { + panic!("ArgsTaskType::CommitDiff not handled") + } ArgsTaskType::BaseAction(_) | ArgsTaskType::Finalize(_) | ArgsTaskType::Undelegate(_) => Err(self), @@ -132,6 +197,7 @@ impl BaseTask for ArgsTask { fn compute_units(&self) -> u32 { match &self.task_type { ArgsTaskType::Commit(_) => 70_000, + ArgsTaskType::CommitDiff(_) => 65_000, ArgsTaskType::BaseAction(task) => task.action.compute_units, ArgsTaskType::Undelegate(_) => 70_000, ArgsTaskType::Finalize(_) => 70_000, @@ -146,6 +212,9 @@ impl BaseTask for ArgsTask { fn task_type(&self) -> TaskType { match &self.task_type { ArgsTaskType::Commit(_) => TaskType::Commit, + // TODO (snawaz): What should we use here? Commit (in the sense of "category of task"), or add a + // new variant "CommitDiff" to indicate a specific instruction? + ArgsTaskType::CommitDiff(_) => TaskType::Commit, ArgsTaskType::BaseAction(_) => TaskType::Action, ArgsTaskType::Undelegate(_) => TaskType::Undelegate, ArgsTaskType::Finalize(_) => TaskType::Finalize, @@ -158,7 +227,9 @@ impl BaseTask for ArgsTask { } fn reset_commit_id(&mut self, commit_id: u64) { + // TODO (snawaz): handle CommitDiff as well? let ArgsTaskType::Commit(commit_task) = &mut self.task_type else { + log::error!("reset_commit_id"); return; }; diff --git a/magicblock-committor-service/src/tasks/mod.rs b/magicblock-committor-service/src/tasks/mod.rs index a31e52c4a..f2fae6c86 100644 --- a/magicblock-committor-service/src/tasks/mod.rs +++ b/magicblock-committor-service/src/tasks/mod.rs @@ -53,6 +53,8 @@ pub enum TaskStrategy { pub trait BaseTask: Send + Sync + DynClone { /// Gets all pubkeys that involved in Task's instruction fn involved_accounts(&self, validator: &Pubkey) -> Vec { + // TODO (snawaz): rewrite it. + // currently it is slow as it discards heavy computations and memory allocations. self.instruction(validator) .accounts .iter() diff --git a/magicblock-committor-service/src/tasks/task_builder.rs b/magicblock-committor-service/src/tasks/task_builder.rs index 36c7315eb..e50f61851 100644 --- a/magicblock-committor-service/src/tasks/task_builder.rs +++ b/magicblock-committor-service/src/tasks/task_builder.rs @@ -47,25 +47,29 @@ impl TasksBuilder for TaskBuilderImpl { base_intent: &ScheduledBaseIntent, persister: &Option

, ) -> TaskBuilderResult>> { - let (accounts, allow_undelegation) = match &base_intent.base_intent { - 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(); - - return Ok(tasks); - } - MagicBaseIntent::Commit(t) => (t.get_committed_accounts(), false), - MagicBaseIntent::CommitAndUndelegate(t) => { - (t.commit_action.get_committed_accounts(), true) - } - }; + let (accounts, allow_undelegation, commit_diff) = + match &base_intent.base_intent { + 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(); + return Ok(tasks); + } + MagicBaseIntent::Commit(t) => { + (t.get_committed_accounts(), false, t.is_commit_diff()) + } + MagicBaseIntent::CommitAndUndelegate(t) => ( + t.commit_action.get_committed_accounts(), + true, + t.commit_action.is_commit_diff(), + ), + }; let committed_pubkeys = accounts .iter() @@ -89,11 +93,16 @@ impl TasksBuilder for TaskBuilderImpl { .iter() .map(|account| { let commit_id = *commit_ids.get(&account.pubkey).expect("CommitIdFetcher provide commit ids for all listed pubkeys, or errors!"); - let task = ArgsTaskType::Commit(CommitTask { + let task = CommitTask { commit_id, allow_undelegation, committed_account: account.clone(), - }); + }; + let task = if commit_diff { + ArgsTaskType::CommitDiff(task) + } else { + ArgsTaskType::Commit(task) + }; Box::new(ArgsTask::new(task)) as Box }) @@ -134,6 +143,9 @@ impl TasksBuilder for TaskBuilderImpl { CommitType::Standalone(accounts) => { accounts.iter().map(finalize_task).collect() } + CommitType::StandaloneDiff(accounts) => { + accounts.iter().map(finalize_task).collect() + } CommitType::WithBaseActions { committed_accounts, base_actions, diff --git a/magicblock-committor-service/src/tasks/task_visitors/persistor_visitor.rs b/magicblock-committor-service/src/tasks/task_visitors/persistor_visitor.rs index c608f2ef9..1911db187 100644 --- a/magicblock-committor-service/src/tasks/task_visitors/persistor_visitor.rs +++ b/magicblock-committor-service/src/tasks/task_visitors/persistor_visitor.rs @@ -26,9 +26,10 @@ where fn visit_args_task(&mut self, task: &ArgsTask) { match self.context { PersistorContext::PersistStrategy { uses_lookup_tables } => { - let ArgsTaskType::Commit(ref commit_task) = task.task_type - else { - return; + let commit_task = match &task.task_type { + ArgsTaskType::Commit(commit_task) => commit_task, + ArgsTaskType::CommitDiff(commit_task) => commit_task, + _ => return, }; let commit_strategy = if uses_lookup_tables { diff --git a/magicblock-magic-program-api/src/instruction.rs b/magicblock-magic-program-api/src/instruction.rs index 6ff29bee2..03d85d8a9 100644 --- a/magicblock-magic-program-api/src/instruction.rs +++ b/magicblock-magic-program-api/src/instruction.rs @@ -107,6 +107,8 @@ pub enum MagicBlockInstruction { /// # Account references /// - **0.** `[SIGNER]` Validator authority EnableExecutableCheck, + + ScheduleCommitDiffAndUndelegate, } impl MagicBlockInstruction { diff --git a/magicblock-rpc-client/src/lib.rs b/magicblock-rpc-client/src/lib.rs index a2f7cb81a..a40f23057 100644 --- a/magicblock-rpc-client/src/lib.rs +++ b/magicblock-rpc-client/src/lib.rs @@ -427,6 +427,7 @@ impl MagicblockRpcClient { .await?; if let Err(err) = processed_status { + error!("> ERROR: {:?}", err); return Err(MagicBlockRpcClientError::SentTransactionError( err, sig, )); diff --git a/programs/magicblock/src/magic_scheduled_base_intent.rs b/programs/magicblock/src/magic_scheduled_base_intent.rs index 4f07b54fa..28ec8d412 100644 --- a/programs/magicblock/src/magic_scheduled_base_intent.rs +++ b/programs/magicblock/src/magic_scheduled_base_intent.rs @@ -101,6 +101,10 @@ impl ScheduledBaseIntent { self.base_intent.is_undelegate() } + pub fn is_commit_diff(&self) -> bool { + self.base_intent.is_commit_diff() + } + pub fn is_empty(&self) -> bool { self.base_intent.is_empty() } @@ -148,6 +152,16 @@ impl MagicBaseIntent { } } + pub fn is_commit_diff(&self) -> bool { + match &self { + MagicBaseIntent::BaseActions(_) => false, + MagicBaseIntent::Commit(c) => c.is_commit_diff(), + MagicBaseIntent::CommitAndUndelegate(c) => { + c.commit_action.is_commit_diff() + } + } + } + pub fn get_committed_accounts(&self) -> Option<&Vec> { match self { MagicBaseIntent::BaseActions(_) => None, @@ -284,6 +298,7 @@ impl BaseAction { } type CommittedAccountRef<'a> = (Pubkey, &'a RefCell); + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct CommittedAccount { pub pubkey: Pubkey, @@ -303,6 +318,7 @@ impl<'a> From> for CommittedAccount { pub enum CommitType { /// Regular commit without actions Standalone(Vec), // accounts to commit + StandaloneDiff(Vec), // accounts to commit /// Commits accounts and runs actions WithBaseActions { committed_accounts: Vec, @@ -429,9 +445,17 @@ impl CommitType { } } + pub fn is_commit_diff(&self) -> bool { + let Self::StandaloneDiff(_) = self else { + return false; + }; + true + } + pub fn get_committed_accounts(&self) -> &Vec { match self { Self::Standalone(committed_accounts) => committed_accounts, + Self::StandaloneDiff(committed_accounts) => committed_accounts, Self::WithBaseActions { committed_accounts, .. } => committed_accounts, @@ -441,6 +465,7 @@ impl CommitType { pub fn get_committed_accounts_mut(&mut self) -> &mut Vec { match self { Self::Standalone(committed_accounts) => committed_accounts, + Self::StandaloneDiff(committed_accounts) => committed_accounts, Self::WithBaseActions { committed_accounts, .. } => committed_accounts, @@ -452,6 +477,9 @@ impl CommitType { Self::Standalone(committed_accounts) => { committed_accounts.is_empty() } + Self::StandaloneDiff(committed_accounts) => { + committed_accounts.is_empty() + } Self::WithBaseActions { committed_accounts, .. } => committed_accounts.is_empty(), diff --git a/programs/magicblock/src/magicblock_processor.rs b/programs/magicblock/src/magicblock_processor.rs index 60cc13486..724e8606a 100644 --- a/programs/magicblock/src/magicblock_processor.rs +++ b/programs/magicblock/src/magicblock_processor.rs @@ -1,4 +1,5 @@ use magicblock_magic_program_api::instruction::MagicBlockInstruction; +use solana_log_collector::ic_msg; use solana_program_runtime::declare_process_instruction; use solana_sdk::program_utils::limited_deserialize; @@ -34,6 +35,8 @@ declare_process_instruction!( transaction_context.get_current_instruction_context()?; let signers = instruction_context.get_signers(transaction_context)?; + ic_msg!(invoke_context, "MagicBlockInstruction: {:?}", instruction); + match instruction { ModifyAccounts(mut account_mods) => process_mutate_accounts( signers, @@ -46,16 +49,20 @@ declare_process_instruction!( invoke_context, ProcessScheduleCommitOptions { request_undelegation: false, + request_diff: false, }, ), - ScheduleCommitAndUndelegate => process_schedule_commit( - signers, - invoke_context, - ProcessScheduleCommitOptions { - request_undelegation: true, - }, - ), - AcceptScheduleCommits => { + MagicBlockInstruction::ScheduleCommitAndUndelegate => { + process_schedule_commit( + signers, + invoke_context, + ProcessScheduleCommitOptions { + request_undelegation: true, + request_diff: false, + }, + ) + } + MagicBlockInstruction::AcceptScheduleCommits => { process_accept_scheduled_commits(signers, invoke_context) } ScheduledCommitSent((id, _bump)) => process_scheduled_commit_sent( @@ -80,6 +87,16 @@ declare_process_instruction!( EnableExecutableCheck => { process_toggle_executable_check(signers, invoke_context, true) } + MagicBlockInstruction::ScheduleCommitDiffAndUndelegate => { + process_schedule_commit( + signers, + invoke_context, + ProcessScheduleCommitOptions { + request_undelegation: true, + request_diff: true, + }, + ) + } } } ); diff --git a/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs b/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs index aa990b807..c9961391a 100644 --- a/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs +++ b/programs/magicblock/src/schedule_transactions/process_schedule_commit.rs @@ -28,6 +28,7 @@ use crate::{ #[derive(Default)] pub(crate) struct ProcessScheduleCommitOptions { pub request_undelegation: bool, + pub request_diff: bool, } pub(crate) fn process_schedule_commit( @@ -220,13 +221,20 @@ pub(crate) fn process_schedule_commit( InstructionUtils::scheduled_commit_sent(intent_id, blockhash); let commit_sent_sig = action_sent_transaction.signatures[0]; + let commit_action = if opts.request_diff { + ic_msg!(invoke_context, "CommitType::StandaloneDiff"); + CommitType::StandaloneDiff(committed_accounts) + } else { + CommitType::Standalone(committed_accounts) + }; + let base_intent = if opts.request_undelegation { MagicBaseIntent::CommitAndUndelegate(CommitAndUndelegate { - commit_action: CommitType::Standalone(committed_accounts), + commit_action, undelegate_action: UndelegateType::Standalone, }) } else { - MagicBaseIntent::Commit(CommitType::Standalone(committed_accounts)) + MagicBaseIntent::Commit(commit_action) }; let scheduled_base_intent = ScheduledBaseIntent { id: intent_id, diff --git a/programs/magicblock/src/schedule_transactions/process_scheduled_commit_sent.rs b/programs/magicblock/src/schedule_transactions/process_scheduled_commit_sent.rs index 7bb293d8a..40fa31367 100644 --- a/programs/magicblock/src/schedule_transactions/process_scheduled_commit_sent.rs +++ b/programs/magicblock/src/schedule_transactions/process_scheduled_commit_sent.rs @@ -26,6 +26,7 @@ pub struct SentCommit { pub included_pubkeys: Vec, pub excluded_pubkeys: Vec, pub requested_undelegation: bool, + pub commit_diff: bool, } /// This is a printable version of the SentCommit struct. @@ -40,6 +41,7 @@ struct SentCommitPrintable { included_pubkeys: String, excluded_pubkeys: String, requested_undelegation: bool, + commit_diff: bool, } impl From for SentCommitPrintable { @@ -67,6 +69,7 @@ impl From for SentCommitPrintable { .collect::>() .join(", "), requested_undelegation: commit.requested_undelegation, + commit_diff: commit.commit_diff, } } } @@ -209,6 +212,9 @@ pub fn process_scheduled_commit_sent( if commit.requested_undelegation { ic_msg!(invoke_context, "ScheduledCommitSent requested undelegation",); } + if commit.commit_diff { + ic_msg!(invoke_context, "ScheduledCommitSent requested commit_diff",); + } Ok(()) } @@ -245,6 +251,7 @@ mod tests { included_pubkeys: vec![acc], excluded_pubkeys: Default::default(), requested_undelegation: false, + commit_diff: false, } } diff --git a/test-integration/Cargo.lock b/test-integration/Cargo.lock index 4717c274b..d4640cfb0 100644 --- a/test-integration/Cargo.lock +++ b/test-integration/Cargo.lock @@ -1720,21 +1720,19 @@ dependencies = [ [[package]] name = "ephemeral-rollups-sdk" version = "0.3.4" -source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=2d0f16b#2d0f16bf18a8618dcac07a8dc271dd3a30096c7c" dependencies = [ "borsh 1.5.7", "ephemeral-rollups-sdk-attribute-commit", "ephemeral-rollups-sdk-attribute-delegate", "ephemeral-rollups-sdk-attribute-ephemeral", "magicblock-delegation-program", - "magicblock-magic-program-api 0.2.1", + "magicblock-magic-program-api", "solana-program", ] [[package]] name = "ephemeral-rollups-sdk-attribute-commit" version = "0.3.4" -source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=2d0f16b#2d0f16bf18a8618dcac07a8dc271dd3a30096c7c" dependencies = [ "quote", "syn 1.0.109", @@ -1743,7 +1741,6 @@ dependencies = [ [[package]] name = "ephemeral-rollups-sdk-attribute-delegate" version = "0.3.4" -source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=2d0f16b#2d0f16bf18a8618dcac07a8dc271dd3a30096c7c" dependencies = [ "proc-macro2", "quote", @@ -1753,7 +1750,6 @@ dependencies = [ [[package]] name = "ephemeral-rollups-sdk-attribute-ephemeral" version = "0.3.4" -source = "git+https://github.com/magicblock-labs/ephemeral-rollups-sdk.git?rev=2d0f16b#2d0f16bf18a8618dcac07a8dc271dd3a30096c7c" dependencies = [ "proc-macro2", "quote", @@ -1844,9 +1840,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "faststr" -version = "0.2.31" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6503af7917fea18ffef8f7e8553fb8dff89e2e6837e94e09dd7fb069c82d62c" +checksum = "baec6a0289d7f1fe5665586ef7340af82e3037207bef60f5785e57569776f0c8" dependencies = [ "bytes", "rkyv", @@ -2653,9 +2649,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ "bytes", "futures-core", @@ -3462,7 +3458,7 @@ dependencies = [ "magicblock-config", "magicblock-core", "magicblock-ledger", - "magicblock-magic-program-api 0.2.3", + "magicblock-magic-program-api", "magicblock-program", "magicblock-rpc-client", "solana-sdk", @@ -3485,7 +3481,7 @@ dependencies = [ "magicblock-core", "magicblock-delegation-program", "magicblock-ledger", - "magicblock-magic-program-api 0.2.3", + "magicblock-magic-program-api", "magicblock-metrics", "magicblock-processor", "magicblock-program", @@ -3585,7 +3581,7 @@ dependencies = [ "magicblock-core", "magicblock-delegation-program", "magicblock-ledger", - "magicblock-magic-program-api 0.2.3", + "magicblock-magic-program-api", "magicblock-metrics", "magicblock-processor", "magicblock-program", @@ -3618,7 +3614,7 @@ dependencies = [ "lru 0.16.0", "magicblock-core", "magicblock-delegation-program", - "magicblock-magic-program-api 0.2.3", + "magicblock-magic-program-api", "serde_json", "solana-account", "solana-account-decoder", @@ -3698,7 +3694,7 @@ dependencies = [ "serde", "solana-keypair", "solana-pubkey", - "strum", + "strum 0.24.1", "thiserror 1.0.69", "toml 0.8.23", "url 2.5.4", @@ -3726,7 +3722,7 @@ version = "0.2.3" dependencies = [ "bincode", "flume", - "magicblock-magic-program-api 0.2.3", + "magicblock-magic-program-api", "serde", "solana-account", "solana-account-decoder", @@ -3743,8 +3739,7 @@ dependencies = [ [[package]] name = "magicblock-delegation-program" -version = "1.1.0" -source = "git+https://github.com/magicblock-labs/delegation-program.git?rev=aa1de56d90c#aa1de56d90c8a242377accd59899f272f0131f8c" +version = "1.1.2" dependencies = [ "bincode", "borsh 1.5.7", @@ -3758,6 +3753,7 @@ dependencies = [ "solana-curve25519", "solana-program", "solana-security-txt", + "strum 0.27.2", "thiserror 1.0.69", ] @@ -3791,17 +3787,6 @@ dependencies = [ "tokio-util 0.7.15", ] -[[package]] -name = "magicblock-magic-program-api" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349b26eb6d819328dad699a6c9a26234548d366d9a30e7edf0d296180188ee27" -dependencies = [ - "bincode", - "serde", - "solana-program", -] - [[package]] name = "magicblock-magic-program-api" version = "0.2.3" @@ -3865,7 +3850,7 @@ dependencies = [ "bincode", "lazy_static", "magicblock-core", - "magicblock-magic-program-api 0.2.3", + "magicblock-magic-program-api", "magicblock-metrics", "num-derive", "num-traits", @@ -4118,18 +4103,18 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "munge" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7feb0b48aa0a25f9fe0899482c6e1379ee7a11b24a53073eacdecb9adb6dc60" +checksum = "5e17401f259eba956ca16491461b6e8f72913a0a114e39736ce404410f915a0c" dependencies = [ "munge_macro", ] [[package]] name = "munge_macro" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2e3795a5d2da581a8b252fec6022eee01aea10161a4d1bf237d4cbe47f7e988" +checksum = "4568f25ccbd45ab5d5603dc34318c1ec56b117531781260002151b8530a9f931" dependencies = [ "proc-macro2", "quote", @@ -4841,7 +4826,7 @@ dependencies = [ "bincode", "borsh 1.5.7", "ephemeral-rollups-sdk", - "magicblock-magic-program-api 0.2.3", + "magicblock-magic-program-api", "serde", "solana-program", ] @@ -4984,18 +4969,18 @@ dependencies = [ [[package]] name = "ptr_meta" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9e76f66d3f9606f44e45598d155cb13ecf09f4a28199e48daf8c8fc937ea90" +checksum = "0b9a0cf95a1196af61d4f1cbdab967179516d9a4a4312af1f31948f8f6224a79" dependencies = [ "ptr_meta_derive", ] [[package]] name = "ptr_meta_derive" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca414edb151b4c8d125c12566ab0d74dc9cdba36fb80eb7b848c15f495fd32d1" +checksum = "7347867d0a7e1208d93b46767be83e2b8f978c3dad35f775ac8d8847551d6fe1" dependencies = [ "proc-macro2", "quote", @@ -5117,9 +5102,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rancor" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf5f7161924b9d1cea0e4cabc97c372cea92b5f927fc13c6bca67157a0ad947" +checksum = "a063ea72381527c2a0561da9c80000ef822bdd7c3241b1cc1b12100e3df081ee" dependencies = [ "ptr_meta", ] @@ -5328,18 +5313,18 @@ dependencies = [ [[package]] name = "ref-cast" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", @@ -5389,9 +5374,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rend" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35e8a6bf28cd121053a66aa2e6a2e3eaffad4a60012179f0e864aa5ffeff215" +checksum = "cadadef317c2f20755a64d7fdc48f9e7178ee6b0e1f7fce33fa60f1d68a276e6" [[package]] name = "reqwest" @@ -5471,9 +5456,9 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f5c3e5da784cd8c69d32cdc84673f3204536ca56e1fa01be31a74b92c932ac" +checksum = "35a640b26f007713818e9a9b65d34da1cf58538207b052916a83d80e43f3ffa4" dependencies = [ "bytes", "hashbrown 0.15.4", @@ -5489,9 +5474,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4270433626cffc9c4c1d3707dd681f2a2718d3d7b09ad754bec137acecda8d22" +checksum = "bd83f5f173ff41e00337d97f6572e416d022ef8a19f371817259ae960324c482" dependencies = [ "proc-macro2", "quote", @@ -5816,12 +5801,14 @@ dependencies = [ name = "schedulecommit-test-scenarios" version = "0.0.0" dependencies = [ + "borsh 1.5.7", "ephemeral-rollups-sdk", "integration-test-tools", "log", "magicblock-core", - "magicblock-magic-program-api 0.2.3", + "magicblock-magic-program-api", "program-schedulecommit", + "rand 0.8.5", "schedulecommit-client", "solana-program", "solana-rpc-client", @@ -5836,7 +5823,7 @@ version = "0.0.0" dependencies = [ "integration-test-tools", "magicblock-core", - "magicblock-magic-program-api 0.2.3", + "magicblock-magic-program-api", "program-schedulecommit", "program-schedulecommit-security", "schedulecommit-client", @@ -7505,8 +7492,8 @@ dependencies = [ "spl-token", "spl-token-2022 7.0.0", "static_assertions", - "strum", - "strum_macros", + "strum 0.24.1", + "strum_macros 0.24.3", "tar", "tempfile", "thiserror 2.0.12", @@ -8517,8 +8504,8 @@ dependencies = [ "solana-vote", "solana-vote-program", "static_assertions", - "strum", - "strum_macros", + "strum 0.24.1", + "strum_macros 0.24.3", "symlink", "tar", "tempfile", @@ -9757,9 +9744,9 @@ dependencies = [ [[package]] name = "sonic-rs" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd1adc42def3cb101f3ebef3cd2d642f9a21072bbcd4ec9423343ccaa6afa596" +checksum = "22540d56ba14521e4878ad436d498518c59698c39a89d5905c694932f0bf7134" dependencies = [ "ahash 0.8.12", "bumpalo", @@ -10212,7 +10199,16 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" dependencies = [ - "strum_macros", + "strum_macros 0.24.3", +] + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros 0.27.2", ] [[package]] @@ -10228,6 +10224,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "subtle" version = "2.6.1" @@ -11236,9 +11244,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.18.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/test-integration/Cargo.toml b/test-integration/Cargo.toml index 9720dd913..5b5afff4b 100644 --- a/test-integration/Cargo.toml +++ b/test-integration/Cargo.toml @@ -36,8 +36,8 @@ 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 = "2d0f16b" } futures = "0.3.31" +ephemeral-rollups-sdk = { path = "../../ephemeral-rollups-sdk/rust/sdk"} integration-test-tools = { path = "test-tools" } isocountry = "0.3.2" lazy_static = "1.4.0" @@ -57,9 +57,7 @@ magicblock-config = { path = "../magicblock-config" } magicblock-core = { path = "../magicblock-core" } magic-domain-program = { git = "https://github.com/magicblock-labs/magic-domain-program.git", rev = "ea04d46", default-features = false } magicblock_magic_program_api = { package = "magicblock-magic-program-api", path = "../magicblock-magic-program-api" } -magicblock-delegation-program = { git = "https://github.com/magicblock-labs/delegation-program.git", rev = "aa1de56d90c", features = [ - "no-entrypoint", -] } +magicblock-delegation-program = { path="../../delegation-program", features = ["no-entrypoint"] } magicblock-program = { path = "../programs/magicblock" } magicblock-rpc-client = { path = "../magicblock-rpc-client" } magicblock-table-mania = { path = "../magicblock-table-mania" } diff --git a/test-integration/programs/schedulecommit-security/src/lib.rs b/test-integration/programs/schedulecommit-security/src/lib.rs index 8236b40a7..8bac187a8 100644 --- a/test-integration/programs/schedulecommit-security/src/lib.rs +++ b/test-integration/programs/schedulecommit-security/src/lib.rs @@ -1,5 +1,5 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use ephemeral_rollups_sdk::ephem::create_schedule_commit_ix; +use ephemeral_rollups_sdk::ephem::{create_schedule_commit_ix, CommitPolicy}; use program_schedulecommit::{ api::schedule_commit_cpi_instruction, process_schedulecommit_cpi, ProcessSchedulecommitCpiArgs, @@ -146,6 +146,7 @@ fn process_sibling_schedule_cpis( magic_context, magic_program, false, + CommitPolicy::UseFullBytes, ); invoke( &direct_ix, diff --git a/test-integration/programs/schedulecommit/src/api.rs b/test-integration/programs/schedulecommit/src/api.rs index 5ccf02184..029f8abd6 100644 --- a/test-integration/programs/schedulecommit/src/api.rs +++ b/test-integration/programs/schedulecommit/src/api.rs @@ -9,7 +9,8 @@ use solana_program::{ }; use crate::{ - DelegateCpiArgs, ScheduleCommitCpiArgs, ScheduleCommitInstruction, + BookUpdate, DelegateCpiArgs, DelegateOrderBookArgs, ScheduleCommitCpiArgs, + ScheduleCommitInstruction, }; pub fn init_account_instruction( @@ -32,6 +33,47 @@ pub fn init_account_instruction( ) } +pub fn init_order_book_instruction( + payer: Pubkey, + book_manager: Pubkey, + order_book: Pubkey, +) -> Instruction { + let program_id = crate::id(); + let account_metas = vec![ + AccountMeta::new(payer, true), + AccountMeta::new_readonly(book_manager, true), + AccountMeta::new(order_book, false), + AccountMeta::new_readonly(system_program::id(), false), + ]; + + Instruction::new_with_borsh( + program_id, + &ScheduleCommitInstruction::InitOrderBook, + account_metas, + ) +} + +pub fn grow_order_book_instruction( + payer: Pubkey, + book_manager: Pubkey, + order_book: Pubkey, + additional_space: u64, +) -> Instruction { + let program_id = crate::id(); + let account_metas = vec![ + AccountMeta::new(payer, true), + AccountMeta::new_readonly(book_manager, false), + AccountMeta::new(order_book, false), + AccountMeta::new_readonly(system_program::id(), false), + ]; + + Instruction::new_with_borsh( + program_id, + &ScheduleCommitInstruction::GrowOrderBook(additional_space), + account_metas, + ) +} + pub fn init_payer_escrow(payer: Pubkey) -> [Instruction; 2] { let top_up_ix = dlp::instruction_builder::top_up_ephemeral_balance( payer, @@ -57,16 +99,14 @@ pub fn init_payer_escrow(payer: Pubkey) -> [Instruction; 2] { pub fn delegate_account_cpi_instruction( payer: Pubkey, - player: Pubkey, + player_or_book_manager: Pubkey, + user_seed: &[u8], ) -> Instruction { let program_id = crate::id(); - let (pda, _) = pda_and_bump(&player); - - let args = DelegateCpiArgs { - valid_until: i64::MAX, - commit_frequency_ms: 1_000_000_000, - player, - }; + let (pda, _) = Pubkey::find_program_address( + &[user_seed, player_or_book_manager.as_ref()], + &crate::ID, + ); let delegate_accounts = DelegateAccounts::new(pda, program_id); let delegate_metas = DelegateAccountMetas::from(delegate_accounts); @@ -83,7 +123,20 @@ pub fn delegate_account_cpi_instruction( Instruction::new_with_borsh( program_id, - &ScheduleCommitInstruction::DelegateCpi(args), + &if user_seed == b"magic_schedule_commit" { + ScheduleCommitInstruction::DelegateCpi(DelegateCpiArgs { + valid_until: i64::MAX, + commit_frequency_ms: 1_000_000_000, + player: player_or_book_manager, + }) + } else { + ScheduleCommitInstruction::DelegateOrderBook( + DelegateOrderBookArgs { + commit_frequency_ms: 1_000_000_000, + book_manager: player_or_book_manager, + }, + ) + }, account_metas, ) } @@ -119,6 +172,45 @@ pub fn schedule_commit_cpi_instruction( ) } +pub fn update_order_book_instruction( + payer: Pubkey, + order_book: Pubkey, + update: BookUpdate, +) -> Instruction { + let program_id = crate::id(); + let account_metas = vec![ + AccountMeta::new(payer, true), + AccountMeta::new(order_book, false), + ]; + + Instruction::new_with_borsh( + program_id, + &ScheduleCommitInstruction::UpdateOrderBook(update), + account_metas, + ) +} + +pub fn schedule_commit_diff_instruction_for_order_book( + payer: Pubkey, + order_book: Pubkey, + magic_program_id: Pubkey, + magic_context_id: Pubkey, +) -> Instruction { + let program_id = crate::id(); + let account_metas = vec![ + AccountMeta::new(payer, true), + AccountMeta::new(order_book, false), + AccountMeta::new(magic_context_id, false), + AccountMeta::new_readonly(magic_program_id, false), + ]; + + Instruction::new_with_borsh( + program_id, + &ScheduleCommitInstruction::ScheduleCommitForOrderBook, + account_metas, + ) +} + pub fn schedule_commit_with_payer_cpi_instruction( payer: Pubkey, magic_program_id: Pubkey, diff --git a/test-integration/programs/schedulecommit/src/lib.rs b/test-integration/programs/schedulecommit/src/lib.rs index d3acef55d..aa12385e2 100644 --- a/test-integration/programs/schedulecommit/src/lib.rs +++ b/test-integration/programs/schedulecommit/src/lib.rs @@ -4,15 +4,22 @@ use ephemeral_rollups_sdk::{ cpi::{ delegate_account, undelegate_account, DelegateAccounts, DelegateConfig, }, - ephem::{commit_accounts, commit_and_undelegate_accounts}, + ephem::{ + commit_accounts, commit_and_undelegate_accounts, + commit_diff_and_undelegate_accounts, + }, }; use solana_program::{ account_info::{next_account_info, AccountInfo}, declare_id, entrypoint::{self, ProgramResult}, msg, + program::invoke, program_error::ProgramError, pubkey::Pubkey, + rent::Rent, + system_instruction, + sysvar::Sysvar, }; use crate::{ @@ -24,8 +31,13 @@ use crate::{ }; pub mod api; pub mod magicblock_program; +mod order_book; mod utils; +use order_book::*; + +pub use order_book::{BookUpdate, OrderBookOwned, OrderLevel}; + declare_id!("9hgprgZiRWmy8KkfvUuaVkDGrqo9GzeXMohwq6BazgUY"); #[cfg(not(feature = "no-entrypoint"))] @@ -38,6 +50,12 @@ pub struct DelegateCpiArgs { player: Pubkey, } +#[derive(BorshSerialize, BorshDeserialize, Debug, Clone)] +pub struct DelegateOrderBookArgs { + commit_frequency_ms: u32, + book_manager: Pubkey, +} + #[derive(BorshSerialize, BorshDeserialize, Debug, Clone)] pub struct ScheduleCommitCpiArgs { /// Pubkeys of players from which PDAs were derived @@ -104,6 +122,19 @@ pub enum ScheduleCommitInstruction { // // It is not part of this enum as it has a custom discriminator // Undelegate, + /// Initialize an OrderBook + InitOrderBook, + + GrowOrderBook(u64), // additional_space + + /// Delegate order book to ER nodes + DelegateOrderBook(DelegateOrderBookArgs), + + /// Update order book + UpdateOrderBook(BookUpdate), + + /// ScheduleCommitDiffCpi + ScheduleCommitForOrderBook, } pub fn process_instruction<'a>( @@ -127,6 +158,7 @@ pub fn process_instruction<'a>( msg!("ERROR: failed to parse instruction data {:?}", err); ProgramError::InvalidArgument })?; + use ScheduleCommitInstruction::*; match ix { Init => process_init(program_id, accounts), @@ -146,6 +178,15 @@ pub fn process_instruction<'a>( ) } IncreaseCount => process_increase_count(accounts), + InitOrderBook => process_init_order_book(accounts), + GrowOrderBook(additional_space) => { + process_grow_order_book(accounts, additional_space) + } + DelegateOrderBook(args) => process_delegate_order_book(accounts, args), + UpdateOrderBook(args) => process_update_order_book(accounts, args), + ScheduleCommitForOrderBook => { + process_schedulecommit_for_orderbook(accounts) + } } } @@ -159,7 +200,7 @@ pub struct MainAccount { } impl MainAccount { - pub const SIZE: usize = std::mem::size_of::(); + pub const SIZE: u64 = std::mem::size_of::() as u64; pub fn try_decode(data: &[u8]) -> std::io::Result { Self::try_from_slice(data) @@ -233,6 +274,185 @@ fn process_init<'a>( Ok(()) } +// ----------------- +// InitOrderBook +// ----------------- +fn process_init_order_book<'a>( + accounts: &'a [AccountInfo<'a>], +) -> entrypoint::ProgramResult { + msg!("Init OrderBook account"); + let [payer, book_manager, order_book, _system_program] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + assert_is_signer(payer, "payer")?; + + let (pda, bump) = Pubkey::find_program_address( + &[b"order_book", book_manager.key.as_ref()], + &crate::ID, + ); + + assert_keys_equal(order_book.key, &pda, || { + format!( + "PDA for the account ('{}') and for book_manager ('{}') is incorrect", + order_book.key, book_manager.key + ) + })?; + + allocate_account_and_assign_owner(AllocateAndAssignAccountArgs { + payer_info: payer, + account_info: order_book, + owner: &crate::ID, + signer_seeds: &[b"order_book", book_manager.key.as_ref(), &[bump]], + size: 10 * 1024, + })?; + + Ok(()) +} + +fn process_grow_order_book<'a>( + accounts: &'a [AccountInfo<'a>], + additional_space: u64, +) -> entrypoint::ProgramResult { + msg!("Grow OrderBook account"); + let [payer, book_manager, order_book, system_program] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + assert_is_signer(payer, "payer")?; + + let (pda, _bump) = Pubkey::find_program_address( + &[b"order_book", book_manager.key.as_ref()], + &crate::ID, + ); + + assert_keys_equal(order_book.key, &pda, || { + format!( + "PDA for the account ('{}') and for book_manager ('{}') is incorrect", + order_book.key, payer.key + ) + })?; + + let new_size = order_book.data_len() + additional_space as usize; + + // Ideally, we should transfer some lamports from payer to order_book + // so that realloc could use it + + let rent = Rent::get()?; + let required = rent.minimum_balance(new_size); + let current = order_book.lamports(); + if current < required { + let diff = required - current; + invoke( + &system_instruction::transfer(payer.key, order_book.key, diff), + &[payer.clone(), order_book.clone(), system_program.clone()], + )?; + } + + order_book.realloc(new_size, true)?; + + Ok(()) +} + +// ----------------- +// Delegate OrderBook +// ----------------- +pub fn process_delegate_order_book( + accounts: &[AccountInfo], + args: DelegateOrderBookArgs, +) -> Result<(), ProgramError> { + msg!("Processing delegate_order_book instruction"); + + let [payer, order_book, owner_program, buffer, delegation_record, delegation_metadata, delegation_program, system_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + let seeds_no_bump = [b"order_book", args.book_manager.as_ref()]; + + delegate_account( + DelegateAccounts { + payer, + pda: order_book, + buffer, + delegation_record, + delegation_metadata, + owner_program, + delegation_program, + system_program, + }, + &seeds_no_bump, + DelegateConfig { + commit_frequency_ms: args.commit_frequency_ms, + ..DelegateConfig::default() + }, + )?; + + Ok(()) +} + +// ----------------- +// UpdateOrderBook +// ----------------- +fn process_update_order_book<'a>( + accounts: &'a [AccountInfo<'a>], + updates: BookUpdate, +) -> entrypoint::ProgramResult { + msg!("Init account"); + let account_info_iter = &mut accounts.iter(); + let payer_info = next_account_info(account_info_iter)?; + let order_book_account = next_account_info(account_info_iter)?; + + assert_is_signer(payer_info, "payer")?; + + let mut book_raw = order_book_account.try_borrow_mut_data()?; + + OrderBook::new(&mut book_raw).update_from(updates); + + Ok(()) +} + +// ----------------- +// Schedule Commit +// ----------------- +pub fn process_schedulecommit_for_orderbook( + accounts: &[AccountInfo], +) -> Result<(), ProgramError> { + msg!("Processing schedulecommit (for orderbook) instruction"); + + let [payer, order_book_account, magic_context, magic_program] = accounts + else { + return Err(ProgramError::MissingRequiredSignature); + }; + + // IMPORTANT: it seems the scoping matters to avoid following error: + // - TransactionError(InstructionError(0, AccountBorrowFailed)) + // - Program 9hgprgZiRWmy8KkfvUuaVkDGrqo9GzeXMohwq6BazgUY failed: instruction tries to borrow reference for an account which is already borrowed + { + // let mut book_raw = order_book_account.try_borrow_mut_data()?; + // let mut order_book = OrderBook::new(&mut book_raw); + + // order_book.add_bids(&[OrderLevel { + // price: 90000, + // size: 10, + // }]); + // order_book.add_asks(&[OrderLevel { + // price: 125000, + // size: 16, + // }]); + } + + commit_diff_and_undelegate_accounts( + payer, + vec![order_book_account], + magic_context, + magic_program, + )?; + + Ok(()) +} + // ----------------- // Delegate // ----------------- @@ -339,7 +559,7 @@ pub fn process_schedulecommit_cpi( ); if args.undelegate { - commit_and_undelegate_accounts( + commit_diff_and_undelegate_accounts( payer, committees, magic_context, diff --git a/test-integration/programs/schedulecommit/src/order_book.rs b/test-integration/programs/schedulecommit/src/order_book.rs new file mode 100644 index 000000000..53dda42a4 --- /dev/null +++ b/test-integration/programs/schedulecommit/src/order_book.rs @@ -0,0 +1,185 @@ +use std::slice; + +use borsh::{BorshDeserialize, BorshSerialize}; + +#[repr(C)] +#[derive( + BorshSerialize, BorshDeserialize, Debug, Clone, Copy, Default, PartialEq, Eq, +)] +pub struct OrderLevel { + pub price: u64, // ideally both fields could be some decimal value + pub size: u64, +} + +#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Default)] +pub struct BookUpdate { + pub bids: Vec, + pub asks: Vec, +} + +#[repr(C, align(4))] +pub struct OrderBookHeader { + pub bids_len: u32, + pub asks_len: u32, +} + +const ORDER_LEVEL_SIZE: usize = std::mem::size_of::(); +const HEADER_SIZE: usize = std::mem::size_of::(); + +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct OrderBookOwned { + pub bids: Vec, + pub asks: Vec, +} + +impl From<&OrderBook<'_>> for OrderBookOwned { + fn from(book_ref: &OrderBook) -> Self { + let mut book = Self::default(); + book.bids.extend_from_slice(book_ref.bids()); + book.asks.extend(book_ref.asks_reversed().iter().rev()); + book + } +} + +impl borsh::de::BorshDeserialize for OrderBookOwned { + fn deserialize(buf: &mut &[u8]) -> Result { + let (book_bytes, rest) = buf.split_at(buf.len()); + *buf = rest; // rest is actually empty + + Ok(Self::from(&OrderBook::new(unsafe { + slice::from_raw_parts_mut( + book_bytes.as_ptr() as *mut u8, + book_bytes.len(), + ) + }))) + } + fn deserialize_reader( + _reader: &mut R, + ) -> ::core::result::Result { + unimplemented!("deserialize_reader() not implemented. Please use buffer version as it needs to know size of the buffer") + } +} +pub struct OrderBook<'a> { + header: &'a mut OrderBookHeader, + capacity: usize, + levels: *mut OrderLevel, +} + +impl<'a> OrderBook<'a> { + // + // ========= Zero-Copy Order Book ========== + // + // ----------------------------------------- + // | account data | + // ----------------------------------------- + // | header | levels | + // ----------------------------------------- + // | asks grows -> <- bids grows | + // ----------------------------------------- + // + // Note: + // + // - asks grows towards right + // - bids grows towards left + // + pub fn new(data: &'a mut [u8]) -> Self { + let (header_bytes, levels_bytes) = data.split_at_mut(HEADER_SIZE); + Self { + header: unsafe { + &mut *(header_bytes.as_ptr() as *mut OrderBookHeader) + }, + capacity: levels_bytes.len() / ORDER_LEVEL_SIZE, + levels: levels_bytes.as_mut_ptr() as *mut OrderLevel, + } + } + + pub fn update_from(&mut self, updates: BookUpdate) { + self.add_bids(&updates.bids); + self.add_asks(&updates.asks); + } + + pub fn add_bids( + &mut self, + bids: &[OrderLevel], + ) -> Option<&'a [OrderLevel]> { + if self.remaining_capacity() < bids.len() { + return None; + } + let new_bids_len = self.bids_len() + bids.len(); + let bids_space = + unsafe { self.bids_with_uninitialized_slots(new_bids_len) }; + + bids_space[self.bids_len()..].copy_from_slice(bids); + self.header.bids_len = new_bids_len as u32; + + Some(bids_space) + } + + pub fn add_asks( + &mut self, + asks: &[OrderLevel], + ) -> Option<&'a [OrderLevel]> { + if self.remaining_capacity() < asks.len() { + return None; + } + let new_asks_len = self.asks_len() + asks.len(); + let asks_space = + unsafe { self.asks_with_uninitialized_slots(new_asks_len) }; + + // copy in the reverse order + for (dst, src) in + asks_space[..asks.len()].iter_mut().zip(asks.iter().rev()) + { + *dst = *src; + } + self.header.asks_len = new_asks_len as u32; + + Some(asks_space) + } + + pub fn bids(&self) -> &'a [OrderLevel] { + unsafe { slice::from_raw_parts(self.levels, self.bids_len()) } + } + + /// Note that the returned slice is in reverse order, means the first entry is the latest + /// entry and the last entry is the oldest entry. + pub fn asks_reversed(&self) -> &'a [OrderLevel] { + unsafe { + slice::from_raw_parts( + self.levels.add(self.capacity - self.asks_len()), + self.asks_len(), + ) + } + } + + pub fn bids_len(&self) -> usize { + self.header.bids_len as usize + } + + pub fn asks_len(&self) -> usize { + self.header.asks_len as usize + } + + unsafe fn bids_with_uninitialized_slots( + &mut self, + bids_len: usize, + ) -> &'a mut [OrderLevel] { + slice::from_raw_parts_mut(self.levels, bids_len) + } + + unsafe fn asks_with_uninitialized_slots( + &mut self, + asks_len: usize, + ) -> &'a mut [OrderLevel] { + slice::from_raw_parts_mut( + self.levels.add(self.capacity - asks_len), + asks_len as usize, + ) + } + + fn remaining_capacity(&self) -> usize { + self.capacity + .checked_sub((self.header.bids_len + self.header.asks_len) as usize) + .expect("remaining_capacity must exist") + } +} diff --git a/test-integration/programs/schedulecommit/src/utils/mod.rs b/test-integration/programs/schedulecommit/src/utils/mod.rs index 7de5b1250..bb1bd9086 100644 --- a/test-integration/programs/schedulecommit/src/utils/mod.rs +++ b/test-integration/programs/schedulecommit/src/utils/mod.rs @@ -50,7 +50,7 @@ pub struct AllocateAndAssignAccountArgs<'a, 'b> { pub payer_info: &'a AccountInfo<'a>, pub account_info: &'a AccountInfo<'a>, pub owner: &'a Pubkey, - pub size: usize, + pub size: u64, pub signer_seeds: &'b [&'b [u8]], } @@ -68,10 +68,16 @@ pub fn allocate_account_and_assign_owner( } = args; let required_lamports = rent - .minimum_balance(size) + .minimum_balance(size as usize) .max(1) .saturating_sub(account_info.lamports()); + msg!( + "required_lamports: {}, payer has {}", + required_lamports, + payer_info.lamports() + ); + // 1. Transfer the required rent to the account if required_lamports > 0 { transfer_lamports(payer_info, account_info, required_lamports)?; @@ -81,10 +87,7 @@ pub fn allocate_account_and_assign_owner( // At this point the account is still owned by the system program msg!(" create_account() allocate space"); invoke_signed( - &system_instruction::allocate( - account_info.key, - size.try_into().unwrap(), - ), + &system_instruction::allocate(account_info.key, size), // 0. `[WRITE, SIGNER]` New account &[account_info.clone()], &[signer_seeds], diff --git a/test-integration/schedulecommit/client/src/schedule_commit_context.rs b/test-integration/schedulecommit/client/src/schedule_commit_context.rs index 05313a62b..e42db0b5a 100644 --- a/test-integration/schedulecommit/client/src/schedule_commit_context.rs +++ b/test-integration/schedulecommit/client/src/schedule_commit_context.rs @@ -5,7 +5,7 @@ use integration_test_tools::IntegrationTestContext; use log::*; use program_schedulecommit::api::{ delegate_account_cpi_instruction, init_account_instruction, - init_payer_escrow, pda_and_bump, + init_order_book_instruction, init_payer_escrow, }; use solana_rpc_client::rpc_client::{RpcClient, SerializableTransaction}; use solana_rpc_client_api::config::RpcSendTransactionConfig; @@ -13,6 +13,7 @@ use solana_rpc_client_api::config::RpcSendTransactionConfig; use solana_sdk::signer::SeedDerivable; use solana_sdk::{ commitment_config::CommitmentConfig, + compute_budget::ComputeBudgetInstruction, hash::Hash, native_token::LAMPORTS_PER_SOL, pubkey::Pubkey, @@ -30,6 +31,7 @@ pub struct ScheduleCommitTestContext { pub payer_ephem: Keypair, // The Payer keypairs along with its PDA pubkey which we'll commit pub committees: Vec<(Keypair, Pubkey)>, + user_seed: Vec, common_ctx: IntegrationTestContext, } @@ -61,14 +63,21 @@ impl ScheduleCommitTestContext { // ----------------- // Init // ----------------- - pub fn try_new_random_keys(ncommittees: usize) -> Result { - Self::try_new_internal(ncommittees, true) + pub fn try_new_random_keys( + ncommittees: usize, + user_seed: &[u8], + ) -> Result { + Self::try_new_internal(ncommittees, true, user_seed) } - pub fn try_new(ncommittees: usize) -> Result { - Self::try_new_internal(ncommittees, false) + pub fn try_new(ncommittees: usize, user_seed: &[u8]) -> Result { + Self::try_new_internal(ncommittees, false, user_seed) } - fn try_new_internal(ncommittees: usize, random_keys: bool) -> Result { + fn try_new_internal( + ncommittees: usize, + random_keys: bool, + user_seed: &[u8], + ) -> Result { let ictx = IntegrationTestContext::try_new()?; let payer_chain = if random_keys { @@ -103,7 +112,10 @@ impl ScheduleCommitTestContext { lamports, ) .unwrap(); - let (pda, _) = pda_and_bump(&payer_ephem.pubkey()); + let (pda, _bump) = Pubkey::find_program_address( + &[user_seed, &payer_ephem.pubkey().as_ref()], + &program_schedulecommit::ID, + ); (payer_ephem, pda) }) .collect::>(); @@ -143,6 +155,7 @@ impl ScheduleCommitTestContext { payer_ephem, committees, common_ctx: ictx, + user_seed: user_seed.to_vec(), }) } @@ -150,17 +163,44 @@ impl ScheduleCommitTestContext { // Schedule Commit specific Transactions // ----------------- pub fn init_committees(&self) -> Result { - let ixs = self - .committees - .iter() - .map(|(player, committee)| { + let mut ixs = vec![ + ComputeBudgetInstruction::set_compute_unit_limit(1_400_000), + ComputeBudgetInstruction::set_compute_unit_price(10_000), + ]; + if self.user_seed == b"magic_schedule_commit" { + ixs.extend(self.committees.iter().map(|(player, committee)| { init_account_instruction( self.payer_chain.pubkey(), player.pubkey(), *committee, ) - }) - .collect::>(); + })); + } else { + ixs.extend(self.committees.iter().map( + |(book_manager, committee)| { + init_order_book_instruction( + self.payer_chain.pubkey(), + book_manager.pubkey(), + *committee, + ) + }, + )); + + //// TODO (snawaz): currently the size of delegatable-account cannot be + //// more than 10K, else delegation will fail. So Let's revisit this when + //// we relax the limit on the account size, then we can use larger + //// account, say even 10 MB, and execute CommitDiff. + // + // ixs.extend(self.committees.iter().flat_map( + // |(payer, committee)| { + // [grow_order_book_instruction( + // payer.pubkey(), + // *committee, + // 10 * 1024 + // )] + // }, + // )); + } let mut signers = self .committees @@ -223,6 +263,7 @@ impl ScheduleCommitTestContext { let ix = delegate_account_cpi_instruction( self.payer_chain.pubkey(), player.pubkey(), + &self.user_seed, ); ixs.push(ix); } diff --git a/test-integration/schedulecommit/client/src/verify.rs b/test-integration/schedulecommit/client/src/verify.rs index 11098f9c9..d383cbc2f 100644 --- a/test-integration/schedulecommit/client/src/verify.rs +++ b/test-integration/schedulecommit/client/src/verify.rs @@ -1,5 +1,5 @@ use integration_test_tools::scheduled_commits::ScheduledCommitResult; -use program_schedulecommit::MainAccount; +use program_schedulecommit::{MainAccount, OrderBookOwned}; use solana_sdk::signature::Signature; use crate::ScheduleCommitTestContext; @@ -12,3 +12,12 @@ pub fn fetch_and_verify_commit_result_from_logs( res.confirm_commit_transactions_on_chain(ctx).unwrap(); res } + +pub fn fetch_and_verify_order_book_commit_result_from_logs( + ctx: &ScheduleCommitTestContext, + sig: Signature, +) -> ScheduledCommitResult { + let res = ctx.fetch_schedule_commit_result(sig).unwrap(); + res.confirm_commit_transactions_on_chain(ctx).unwrap(); + res +} diff --git a/test-integration/schedulecommit/test-scenarios/Cargo.toml b/test-integration/schedulecommit/test-scenarios/Cargo.toml index 93d93863a..3cda55a8f 100644 --- a/test-integration/schedulecommit/test-scenarios/Cargo.toml +++ b/test-integration/schedulecommit/test-scenarios/Cargo.toml @@ -16,3 +16,5 @@ solana-rpc-client = { workspace = true } solana-rpc-client-api = { workspace = true } solana-sdk = { workspace = true } test-kit = { workspace = true } +rand = { workspace = true } +borsh = { workspace = true } diff --git a/test-integration/schedulecommit/test-scenarios/tests/01_commits.rs b/test-integration/schedulecommit/test-scenarios/tests/01_commits.rs index aca0b53da..b268b6d33 100644 --- a/test-integration/schedulecommit/test-scenarios/tests/01_commits.rs +++ b/test-integration/schedulecommit/test-scenarios/tests/01_commits.rs @@ -27,7 +27,8 @@ mod utils; #[test] fn test_committing_one_account() { run_test!({ - let ctx = get_context_with_delegated_committees(1); + let ctx = + get_context_with_delegated_committees(1, b"magic_schedule_commit"); let ScheduleCommitTestContextFields { payer_ephem: payer, @@ -80,7 +81,8 @@ fn test_committing_one_account() { #[test] fn test_committing_two_accounts() { run_test!({ - let ctx = get_context_with_delegated_committees(2); + let ctx = + get_context_with_delegated_committees(2, b"magic_schedule_commit"); let ScheduleCommitTestContextFields { payer_ephem: payer, diff --git a/test-integration/schedulecommit/test-scenarios/tests/02_commit_and_undelegate.rs b/test-integration/schedulecommit/test-scenarios/tests/02_commit_and_undelegate.rs index 120650a70..6c768362f 100644 --- a/test-integration/schedulecommit/test-scenarios/tests/02_commit_and_undelegate.rs +++ b/test-integration/schedulecommit/test-scenarios/tests/02_commit_and_undelegate.rs @@ -4,10 +4,17 @@ use integration_test_tools::{ transactions::send_and_confirm_instructions_with_payer, }; use log::*; -use program_schedulecommit::api::{ - increase_count_instruction, schedule_commit_and_undelegate_cpi_instruction, - schedule_commit_and_undelegate_cpi_with_mod_after_instruction, +use program_schedulecommit::{ + api::{ + increase_count_instruction, + schedule_commit_and_undelegate_cpi_instruction, + schedule_commit_and_undelegate_cpi_with_mod_after_instruction, + schedule_commit_diff_instruction_for_order_book, + update_order_book_instruction, + }, + BookUpdate, OrderLevel, }; +use rand::{RngCore, SeedableRng}; use schedulecommit_client::{ verify, ScheduleCommitTestContext, ScheduleCommitTestContextFields, }; @@ -45,7 +52,8 @@ fn commit_and_undelegate_one_account( Signature, Result, ) { - let ctx = get_context_with_delegated_committees(1); + let ctx = + get_context_with_delegated_committees(1, b"magic_schedule_commit"); let ScheduleCommitTestContextFields { payer_ephem: payer, committees, @@ -99,6 +107,62 @@ fn commit_and_undelegate_one_account( (ctx, *sig, tx_res) } +fn commit_and_undelegate_order_book_account( + update: BookUpdate, +) -> ( + ScheduleCommitTestContext, + Signature, + Result, +) { + let ctx = get_context_with_delegated_committees(1, b"order_book"); + let ScheduleCommitTestContextFields { + payer_ephem, + committees, + commitment, + ephem_client, + .. + } = ctx.fields(); + + assert_eq!(committees.len(), 1); + + let ixs = [ + update_order_book_instruction( + payer_ephem.pubkey(), + committees[0].1, + update, + ), + schedule_commit_diff_instruction_for_order_book( + payer_ephem.pubkey(), + committees[0].1, + magicblock_magic_program_api::id(), + magicblock_magic_program_api::MAGIC_CONTEXT_PUBKEY, + ), + ]; + + let ephem_blockhash = ephem_client.get_latest_blockhash().unwrap(); + let tx = Transaction::new_signed_with_payer( + &ixs, + Some(&payer_ephem.pubkey()), + &[&payer_ephem], + ephem_blockhash, + ); + + let sig = tx.get_signature(); + let tx_res = ephem_client + .send_and_confirm_transaction_with_spinner_and_config( + &tx, + *commitment, + RpcSendTransactionConfig { + skip_preflight: true, + ..Default::default() + }, + ); + println!("txhash (scheduled_commit): {:?}", tx_res); + + debug!("Commit and Undelegate Transaction result: '{:?}'", tx_res); + (ctx, *sig, tx_res) +} + fn commit_and_undelegate_two_accounts( modify_after: bool, ) -> ( @@ -106,7 +170,8 @@ fn commit_and_undelegate_two_accounts( Signature, Result, ) { - let ctx = get_context_with_delegated_committees(2); + let ctx = + get_context_with_delegated_committees(2, b"magic_schedule_commit"); let ScheduleCommitTestContextFields { payer_ephem: payer, committees, @@ -176,6 +241,76 @@ fn test_committing_and_undelegating_one_account() { }); } +#[test] +fn test_committing_and_undelegating_huge_order_book_account() { + run_test!({ + let (rng_seed, update) = { + use rand::{ + rngs::{OsRng, StdRng}, + Rng, + }; + let rng_seed = OsRng.next_u64(); + println!("Use {rng_seed} as seed to random generator"); + let mut random = StdRng::seed_from_u64(rng_seed); + let mut update = BookUpdate::default(); + update.bids.extend((0..random.gen_range(5..10)).map(|_| { + OrderLevel { + price: random.gen_range(75000..90000), + size: random.gen_range(1..10), + } + })); + update.asks.extend((0..random.gen_range(5..10)).map(|_| { + OrderLevel { + price: random.gen_range(125000..150000), + size: random.gen_range(1..10), + } + })); + println!( + "BookUpdate: total = {}, bids = {}, asks = {}", + update.bids.len() + update.asks.len(), + update.bids.len(), + update.asks.len() + ); + (rng_seed, update) + }; + let (ctx, sig, tx_res) = + commit_and_undelegate_order_book_account(update.clone()); + info!("'{}' {:?}", sig, tx_res); + + let res = verify::fetch_and_verify_order_book_commit_result_from_logs( + &ctx, sig, + ); + + let book = res + .included + .values() + .next() + .expect("one order-book must exist"); + + assert_eq!( + book.bids.len(), + update.bids.len(), + "Use {rng_seed} to generate the input and investigate" + ); + assert_eq!( + book.asks.len(), + update.asks.len(), + "Use {rng_seed} to generate the input and investigate" + ); + assert_eq!( + book.bids, update.bids, + "Use {rng_seed} to generate the input and investigate" + ); + assert_eq!( + book.asks, update.asks, + "Use {rng_seed} to generate the input and investigate" + ); + + assert_one_committee_was_committed(&ctx, &res, true); + assert_one_committee_account_was_undelegated_on_chain(&ctx); + }); +} + #[test] fn test_committing_and_undelegating_two_accounts_success() { run_test!({ diff --git a/test-integration/schedulecommit/test-scenarios/tests/03_commits_fee_payer.rs b/test-integration/schedulecommit/test-scenarios/tests/03_commits_fee_payer.rs new file mode 100644 index 000000000..b7118b26a --- /dev/null +++ b/test-integration/schedulecommit/test-scenarios/tests/03_commits_fee_payer.rs @@ -0,0 +1,135 @@ +use integration_test_tools::run_test; +use log::*; +use program_schedulecommit::api::schedule_commit_with_payer_cpi_instruction; +use schedulecommit_client::{verify, ScheduleCommitTestContextFields}; +use solana_rpc_client::rpc_client::SerializableTransaction; +use solana_rpc_client_api::config::RpcSendTransactionConfig; +use solana_sdk::{signer::Signer, transaction::Transaction}; +use test_tools_core::init_logger; +use utils::{ + assert_two_committees_synchronized_count, + assert_two_committees_were_committed, + get_context_with_delegated_committees, +}; + +use crate::utils::{ + assert_feepayer_was_committed, + get_context_with_delegated_committees_without_payer_escrow, +}; + +mod utils; + +#[test] +fn test_committing_fee_payer_without_escrowing_lamports() { + // NOTE: this test requires the following config + // [validator] + // base_fees = 1000 + // see ../../../configs/schedulecommit-conf-fees.ephem.toml + run_test!({ + let ctx = get_context_with_delegated_committees_without_payer_escrow( + 2, + b"magic_schedule_commit", + ); + + let ScheduleCommitTestContextFields { + payer, + committees, + commitment, + ephem_client, + ephem_blockhash, + .. + } = ctx.fields(); + + let ix = schedule_commit_with_payer_cpi_instruction( + payer.pubkey(), + magicblock_magic_program_api::id(), + magicblock_magic_program_api::MAGIC_CONTEXT_PUBKEY, + &committees + .iter() + .map(|(player, _)| player.pubkey()) + .collect::>(), + &committees.iter().map(|(_, pda)| *pda).collect::>(), + ); + + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[&payer], + *ephem_blockhash, + ); + + let sig = tx.get_signature(); + let res = ephem_client + .send_and_confirm_transaction_with_spinner_and_config( + &tx, + *commitment, + RpcSendTransactionConfig { + skip_preflight: true, + ..Default::default() + }, + ); + info!("{} '{:?}'", sig, res); + + assert!(res.is_err()); + assert!(res + .err() + .unwrap() + .to_string() + .contains("DoesNotHaveEscrowAccount")); + }); +} + +#[test] +fn test_committing_fee_payer_escrowing_lamports() { + run_test!({ + let ctx = + get_context_with_delegated_committees(2, b"magic_schedule_commit"); + + let ScheduleCommitTestContextFields { + payer, + committees, + commitment, + ephem_client, + ephem_blockhash, + .. + } = ctx.fields(); + + let ix = schedule_commit_with_payer_cpi_instruction( + payer.pubkey(), + magicblock_magic_program_api::id(), + magicblock_magic_program_api::MAGIC_CONTEXT_PUBKEY, + &committees + .iter() + .map(|(player, _)| player.pubkey()) + .collect::>(), + &committees.iter().map(|(_, pda)| *pda).collect::>(), + ); + + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&payer.pubkey()), + &[&payer], + *ephem_blockhash, + ); + + let sig = tx.get_signature(); + let res = ephem_client + .send_and_confirm_transaction_with_spinner_and_config( + &tx, + *commitment, + RpcSendTransactionConfig { + skip_preflight: true, + ..Default::default() + }, + ); + info!("{} '{:?}'", sig, res); + assert!(res.is_ok()); + + let res = verify::fetch_and_verify_commit_result_from_logs(&ctx, *sig); + assert_two_committees_were_committed(&ctx, &res, true); + assert_two_committees_synchronized_count(&ctx, &res, 1); + + // The fee payer should have been committed + assert_feepayer_was_committed(&ctx, &res, true); + }); +} diff --git a/test-integration/schedulecommit/test-scenarios/tests/utils/mod.rs b/test-integration/schedulecommit/test-scenarios/tests/utils/mod.rs index eb8f80dfb..cf1ae2c87 100644 --- a/test-integration/schedulecommit/test-scenarios/tests/utils/mod.rs +++ b/test-integration/schedulecommit/test-scenarios/tests/utils/mod.rs @@ -15,16 +15,22 @@ use solana_sdk::{ // ----------------- pub fn get_context_with_delegated_committees( ncommittees: usize, + user_seed: &[u8], ) -> ScheduleCommitTestContext { let ctx = if std::env::var("FIXED_KP").is_ok() { - ScheduleCommitTestContext::try_new(ncommittees) + ScheduleCommitTestContext::try_new(ncommittees, user_seed) } else { - ScheduleCommitTestContext::try_new_random_keys(ncommittees) + ScheduleCommitTestContext::try_new_random_keys(ncommittees, user_seed) } .unwrap(); + println!("get_context_with_delegated_committees inside"); + + let txhash = ctx.init_committees().unwrap(); + println!("txhash (init_committees): {}", txhash); + + let txhash = ctx.delegate_committees().unwrap(); + println!("txhash (delegate_committees): {}", txhash); - ctx.init_committees().unwrap(); - ctx.delegate_committees().unwrap(); ctx } @@ -32,11 +38,13 @@ pub fn get_context_with_delegated_committees( // Asserts // ----------------- #[allow(dead_code)] // used in 02_commit_and_undelegate.rs -pub fn assert_one_committee_was_committed( +pub fn assert_one_committee_was_committed( ctx: &ScheduleCommitTestContext, - res: &ScheduledCommitResult, + res: &ScheduledCommitResult, is_single_stage: bool, -) { +) +where + T: std::fmt::Debug + borsh::BorshDeserialize + PartialEq + Eq { let pda = ctx.committees[0].1; assert_eq!(res.included.len(), 1, "includes 1 pda"); diff --git a/test-integration/test-ledger-restore/tests/08_commit_update.rs b/test-integration/test-ledger-restore/tests/08_commit_update.rs index 699a7d03d..da24d2af2 100644 --- a/test-integration/test-ledger-restore/tests/08_commit_update.rs +++ b/test-integration/test-ledger-restore/tests/08_commit_update.rs @@ -52,7 +52,7 @@ fn test_restore_ledger_committed_and_updated_account() { fn write(ledger_path: &Path, payer: &Keypair) -> (Child, u64) { let programs = get_programs_with_flexi_counter(); - let (_, mut validator, ctx) = setup_validator_with_local_remote( + let (_tmpdir, mut validator, ctx) = setup_validator_with_local_remote( ledger_path, Some(programs), true, @@ -167,7 +167,7 @@ fn read(ledger_path: &Path, payer_kp: &Keypair) -> Child { let payer = &payer_kp.pubkey(); let programs = get_programs_with_flexi_counter(); - let (_, mut validator, ctx) = setup_validator_with_local_remote( + let (_tmpdir, mut validator, ctx) = setup_validator_with_local_remote( ledger_path, Some(programs), false, diff --git a/test-integration/test-tools/src/integration_test_context.rs b/test-integration/test-tools/src/integration_test_context.rs index f31287102..e9fd9f672 100644 --- a/test-integration/test-tools/src/integration_test_context.rs +++ b/test-integration/test-tools/src/integration_test_context.rs @@ -154,7 +154,8 @@ impl IntegrationTestContext { rpc_client: Option<&RpcClient>, label: &str, ) -> Option> { - let rpc_client = rpc_client.or(self.chain_client.as_ref())?; + let rpc_client = + rpc_client.expect("rpc_client for [{}] does not exist"); // Try this up to 50 times since devnet here returns the version response instead of // the EncodedConfirmedTransactionWithStatusMeta at times @@ -163,6 +164,11 @@ impl IntegrationTestContext { &sig, RpcTransactionConfig { commitment: Some(self.commitment), + max_supported_transaction_version: if label == "chain" { + Some(0) + } else { + None + }, ..Default::default() }, ) { diff --git a/test-integration/test-tools/src/scheduled_commits.rs b/test-integration/test-tools/src/scheduled_commits.rs index 038326a06..95260078f 100644 --- a/test-integration/test-tools/src/scheduled_commits.rs +++ b/test-integration/test-tools/src/scheduled_commits.rs @@ -179,7 +179,7 @@ impl IntegrationTestContext { { // 1. Find scheduled commit sent signature via // ScheduledCommitSent signature: - let (ephem_logs, scheduled_commmit_sent_sig) = { + let (ephem_logs_l1, scheduled_commmit_sent_sig) = { let logs = self.fetch_ephemeral_logs(sig).with_context(|| { format!( "Scheduled commit sent logs not found for sig {:?}", @@ -195,18 +195,22 @@ impl IntegrationTestContext { (logs, sig) }; + println!("Ephem Logs level-1: {:#?}", ephem_logs_l1); + // 2. Find chain commit signatures - let chain_logs = self + let ephem_logs_l2 = self .fetch_ephemeral_logs(scheduled_commmit_sent_sig) .with_context(|| { format!( "Logs {:#?}\nScheduled commit sent sig {:?}", - ephem_logs, scheduled_commmit_sent_sig + ephem_logs_l1, scheduled_commmit_sent_sig ) })?; + println!("Ephem Logs level-2: {:#?}", ephem_logs_l2); + let (included, excluded, feepayers, sigs) = - extract_sent_commit_info_from_logs(&chain_logs); + extract_sent_commit_info_from_logs(&ephem_logs_l2); let mut committed_accounts = HashMap::new(); for pubkey in included { @@ -226,6 +230,10 @@ impl IntegrationTestContext { }; } + for sig in sigs.iter() { + self.dump_chain_logs(sig.clone()); + } + Ok(ScheduledCommitResult { included: committed_accounts, excluded,