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) を使って cranks を実装する方法を説明します。流れは次のとおりです。
- Solana base layer 上で counter を初期化する
- 高速実行のため counter account を Ephemeral Rollup に delegate する
- counter を自動 increment する crank task を schedule する
- 指定間隔で crank を自動実行する
- 完了後に account を undelegate して Solana base layer に戻す
実行フロー
1. User が Solana base layer 上で `initialize()` を呼び出す
└─> `count = 0` の Counter PDA を作成
2. User が Solana base layer 上で `delegate()` を呼び出す
└─> Counter account を Ephemeral Rollup に移動
3. User が Ephemeral Rollup 上で `schedule_increment()` を呼び出す
└─> MagicBlock program へ CPI
└─> 次の内容で task を schedule:
- 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. User が Ephemeral Rollup 上で `undelegate()` を呼び出す
└─> 変更を commit し、Counter を Solana base layer に戻す
コアコンポーネント
スケジュール対象の関数
たとえば、counter に対して次のような単純な increment instruction を schedule したいとします。
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(())
}
Increment をスケジュールする関数
crank scheduling の中心となるロジックは次のとおりです。
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(())
}
Key Points:
- counter を increment する instruction を作成する
- それを MagicBlock 向けの
ScheduleTask instruction に serialize する
- CPI(Cross-Program Invocation)で MagicBlock program を呼び出す
- 実際の scheduling と execution は MagicBlock program が担当する
スケジュール引数
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct ScheduleIncrementArgs {
pub task_id: u64, // task の一意識別子
pub execution_interval_millis: u64, // 実行間隔(ミリ秒)
pub iterations: u64, // 実行回数
}
ScheduleIncrement の Context
#[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>,
}
重要: CPI 呼び出し後に Anchor が古いデータを再 serialize しないよう、Account<Counter> ではなく AccountInfo を使っています。