Rust区块链调度器库pallet-scheduler的使用,实现Substrate链上任务定时执行与自动化管理

Rust区块链调度器库pallet-scheduler的使用,实现Substrate链上任务定时执行与自动化管理

概述

pallet-scheduler模块提供了在指定区块号或指定周期调度调用的能力。这些调度可以是命名的或匿名的,并且可以被取消。

注意:调度调用将使用默认的origin过滤器进行分发,即除了root origin外所有origin都使用frame_system::Config::BaseCallFilter过滤器。而调用fn schedule时使用的origin中的过滤器不会被使用。

如果使用proxy或其他添加过滤器的机制调度调用,那么在分发调度调用时不会使用这些过滤器。

接口

可调度函数

  • schedule - 调度一个可能在指定区块周期发生的调用,并指定优先级
  • cancel - 取消一个已调度的调用,由区块号和索引指定
  • schedule_named - 通过附加的Vec<u8>参数扩展schedule接口,可用于识别
  • cancel_named - cancel函数的命名对应函数

完整示例

以下是使用pallet-scheduler实现Substrate链上任务定时执行的完整示例:

use frame_support::{dispatch::DispatchResult, traits::schedule::DispatchTime};
use frame_system::pallet_prelude::OriginFor;
use pallet_scheduler::{self as scheduler};

// 1. 定义你的Runtime配置
pub trait Config: frame_system::Config + scheduler::Config {
    // 你的其他配置
}

// 2. 实现定时任务调度
pub fn schedule_task<T: Config>(
    origin: OriginFor<T>,
    call: <T as Config>::Call,
    when: DispatchTime<T::BlockNumber>,
) -> DispatchResult {
    // 使用pallet-scheduler调度任务
    scheduler::Pallet::<T>::schedule(
        origin,
        when,              // 调度时间(区块号)
        None,              // 优先级(可选)
        Box::new(call),    // 要调度的Call
    )?;
    
    Ok(())
}

// 3. 实现命名定时任务调度
pub fn schedule_named_task<T: Config>(
    origin: OriginFor<T>,
    id: Vec<u8>,          // 任务ID
    call: <T as Config>::Call,
    when: DispatchTime<T::BlockNumber>,
) -> DispatchResult {
    // 使用pallet-scheduler调度命名任务
    scheduler::Pallet::<T>::schedule_named(
        origin,
        id,                // 任务标识符
        when,              // 调度时间(区块号)
        None,              // 优先级(可选)
        Box::new(call),    // 要调度的Call
    )?;
    
    Ok(())
}

// 4. 取消任务
pub fn cancel_task<T: Config>(
    origin: OriginFor<T>,
    when: T::BlockNumber,
    index: u32,
) -> DispatchResult {
    // 取消指定区块和索引的任务
    scheduler::Pallet::<T>::cancel(origin, when, index)?;
    Ok(())
}

// 5. 取消命名任务
pub fn cancel_named_task<T: Config>(
    origin: OriginFor<T>,
    id: Vec<u8>,
) -> DispatchResult {
    // 取消命名任务
    scheduler::Pallet::<T>::cancel_named(origin, id)?;
    Ok(())
}

安装

要使用pallet-scheduler,请将以下内容添加到你的Cargo.toml中:

pallet-scheduler = "43.0.0"

或者运行以下Cargo命令:

cargo add pallet-scheduler

许可证

Apache-2.0


1 回复

Rust区块链调度器库pallet-scheduler使用指南

概述

pallet-scheduler是Substrate框架中的一个核心模块,用于在区块链上实现定时任务的调度和执行。它允许开发者安排未来某个区块高度或特定时间执行特定的调用,为区块链自动化提供了强大的支持。

主要特性

  • 支持基于区块高度和时间的任务调度
  • 提供优先级队列管理任务执行顺序
  • 可配置的任务权重和费用计算
  • 与Substrate其他模块无缝集成

基本使用方法

1. 在runtime中引入pallet-scheduler

首先需要在你的runtime中引入这个pallet:

// runtime/src/lib.rs

impl pallet_scheduler::Config for Runtime {
    type Event = Event;
    type Origin = Origin;
    type PalletsOrigin = OriginCaller;
    type Call = Call;
    type MaximumWeight = MaximumSchedulerWeight;
    type ScheduleOrigin = EnsureRoot<AccountId>;
    type MaxScheduledPerBlock = ConstU32<50>;
    type WeightInfo = pallet_scheduler::weights::SubstrateWeight<Runtime>;
}

construct_runtime!(
    pub enum Runtime where
        Block = Block,
        NodeBlock = opaque::Block,
        UncheckedExtrinsic = UncheckedExtrinsic
    {
        // ... 其他pallet
        Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event<T>},
    }
);

2. 调度任务的基本API

安排任务

// 安排一个任务在100个区块后执行
let call = Box::new(Call::System::remark(b"Hello Scheduler".to_vec()));
let _ = Scheduler::schedule(
    RuntimeOrigin::root(),
    100, // 延迟的区块数
    None, // 优先级
    None, // 任务ID
    call,
);

安排命名任务

let call = Box::new(Call::Balances::transfer(dest, amount));
let task_id = b"monthly_payment".to_vec();

let _ = Scheduler::schedule_named(
    RuntimeOrigin::root(),
    task_id,
    100, // 延迟的区块数
    None, // 优先级
    None, // 周期
    call,
);

取消任务

let task_id = b"monthly_payment".to_vec();
let _ = Scheduler::cancel_named(RuntimeOrigin::root(), task_id);

高级用法

周期性任务

let call = Box::new(Cull::System::remark(b"Recurring task".to_vec()));
let _ = Scheduler::schedule(
    RuntimeOrigin::root(),
    10, // 初始延迟
    None, // 优先级
    Some((10, 5)), // (周期, 重复次数)
    call,
);

带优先级的任务

let call = Box::new(Call::System::remark(b"High priority task".to_vec()));
let _ = Scheduler::schedule(
    RuntimeOrigin::root(),
    5, // 延迟
    Some(100), // 高优先级
    None,
    call,
);

实际应用示例

DAO自动投票关闭

// 安排7天后自动关闭投票
let close_call = Box::new(Call::Dao::close_voting(poll_id));
let blocks_in_7_days = 7 * 24 * 60 * 60 / 6; // 假设6秒一个区块

let _ = Scheduler::schedule_named(
    RuntimeOrigin::signed(dao_account),
    poll_id.to_le_bytes().to_vec(),
    blocks_in_7_days,
    None,
    None,
    close_call,
);

定期代币分发

// 每月1号分发代币
fn schedule_monthly_distribution() {
    let distribution_call = Box::new(Call::Rewards::distribute_monthly());
    let blocks_in_month = 30 * 24 * 60 * 60 / 6; // 约30天的区块数
    
    let _ = Scheduler::schedule_named(
        RuntimeOrigin::root(),
        b"monthly_distrib".to_vec(),
        blocks_in_month,
        None,
        Some((blocks_in_month, None)), // 无限重复
        distribution_call,
    );
}

完整示例Demo

下面是一个完整的pallet-scheduler使用示例,包含runtime集成和实际调用:

// runtime/src/lib.rs

// 1. 引入必要的依赖
use frame_support::traits::Contains;
use frame_system::EnsureRoot;

// 2. 配置Scheduler pallet
impl pallet_scheduler::Config for Runtime {
    type Event = Event;
    type Origin = Origin;
    type PalletsOrigin = OriginCaller;
    type Call = Call;
    type MaximumWeight = MaximumSchedulerWeight;
    type ScheduleOrigin = EnsureRoot<AccountId>; // 只有root可以调度任务
    type MaxScheduledPerBlock = ConstU32<50>; // 每个区块最多50个调度任务
    type WeightInfo = pallet_scheduler::weights::SubstrateWeight<Runtime>;
}

// 3. 在construct_runtime!宏中添加Scheduler
construct_runtime!(
    pub enum Runtime where
        Block = Block,
        NodeBlock = opaque::Block,
        UncheckedExtrinsic = UncheckedExtrinsic
    {
        // ... 其他pallet
        Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event<T>},
    }
);

// 4. 在某个pallet中使用调度器
impl<T: Config> Pallet<T> {
    pub fn schedule_delayed_action(origin: OriginFor<T>, delay: u32) -> DispatchResult {
        // 验证调用者权限
        ensure_root(origin)?;
        
        // 准备要调用的函数
        let call = Box::new(Call::System::remark(b"Delayed action".to_vec()));
        
        // 调度任务
        Scheduler::schedule(
            RuntimeOrigin::root(),
            delay,
            None, // 默认优先级
            None, // 不指定任务ID
            call,
        )?;
        
        Ok(())
    }
    
    pub fn schedule_recurring_payment(
        origin: OriginFor<T>, 
        recipient: AccountId,
        amount: Balance,
        interval: u32,
        repeats: Option<u32>
    ) -> DispatchResult {
        // 验证调用者权限
        ensure_root(origin)?;
        
        // 准备转账调用
        let call = Box::new(Call::Balances::transfer(recipient, amount));
        
        // 任务ID使用recipient地址和amount组合
        let task_id = format!("payment_{}_{}", recipient, amount).into_bytes();
        
        // 调度周期性任务
        Scheduler::schedule_named(
            RuntimeOrigin::root(),
            task_id,
            interval, // 初始延迟
            None, // 默认优先级
            Some((interval, repeats)), // 周期和重复次数
            call,
        )?;
        
        Ok(())
    }
}

注意事项

  1. 调度任务会占用链上存储空间,需要支付相应的费用
  2. 任务执行时消耗的计算资源会影响区块执行时间
  3. 合理安排任务优先级,避免高优先级任务阻塞其他操作
  4. 考虑使用命名任务以便后续管理
  5. 测试时注意模拟区块前进以验证任务执行

pallet-scheduler为Substrate链提供了强大的自动化能力,合理使用可以大大增强链的功能性和灵活性。

回到顶部