Skip to main content

Обзор

В этом руководстве показано, как реализовать cranks с использованием Эфемерных Роллапов от MagicBlock в рамках фреймворка Anchor. Реализация выполняется по следующему алгоритму:
  1. Инициализируйте счётчик на базовом уровне Solana
  2. Делегируйте аккаунт счётчика в Эфемер Роллап для более быстрого выполнения
  3. Запланируйте задачу активатора, которая будет автоматически увеличивать счётчик
  4. Автоматически выполняйте crank через заданные интервалы времени
  5. После завершения верните аккаунт обратно на базовый уровень Solana

Порядок выполнения

1. User calls initialize() on Solana base layer
   └─> Creates Counter PDA with count = 0

2. User calls delegate() on Solana base layer
   └─> Moves Counter account to Ephemeral Rollup

3. User calls schedule_increment() on Ephemeral Rollup
   └─> CPI to MagicBlock program
       └─> Schedules task with:
           - task_id: 1
           - interval: 100ms
           - iterations: 3
           - instruction: increment()

4. MagicBlock automatically executes increment() 3 times:
   └─> Execution 1: count = 1 (at T+0ms)
   └─> Execution 2: count = 2 (at T+100ms)
   └─> Execution 3: count = 3 (at T+200ms)

5. User calls undelegate() on Ephemeral Rollup
   └─> Commits changes and moves Counter back to Solana base layer

Основные компоненты

«Запланированная функция»

Возьмём, к примеру, ситуацию, когда вы хотите запланировать следующую простую инструкцию для увеличения счётчика.
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(())
}

Запланировать функцию увеличения

Основная логика планирования активаторов:
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(())
}
Ключевые моменты:
  • Создаёт инструкцию для увеличения счётчика
  • Сериализует её в инструкцию ScheduleTask для MagicBlock
  • Использует CPI (Cross-Program Invocation) для вызова программы MagicBlock
  • Программа MagicBlock отвечает за планирование и выполнение

Аргументы планирования

#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct ScheduleIncrementArgs {
    pub task_id: u64,                      // Unique identifier for the task
    pub execution_interval_millis: u64,    // Time between executions in milliseconds
    pub iterations: u64,                   // Number of times to execute
}

Сведения о запланированном увеличении

#[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.