Documentation Index
Fetch the complete documentation index at: https://docs.magicblock.gg/llms.txt
Use this file to discover all available pages before exploring further.
本指南演示如何在 Anchor 框架下,借助 MagicBlock 的 Ephemeral Rollups(ER) 实现 crank。整体流程如下:
- 在 Solana 基础层初始化 counter
- 将 counter account 委托到 Ephemeral Rollup 以获得更快执行
- 调度一个自动递增 counter 的 crank 任务
- 按指定间隔自动执行 crank
- 完成后将 account 取消委托并提交回 Solana 基础层
执行流程
1. 用户在 Solana 基础层调用 `initialize()`
└─> 创建 `count = 0` 的 Counter PDA
2. 用户在 Solana 基础层调用 `delegate()`
└─> 将 Counter account 移动到 Ephemeral Rollup
3. 用户在 Ephemeral Rollup 上调用 `schedule_increment()`
└─> 对 MagicBlock program 发起 CPI
└─> 调度一个任务,参数如下:
- task_id: 1
- interval: 100ms
- iterations: 3
- instruction: increment()
4. MagicBlock 自动执行 `increment()` 3 次:
└─> 第 1 次执行:`count = 1`(T+0ms)
└─> 第 2 次执行:`count = 2`(T+100ms)
└─> 第 3 次执行:`count = 3`(T+200ms)
5. 用户在 Ephemeral Rollup 上调用 `undelegate()`
└─> 提交变更并将 Counter 移回 Solana 基础层
核心组成部分
被调度的函数
假设你想为一个 counter 调度下面这个简单的递增指令。
pub fn increment(ctx: Context<Increment>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count += 1;
if counter.count > 1000 {
counter.count = 0;
}
msg!("PDA {} count: {}", counter.key(), counter.count);
Ok(())
}
调度递增函数
核心的 crank 调度逻辑如下:
pub fn schedule_increment(ctx: Context<ScheduleIncrement>, args: ScheduleIncrementArgs) -> Result<()> {
let increment_ix = Instruction {
program_id: crate::ID,
accounts: vec![AccountMeta::new(ctx.accounts.counter.key(), false)],
// Defining the instruction to call.
data: anchor_lang::InstructionData::data(&crate::instruction::Increment {}),
};
let ix_data = bincode::serialize(&MagicBlockInstruction::ScheduleTask(
ScheduleTaskArgs {
task_id: args.task_id,
execution_interval_millis: args.execution_interval_millis,
iterations: args.iterations,
instructions: vec![increment_ix],
},
))
.map_err(|err| {
msg!("ERROR: failed to serialize args {:?}", err);
ProgramError::InvalidArgument
})?;
let schedule_ix = Instruction::new_with_bytes(
MAGIC_PROGRAM_ID,
&ix_data,
vec![
AccountMeta::new(ctx.accounts.payer.key(), true),
AccountMeta::new(ctx.accounts.counter.key(), false),
],
);
invoke_signed(
&schedule_ix,
&[
ctx.accounts.payer.to_account_info(),
ctx.accounts.counter.to_account_info(),
],
&[],
)?;
Ok(())
}
关键点:
- 创建一个用于递增 counter 的指令
- 将其序列化为发送给 MagicBlock 的
ScheduleTask 指令
- 使用 CPI(Cross-Program Invocation)调用 MagicBlock program
- 由 MagicBlock program 负责实际的调度与执行
调度参数
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct ScheduleIncrementArgs {
pub task_id: u64, // 任务的唯一标识符
pub execution_interval_millis: u64, // 两次执行之间的毫秒间隔
pub iterations: u64, // 总执行次数
}
ScheduleIncrement 上下文
#[derive(Accounts)]
pub struct ScheduleIncrement<'info> {
/// CHECK: used for CPI
#[account()]
pub magic_program: AccountInfo<'info>,
#[account(mut)]
pub payer: Signer<'info>,
/// CHECK: Passed to CPI - using AccountInfo to avoid Anchor re-serializing stale data after CPI
#[account(mut, seeds = [COUNTER_SEED], bump)]
pub counter: AccountInfo<'info>,
/// CHECK: used for CPI
pub program: AccountInfo<'info>,
}
重要说明:这里使用 AccountInfo 而不是 Account<Counter>,以避免 Anchor 在 CPI 调用后重新序列化过期数据。